Loading browser/app/profile/001-base-profile.js +4 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,10 @@ pref("security.remote_settings.intermediates.enabled", false); pref("dom.use_components_shim", false); // Enable letterboxing pref("privacy.resistFingerprinting.letterboxing", true); // tor-browser#41917: Center letterboxed area vertically pref("privacy.resistFingerprinting.letterboxing.vcenter", true); // tor-browser#41917: Letterboxing gradient background pref("privacy.resistFingerprinting.letterboxing.gradient", true); // tor-browser#43402: Avoid a resize from the skeleton to the newwin size. // Should be fixed in ESR-140 with Bug 1448423. pref("browser.startup.blankWindow", false); Loading browser/components/tabbrowser/content/tabbrowser.js +4 −0 Original line number Diff line number Diff line Loading @@ -2824,6 +2824,10 @@ stack.className = "browserStack"; stack.appendChild(b); let decorator = document.createXULElement("hbox"); decorator.className = "browserDecorator"; stack.appendChild(decorator); let browserContainer = document.createXULElement("vbox"); browserContainer.className = "browserContainer"; browserContainer.appendChild(stack); Loading browser/themes/shared/tabbrowser/content-area.css +5 −0 Original line number Diff line number Diff line Loading @@ -548,6 +548,11 @@ split-view-footer { width: 100%; height: 100%; /* Override the <stack> `grid-area: 1 / 1` rule with an `auto` placement. * Otherwise the .dialogStack start edges are placed relative to the * center-aligned grid items, rather than the grid's padding area. */ grid-area: auto; /* Hide tab-modal dialogs when a window-modal one is up. */ :root[window-modal-open] .browserStack > &, /* For some printing use cases we need to visually hide the dialog before Loading toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +368 −23 Original line number Diff line number Diff line Loading @@ -14,12 +14,21 @@ const kPrefLetterboxingDimensions = "privacy.resistFingerprinting.letterboxing.dimensions"; const kPrefLetterboxingTesting = "privacy.resistFingerprinting.letterboxing.testing"; const kPrefLetterboxingVcenter = "privacy.resistFingerprinting.letterboxing.vcenter"; const kTopicDOMWindowOpened = "domwindowopened"; const kTopicDOMWindowClosed = "domwindowclosed"; const kTopicFullscreenNavToolbox = "fullscreen-nav-toolbox"; const kPrefVerticalTabs = "sidebar.verticalTabs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { Color: "resource://gre/modules/Color.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "logConsole", () => console.createInstance({ prefix: "RFPHelper", Loading Loading @@ -55,6 +64,10 @@ class _RFPHelper { // Add unconditional observers Services.prefs.addObserver(kPrefResistFingerprinting, this); Services.prefs.addObserver(kPrefLetterboxing, this); Services.prefs.addObserver(kPrefLetterboxingVcenter, this); Services.prefs.addObserver(kPrefVerticalTabs, this); Services.obs.addObserver(this, kTopicFullscreenNavToolbox); XPCOMUtils.defineLazyPreferenceGetter( this, "_letterboxingDimensions", Loading Loading @@ -86,7 +99,10 @@ class _RFPHelper { // Remove unconditional observers Services.prefs.removeObserver(kPrefResistFingerprinting, this); Services.prefs.removeObserver(kPrefLetterboxingVcenter, this); Services.prefs.removeObserver(kPrefLetterboxing, this); Services.prefs.removeObserver(kPrefVerticalTabs, this); Services.obs.removeObserver(this, kTopicFullscreenNavToolbox); // Remove the RFP observers, swallowing exceptions if they weren't present this._removeLanguagePrefObservers(); } Loading @@ -108,6 +124,15 @@ class _RFPHelper { case kTopicDOMWindowClosed: this._handleDOMWindowClosed(subject); break; case kTopicFullscreenNavToolbox: // The `subject` is the gNavToolbox. // Record whether the toobox has been hidden when the browser (not // content) is in fullscreen. subject.ownerGlobal.gBrowser.tabbox.classList.toggle( "letterboxing-nav-toolbox-hidden", data === "hidden" ); break; default: break; } Loading @@ -122,6 +147,13 @@ class _RFPHelper { resizeObserver.observe(browser.parentElement); break; } case "nativethemechange": // NOTE: "nativethemechange" seems to always be sent after // "windowlwthemeupdate". So all the lwtheme CSS properties should be // set to the new theme's values already, so we don't need to wait for // windowlwthemeupdate. this._updateLetterboxingColors(aMessage.currentTarget, true); break; default: break; } Loading @@ -137,8 +169,14 @@ class _RFPHelper { this._handleSpoofEnglishChanged(); break; case kPrefLetterboxing: case kPrefLetterboxingVcenter: this._handleLetterboxingPrefChanged(); break; case kPrefVerticalTabs: if (this.letterboxingEnabled) { forEachWindow(win => this._updateLetterboxingColors(win)); } break; default: break; } Loading Loading @@ -345,7 +383,7 @@ class _RFPHelper { // If not already cached on the document object, traverse the CSSOM and // find the rule applying the default letterboxing styles to browsers // preemptively in order to beat race conditions on tab/window creation return (document._letterboxingMarginsRule ||= (() => { return (document._letterboxingDefaultRule ||= (() => { const LETTERBOX_CSS_SELECTOR = ".letterboxing"; const LETTERBOX_CSS_URL = "chrome://global/content/resistfingerprinting/letterboxing.css"; Loading Loading @@ -552,26 +590,22 @@ class _RFPHelper { if (lastRoundedSize) { // Check whether the letterboxing margin is less than the border radius, // and if so flatten the borders. let borderRadius = parseInt( win .getComputedStyle(browserContainer) .getPropertyValue("--letterboxing-border-radius") // and if so do not show an outline. const gapVertical = parentHeight - lastRoundedSize.height; const gapHorizontal = parentWidth - lastRoundedSize.width; browserParent.classList.toggle( "letterboxing-show-outline", gapVertical >= this._letterboxingBorderRadius || gapHorizontal >= this._letterboxingBorderRadius ); // When the Letterboxing area is top-aligned, only show the sidebar corner // if there is enough horizontal space. // The factor of 4 is from the horizontal centre-alignment and wanting // enough space for twice the corner radius. browserParent.classList.toggle( "letterboxing-show-sidebar-corner", gapHorizontal >= 4 * this._letterboxingBorderRadius ); if ( borderRadius && parentWidth - lastRoundedSize.width < borderRadius && parentHeight - lastRoundedSize.height < borderRadius ) { borderRadius = 0; } else { borderRadius = ""; } styleChanges.queueIfNeeded(browserParent, { "--letterboxing-decorator-visibility": borderRadius === 0 ? "hidden" : "", "--letterboxing-border-radius": borderRadius, }); } // If the size of the content is already quantized, we do nothing. Loading Loading @@ -604,12 +638,31 @@ class _RFPHelper { _resetContentSize(aBrowser) { aBrowser.parentElement.classList.add("exclude-letterboxing"); aBrowser.parentElement.classList.remove( "letterboxing-show-outline", "letterboxing-show-sidebar-corner" ); } _updateSizeForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; tabBrowser.tabpanels?.classList.add("letterboxing"); tabBrowser.tabbox.classList.add("letterboxing"); tabBrowser.tabbox.classList.toggle( "letterboxing-vcenter", Services.prefs.getBoolPref(kPrefLetterboxingVcenter, false) ); if (this._letterboxingBorderRadius === undefined && tabBrowser.tabbox) { // Cache the value since it is not expected to change in a session for any // window. this._letterboxingBorderRadius = Math.ceil( parseFloat( aWindow .getComputedStyle(tabBrowser.tabbox) .getPropertyValue("--letterboxing-border-radius") ) ); } for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; Loading @@ -618,7 +671,7 @@ class _RFPHelper { // We need to add this class late because otherwise new windows get // maximized. aWindow.setTimeout(() => { tabBrowser.tabpanels?.classList.add("letterboxing-ready"); tabBrowser.tabbox.classList.add("letterboxing-ready"); }); } Loading @@ -638,6 +691,295 @@ class _RFPHelper { this._resizeObservers.set(aWindow, resizeObserver); // Rounding the content viewport. this._updateSizeForTabsInWindow(aWindow); this._updateLetterboxingColors(aWindow, true); aWindow.addEventListener("nativethemechange", this); } /** * Convert a CSS property to its RGBA value. * * @param {Window} win - The window for the element. * @param {CSSStyleDeclaration} style - The computed style for the element we * want to grab the color from. * @param {string} property - The name of the property we want. * @param {object} [options] - Optional details. * @param {string} [options.fallbackProperty] - A fallback to use instead if * the property doesn't have a computed value. * @param {string} [options.currentColorProperty] - The name of a property to * use as the currentColor. * * @returns {InspectorRGBATuple} - The RGBA color. The "r", "g", "b" fields * are relative to the 0-255 color range. The "a" field is in the 0-1 range. */ _convertToRGBA(win, style, property, options) { let cssColor = style.getPropertyValue(property); if (!cssColor) { if (options?.fallbackProperty) { lazy.logConsole.debug( "Using fallback property for RGBA.", property, options.fallbackProperty ); return this._convertToRGBA(win, style, options.fallbackProperty); } lazy.logConsole.error(`Missing color "${property}"`); return { r: 0, g: 0, b: 0, a: 0 }; } const currentColorRegex = /(^|[^a-zA-Z0-9_-])currentColor($|[^a-zA-Z0-9_-])/g; if (currentColorRegex.test(cssColor)) { let currentColor; if (options?.currentColorProperty) { const currRGBA = this._convertToRGBA( win, style, options.currentColorProperty ); currentColor = `rgba(${currRGBA.r}, ${currRGBA.g}, ${currRGBA.b}, ${currRGBA.a})`; } else { lazy.logConsole.warning( "Missing a specification for the currentColor, using computed color." ); // Use the current "color" value. NOTE: this may not be exactly what we // want since it's current value may be effected by :hover, :active, // :focus, etc. But we want this to be a stable colour for the theme. currentColor = style.color; } cssColor = cssColor.replace(currentColorRegex, (_, pre, post) => { return pre + currentColor + post; }); lazy.logConsole.debug( "Replaced currentColor.", property, currentColor, cssColor ); } // Can drop the document argument after bugzilla bug 1973684 (142). const colorRGBA = win.InspectorUtils.colorToRGBA(cssColor, win.document); if (!colorRGBA) { lazy.logConsole.error( `Failed to convert "${property}" color (${cssColor}) to RGBA` ); return { r: 0, g: 0, b: 0, a: 0 }; } return colorRGBA; } /** * Compose two colors with alpha values on top of each other. * * @param {InspectorRGBATuple} topRGBA - The color to place on the top. * @param {InspectorRGBATuple} bottomRGBA - The color to place on the bottom. * * @returns {InspectorRGBATuple} - The composed color. */ _composeRGBA(topRGBA, bottomRGBA) { const topA = Math.max(0, Math.min(1, topRGBA.a)); const bottomA = Math.max(0, Math.min(1, bottomRGBA.a)); const a = topA + bottomA - topA * bottomA; // Should be 1 if either is 1. if (a === 0) { return { r: 0, g: 0, b: 0, a }; } const ret = { a }; for (const field of ["r", "g", "b"]) { ret[field] = (topRGBA[field] * topA + bottomRGBA[field] * bottomA * (1 - topA)) / a; } return ret; } /** * Calculate the urlbar's container opaque background color, removing any * transparency. * * @param {Window} win - The window to calculate the color for. * @param {CSSStyleDeclaration} style - The computed style for the #nav-bar * element. * @param {boolean} verticalTabs - Whether vertical tabs are enabled. * * @returns {InspectorRGBATuple} - The calculated color, which will be opaque. */ _calculateUrlbarContainerColor(win, style, verticalTabs) { let colorRGBA; if (!verticalTabs) { lazy.logConsole.debug("Toolbar background used."); colorRGBA = this._convertToRGBA(win, style, "--toolbar-bgcolor"); if (colorRGBA.a === 1) { return colorRGBA; } } else { // The urlbar only has the toolbox colour. colorRGBA = { r: 0, g: 0, b: 0, a: 0 }; } let toolboxHasBackgroundImage = false; const isLwTheme = win.document.documentElement.hasAttribute("lwtheme"); if (isLwTheme) { for (const prop of ["--lwt-header-image", "--lwt-additional-images"]) { const headerImage = style.getPropertyValue(prop); if (headerImage && headerImage !== "none") { // The theme sets a background image behind the urlbar. No easy way to // derive a single colour from this. toolboxHasBackgroundImage = true; lazy.logConsole.debug( "Toolbox has background image.", prop, headerImage ); break; } } } if (!toolboxHasBackgroundImage) { lazy.logConsole.debug("Toolbox background used."); colorRGBA = this._composeRGBA( colorRGBA, this._convertToRGBA(win, style, "--toolbox-bgcolor") ); if (colorRGBA.a === 1) { return colorRGBA; } } // Determine whether the urlbar is dark. // At this point, the urlbar background has some transparency, likely on top // of an image. // We use the theme's text colour to figure out whether the urlbar // background is overall meant to be light or dark. Unlike the urlbar, we // expect this colour to be (almost) opaque. const textRGBA = this._convertToRGBA(win, style, "--toolbar-field-color"); const textColor = new lazy.Color(textRGBA.r, textRGBA.g, textRGBA.b); if (textColor.relativeLuminance >= 0.5) { // Light text, so assume it has a dark background. // Combine with a generic opaque dark colour. Copied from "frame" for the // built-in dark theme. lazy.logConsole.debug("Generic dark background used."); const darkFrameRGBA = { r: 28, g: 27, b: 34, a: 1 }; return this._composeRGBA(colorRGBA, darkFrameRGBA); } // Combine with an opaque light colour. Copied from "frame" for the built-in // light theme. lazy.logConsole.debug("Generic light background used."); const lightFrameRGBA = { r: 234, g: 234, b: 237, a: 1 }; return this._composeRGBA(colorRGBA, lightFrameRGBA); } /** * Update the Letterboxing colors and related classes, or clear them if * Letterboxing is not enabled. * * @param {Window} win - The window to update the colors for. * @param {boolean} letterboxingEnabled - Whether Letterboxing is enabled. */ _updateLetterboxingColors(win, letterboxingEnabled) { let urlbarBackgroundRGBA; let urlbarTextRGBA; let contentSeparatorRGBA; let urlbarBackgroundDark = false; let lowBackgroundOutlineContrast = false; if (letterboxingEnabled) { // Want the effective colour of various elements without any alpha values // so they can be used consistently. const verticalTabs = Services.prefs.getBoolPref(kPrefVerticalTabs); const chromeTextColorProperty = verticalTabs ? "--toolbox-textcolor" : "--toolbar-color"; const navbarStyle = win.getComputedStyle( win.document.getElementById("nav-bar") ); const containerRGBA = this._calculateUrlbarContainerColor( win, navbarStyle, verticalTabs ); urlbarBackgroundRGBA = this._composeRGBA( this._convertToRGBA( win, navbarStyle, "--toolbar-field-background-color" ), containerRGBA ); // NOTE: In the default theme (no "lwtheme" attribute) with // browser.theme.native-theme set to false, --toolbar-field-color can be // set to "inherit", which means it will have a blank computed value. We // fallback to --toolbar-color or --toolbox-textcolor in this case. // Similarly, for windows OS, it can be set to "currentColor". urlbarTextRGBA = this._composeRGBA( this._convertToRGBA(win, navbarStyle, "--toolbar-field-color", { fallbackProperty: chromeTextColorProperty, currentColorProperty: chromeTextColorProperty, }), urlbarBackgroundRGBA ); // Separator between the urlbar container #nav-bar and the tabbox. // For the default theme, this can be set to --border-color-card, which // can use "currentColor". const tabboxStyle = win.getComputedStyle(win.gBrowser.tabbox); contentSeparatorRGBA = this._composeRGBA( this._convertToRGBA( win, tabboxStyle, "--chrome-content-separator-color", { currentColorProperty: chromeTextColorProperty } ), containerRGBA ); const bgColor = new lazy.Color( urlbarBackgroundRGBA.r, urlbarBackgroundRGBA.g, urlbarBackgroundRGBA.b ); const outlineColor = new lazy.Color( contentSeparatorRGBA.r, contentSeparatorRGBA.g, contentSeparatorRGBA.b ); const contrastRatio = bgColor.contrastRatio(outlineColor); lazy.logConsole.debug( "Outline-background contrast ratio.", contrastRatio ); urlbarBackgroundDark = bgColor.relativeLuminance < 0.5; // Very low contrast ratio. For reference the default light theme has // a contrast ratio of ~1.1. lowBackgroundOutlineContrast = contrastRatio < 1.05; } for (const { name, colorRGBA } of [ { name: "--letterboxing-urlbar-text-color", colorRGBA: urlbarTextRGBA, }, { name: "--letterboxing-urlbar-background-color", colorRGBA: urlbarBackgroundRGBA, }, { name: "--letterboxing-content-separator-color", colorRGBA: contentSeparatorRGBA, }, ]) { if (letterboxingEnabled) { win.gBrowser.tabbox.style.setProperty( name, `rgb(${colorRGBA.r}, ${colorRGBA.g}, ${colorRGBA.b})` ); } else { win.gBrowser.tabbox.style.removeProperty(name); } } win.gBrowser.tabbox.classList.toggle( "letterboxing-urlbar-background-dark", urlbarBackgroundDark ); win.gBrowser.tabbox.classList.toggle( "letterboxing-low-background-outline-contrast", lowBackgroundOutlineContrast ); } _attachAllWindows() { Loading Loading @@ -669,13 +1011,16 @@ class _RFPHelper { aWindow.removeEventListener("TabOpen", this); // revert tabpanel's style to default tabBrowser.tabpanels?.classList.remove("letterboxing"); tabBrowser.tabbox.classList.remove("letterboxing"); // and restore default size on each browser element for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._resetContentSize(browser); } aWindow.removeEventListener("nativethemechange", this); this._updateLetterboxingColors(aWindow, false); } _detachAllWindows() { Loading toolkit/components/resistfingerprinting/content/letterboxing.css +160 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
browser/app/profile/001-base-profile.js +4 −0 Original line number Diff line number Diff line Loading @@ -538,6 +538,10 @@ pref("security.remote_settings.intermediates.enabled", false); pref("dom.use_components_shim", false); // Enable letterboxing pref("privacy.resistFingerprinting.letterboxing", true); // tor-browser#41917: Center letterboxed area vertically pref("privacy.resistFingerprinting.letterboxing.vcenter", true); // tor-browser#41917: Letterboxing gradient background pref("privacy.resistFingerprinting.letterboxing.gradient", true); // tor-browser#43402: Avoid a resize from the skeleton to the newwin size. // Should be fixed in ESR-140 with Bug 1448423. pref("browser.startup.blankWindow", false); Loading
browser/components/tabbrowser/content/tabbrowser.js +4 −0 Original line number Diff line number Diff line Loading @@ -2824,6 +2824,10 @@ stack.className = "browserStack"; stack.appendChild(b); let decorator = document.createXULElement("hbox"); decorator.className = "browserDecorator"; stack.appendChild(decorator); let browserContainer = document.createXULElement("vbox"); browserContainer.className = "browserContainer"; browserContainer.appendChild(stack); Loading
browser/themes/shared/tabbrowser/content-area.css +5 −0 Original line number Diff line number Diff line Loading @@ -548,6 +548,11 @@ split-view-footer { width: 100%; height: 100%; /* Override the <stack> `grid-area: 1 / 1` rule with an `auto` placement. * Otherwise the .dialogStack start edges are placed relative to the * center-aligned grid items, rather than the grid's padding area. */ grid-area: auto; /* Hide tab-modal dialogs when a window-modal one is up. */ :root[window-modal-open] .browserStack > &, /* For some printing use cases we need to visually hide the dialog before Loading
toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +368 −23 Original line number Diff line number Diff line Loading @@ -14,12 +14,21 @@ const kPrefLetterboxingDimensions = "privacy.resistFingerprinting.letterboxing.dimensions"; const kPrefLetterboxingTesting = "privacy.resistFingerprinting.letterboxing.testing"; const kPrefLetterboxingVcenter = "privacy.resistFingerprinting.letterboxing.vcenter"; const kTopicDOMWindowOpened = "domwindowopened"; const kTopicDOMWindowClosed = "domwindowclosed"; const kTopicFullscreenNavToolbox = "fullscreen-nav-toolbox"; const kPrefVerticalTabs = "sidebar.verticalTabs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { Color: "resource://gre/modules/Color.sys.mjs", }); ChromeUtils.defineLazyGetter(lazy, "logConsole", () => console.createInstance({ prefix: "RFPHelper", Loading Loading @@ -55,6 +64,10 @@ class _RFPHelper { // Add unconditional observers Services.prefs.addObserver(kPrefResistFingerprinting, this); Services.prefs.addObserver(kPrefLetterboxing, this); Services.prefs.addObserver(kPrefLetterboxingVcenter, this); Services.prefs.addObserver(kPrefVerticalTabs, this); Services.obs.addObserver(this, kTopicFullscreenNavToolbox); XPCOMUtils.defineLazyPreferenceGetter( this, "_letterboxingDimensions", Loading Loading @@ -86,7 +99,10 @@ class _RFPHelper { // Remove unconditional observers Services.prefs.removeObserver(kPrefResistFingerprinting, this); Services.prefs.removeObserver(kPrefLetterboxingVcenter, this); Services.prefs.removeObserver(kPrefLetterboxing, this); Services.prefs.removeObserver(kPrefVerticalTabs, this); Services.obs.removeObserver(this, kTopicFullscreenNavToolbox); // Remove the RFP observers, swallowing exceptions if they weren't present this._removeLanguagePrefObservers(); } Loading @@ -108,6 +124,15 @@ class _RFPHelper { case kTopicDOMWindowClosed: this._handleDOMWindowClosed(subject); break; case kTopicFullscreenNavToolbox: // The `subject` is the gNavToolbox. // Record whether the toobox has been hidden when the browser (not // content) is in fullscreen. subject.ownerGlobal.gBrowser.tabbox.classList.toggle( "letterboxing-nav-toolbox-hidden", data === "hidden" ); break; default: break; } Loading @@ -122,6 +147,13 @@ class _RFPHelper { resizeObserver.observe(browser.parentElement); break; } case "nativethemechange": // NOTE: "nativethemechange" seems to always be sent after // "windowlwthemeupdate". So all the lwtheme CSS properties should be // set to the new theme's values already, so we don't need to wait for // windowlwthemeupdate. this._updateLetterboxingColors(aMessage.currentTarget, true); break; default: break; } Loading @@ -137,8 +169,14 @@ class _RFPHelper { this._handleSpoofEnglishChanged(); break; case kPrefLetterboxing: case kPrefLetterboxingVcenter: this._handleLetterboxingPrefChanged(); break; case kPrefVerticalTabs: if (this.letterboxingEnabled) { forEachWindow(win => this._updateLetterboxingColors(win)); } break; default: break; } Loading Loading @@ -345,7 +383,7 @@ class _RFPHelper { // If not already cached on the document object, traverse the CSSOM and // find the rule applying the default letterboxing styles to browsers // preemptively in order to beat race conditions on tab/window creation return (document._letterboxingMarginsRule ||= (() => { return (document._letterboxingDefaultRule ||= (() => { const LETTERBOX_CSS_SELECTOR = ".letterboxing"; const LETTERBOX_CSS_URL = "chrome://global/content/resistfingerprinting/letterboxing.css"; Loading Loading @@ -552,26 +590,22 @@ class _RFPHelper { if (lastRoundedSize) { // Check whether the letterboxing margin is less than the border radius, // and if so flatten the borders. let borderRadius = parseInt( win .getComputedStyle(browserContainer) .getPropertyValue("--letterboxing-border-radius") // and if so do not show an outline. const gapVertical = parentHeight - lastRoundedSize.height; const gapHorizontal = parentWidth - lastRoundedSize.width; browserParent.classList.toggle( "letterboxing-show-outline", gapVertical >= this._letterboxingBorderRadius || gapHorizontal >= this._letterboxingBorderRadius ); // When the Letterboxing area is top-aligned, only show the sidebar corner // if there is enough horizontal space. // The factor of 4 is from the horizontal centre-alignment and wanting // enough space for twice the corner radius. browserParent.classList.toggle( "letterboxing-show-sidebar-corner", gapHorizontal >= 4 * this._letterboxingBorderRadius ); if ( borderRadius && parentWidth - lastRoundedSize.width < borderRadius && parentHeight - lastRoundedSize.height < borderRadius ) { borderRadius = 0; } else { borderRadius = ""; } styleChanges.queueIfNeeded(browserParent, { "--letterboxing-decorator-visibility": borderRadius === 0 ? "hidden" : "", "--letterboxing-border-radius": borderRadius, }); } // If the size of the content is already quantized, we do nothing. Loading Loading @@ -604,12 +638,31 @@ class _RFPHelper { _resetContentSize(aBrowser) { aBrowser.parentElement.classList.add("exclude-letterboxing"); aBrowser.parentElement.classList.remove( "letterboxing-show-outline", "letterboxing-show-sidebar-corner" ); } _updateSizeForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; tabBrowser.tabpanels?.classList.add("letterboxing"); tabBrowser.tabbox.classList.add("letterboxing"); tabBrowser.tabbox.classList.toggle( "letterboxing-vcenter", Services.prefs.getBoolPref(kPrefLetterboxingVcenter, false) ); if (this._letterboxingBorderRadius === undefined && tabBrowser.tabbox) { // Cache the value since it is not expected to change in a session for any // window. this._letterboxingBorderRadius = Math.ceil( parseFloat( aWindow .getComputedStyle(tabBrowser.tabbox) .getPropertyValue("--letterboxing-border-radius") ) ); } for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; Loading @@ -618,7 +671,7 @@ class _RFPHelper { // We need to add this class late because otherwise new windows get // maximized. aWindow.setTimeout(() => { tabBrowser.tabpanels?.classList.add("letterboxing-ready"); tabBrowser.tabbox.classList.add("letterboxing-ready"); }); } Loading @@ -638,6 +691,295 @@ class _RFPHelper { this._resizeObservers.set(aWindow, resizeObserver); // Rounding the content viewport. this._updateSizeForTabsInWindow(aWindow); this._updateLetterboxingColors(aWindow, true); aWindow.addEventListener("nativethemechange", this); } /** * Convert a CSS property to its RGBA value. * * @param {Window} win - The window for the element. * @param {CSSStyleDeclaration} style - The computed style for the element we * want to grab the color from. * @param {string} property - The name of the property we want. * @param {object} [options] - Optional details. * @param {string} [options.fallbackProperty] - A fallback to use instead if * the property doesn't have a computed value. * @param {string} [options.currentColorProperty] - The name of a property to * use as the currentColor. * * @returns {InspectorRGBATuple} - The RGBA color. The "r", "g", "b" fields * are relative to the 0-255 color range. The "a" field is in the 0-1 range. */ _convertToRGBA(win, style, property, options) { let cssColor = style.getPropertyValue(property); if (!cssColor) { if (options?.fallbackProperty) { lazy.logConsole.debug( "Using fallback property for RGBA.", property, options.fallbackProperty ); return this._convertToRGBA(win, style, options.fallbackProperty); } lazy.logConsole.error(`Missing color "${property}"`); return { r: 0, g: 0, b: 0, a: 0 }; } const currentColorRegex = /(^|[^a-zA-Z0-9_-])currentColor($|[^a-zA-Z0-9_-])/g; if (currentColorRegex.test(cssColor)) { let currentColor; if (options?.currentColorProperty) { const currRGBA = this._convertToRGBA( win, style, options.currentColorProperty ); currentColor = `rgba(${currRGBA.r}, ${currRGBA.g}, ${currRGBA.b}, ${currRGBA.a})`; } else { lazy.logConsole.warning( "Missing a specification for the currentColor, using computed color." ); // Use the current "color" value. NOTE: this may not be exactly what we // want since it's current value may be effected by :hover, :active, // :focus, etc. But we want this to be a stable colour for the theme. currentColor = style.color; } cssColor = cssColor.replace(currentColorRegex, (_, pre, post) => { return pre + currentColor + post; }); lazy.logConsole.debug( "Replaced currentColor.", property, currentColor, cssColor ); } // Can drop the document argument after bugzilla bug 1973684 (142). const colorRGBA = win.InspectorUtils.colorToRGBA(cssColor, win.document); if (!colorRGBA) { lazy.logConsole.error( `Failed to convert "${property}" color (${cssColor}) to RGBA` ); return { r: 0, g: 0, b: 0, a: 0 }; } return colorRGBA; } /** * Compose two colors with alpha values on top of each other. * * @param {InspectorRGBATuple} topRGBA - The color to place on the top. * @param {InspectorRGBATuple} bottomRGBA - The color to place on the bottom. * * @returns {InspectorRGBATuple} - The composed color. */ _composeRGBA(topRGBA, bottomRGBA) { const topA = Math.max(0, Math.min(1, topRGBA.a)); const bottomA = Math.max(0, Math.min(1, bottomRGBA.a)); const a = topA + bottomA - topA * bottomA; // Should be 1 if either is 1. if (a === 0) { return { r: 0, g: 0, b: 0, a }; } const ret = { a }; for (const field of ["r", "g", "b"]) { ret[field] = (topRGBA[field] * topA + bottomRGBA[field] * bottomA * (1 - topA)) / a; } return ret; } /** * Calculate the urlbar's container opaque background color, removing any * transparency. * * @param {Window} win - The window to calculate the color for. * @param {CSSStyleDeclaration} style - The computed style for the #nav-bar * element. * @param {boolean} verticalTabs - Whether vertical tabs are enabled. * * @returns {InspectorRGBATuple} - The calculated color, which will be opaque. */ _calculateUrlbarContainerColor(win, style, verticalTabs) { let colorRGBA; if (!verticalTabs) { lazy.logConsole.debug("Toolbar background used."); colorRGBA = this._convertToRGBA(win, style, "--toolbar-bgcolor"); if (colorRGBA.a === 1) { return colorRGBA; } } else { // The urlbar only has the toolbox colour. colorRGBA = { r: 0, g: 0, b: 0, a: 0 }; } let toolboxHasBackgroundImage = false; const isLwTheme = win.document.documentElement.hasAttribute("lwtheme"); if (isLwTheme) { for (const prop of ["--lwt-header-image", "--lwt-additional-images"]) { const headerImage = style.getPropertyValue(prop); if (headerImage && headerImage !== "none") { // The theme sets a background image behind the urlbar. No easy way to // derive a single colour from this. toolboxHasBackgroundImage = true; lazy.logConsole.debug( "Toolbox has background image.", prop, headerImage ); break; } } } if (!toolboxHasBackgroundImage) { lazy.logConsole.debug("Toolbox background used."); colorRGBA = this._composeRGBA( colorRGBA, this._convertToRGBA(win, style, "--toolbox-bgcolor") ); if (colorRGBA.a === 1) { return colorRGBA; } } // Determine whether the urlbar is dark. // At this point, the urlbar background has some transparency, likely on top // of an image. // We use the theme's text colour to figure out whether the urlbar // background is overall meant to be light or dark. Unlike the urlbar, we // expect this colour to be (almost) opaque. const textRGBA = this._convertToRGBA(win, style, "--toolbar-field-color"); const textColor = new lazy.Color(textRGBA.r, textRGBA.g, textRGBA.b); if (textColor.relativeLuminance >= 0.5) { // Light text, so assume it has a dark background. // Combine with a generic opaque dark colour. Copied from "frame" for the // built-in dark theme. lazy.logConsole.debug("Generic dark background used."); const darkFrameRGBA = { r: 28, g: 27, b: 34, a: 1 }; return this._composeRGBA(colorRGBA, darkFrameRGBA); } // Combine with an opaque light colour. Copied from "frame" for the built-in // light theme. lazy.logConsole.debug("Generic light background used."); const lightFrameRGBA = { r: 234, g: 234, b: 237, a: 1 }; return this._composeRGBA(colorRGBA, lightFrameRGBA); } /** * Update the Letterboxing colors and related classes, or clear them if * Letterboxing is not enabled. * * @param {Window} win - The window to update the colors for. * @param {boolean} letterboxingEnabled - Whether Letterboxing is enabled. */ _updateLetterboxingColors(win, letterboxingEnabled) { let urlbarBackgroundRGBA; let urlbarTextRGBA; let contentSeparatorRGBA; let urlbarBackgroundDark = false; let lowBackgroundOutlineContrast = false; if (letterboxingEnabled) { // Want the effective colour of various elements without any alpha values // so they can be used consistently. const verticalTabs = Services.prefs.getBoolPref(kPrefVerticalTabs); const chromeTextColorProperty = verticalTabs ? "--toolbox-textcolor" : "--toolbar-color"; const navbarStyle = win.getComputedStyle( win.document.getElementById("nav-bar") ); const containerRGBA = this._calculateUrlbarContainerColor( win, navbarStyle, verticalTabs ); urlbarBackgroundRGBA = this._composeRGBA( this._convertToRGBA( win, navbarStyle, "--toolbar-field-background-color" ), containerRGBA ); // NOTE: In the default theme (no "lwtheme" attribute) with // browser.theme.native-theme set to false, --toolbar-field-color can be // set to "inherit", which means it will have a blank computed value. We // fallback to --toolbar-color or --toolbox-textcolor in this case. // Similarly, for windows OS, it can be set to "currentColor". urlbarTextRGBA = this._composeRGBA( this._convertToRGBA(win, navbarStyle, "--toolbar-field-color", { fallbackProperty: chromeTextColorProperty, currentColorProperty: chromeTextColorProperty, }), urlbarBackgroundRGBA ); // Separator between the urlbar container #nav-bar and the tabbox. // For the default theme, this can be set to --border-color-card, which // can use "currentColor". const tabboxStyle = win.getComputedStyle(win.gBrowser.tabbox); contentSeparatorRGBA = this._composeRGBA( this._convertToRGBA( win, tabboxStyle, "--chrome-content-separator-color", { currentColorProperty: chromeTextColorProperty } ), containerRGBA ); const bgColor = new lazy.Color( urlbarBackgroundRGBA.r, urlbarBackgroundRGBA.g, urlbarBackgroundRGBA.b ); const outlineColor = new lazy.Color( contentSeparatorRGBA.r, contentSeparatorRGBA.g, contentSeparatorRGBA.b ); const contrastRatio = bgColor.contrastRatio(outlineColor); lazy.logConsole.debug( "Outline-background contrast ratio.", contrastRatio ); urlbarBackgroundDark = bgColor.relativeLuminance < 0.5; // Very low contrast ratio. For reference the default light theme has // a contrast ratio of ~1.1. lowBackgroundOutlineContrast = contrastRatio < 1.05; } for (const { name, colorRGBA } of [ { name: "--letterboxing-urlbar-text-color", colorRGBA: urlbarTextRGBA, }, { name: "--letterboxing-urlbar-background-color", colorRGBA: urlbarBackgroundRGBA, }, { name: "--letterboxing-content-separator-color", colorRGBA: contentSeparatorRGBA, }, ]) { if (letterboxingEnabled) { win.gBrowser.tabbox.style.setProperty( name, `rgb(${colorRGBA.r}, ${colorRGBA.g}, ${colorRGBA.b})` ); } else { win.gBrowser.tabbox.style.removeProperty(name); } } win.gBrowser.tabbox.classList.toggle( "letterboxing-urlbar-background-dark", urlbarBackgroundDark ); win.gBrowser.tabbox.classList.toggle( "letterboxing-low-background-outline-contrast", lowBackgroundOutlineContrast ); } _attachAllWindows() { Loading Loading @@ -669,13 +1011,16 @@ class _RFPHelper { aWindow.removeEventListener("TabOpen", this); // revert tabpanel's style to default tabBrowser.tabpanels?.classList.remove("letterboxing"); tabBrowser.tabbox.classList.remove("letterboxing"); // and restore default size on each browser element for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._resetContentSize(browser); } aWindow.removeEventListener("nativethemechange", this); this._updateLetterboxingColors(aWindow, false); } _detachAllWindows() { Loading
toolkit/components/resistfingerprinting/content/letterboxing.css +160 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes