Loading browser/app/profile/001-base-profile.js +4 −0 Original line number Diff line number Diff line Loading @@ -508,6 +508,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/base/content/browser-fullScreenAndPointerLock.js +14 −2 Original line number Diff line number Diff line Loading @@ -878,7 +878,13 @@ var FullScreen = { } this._isChromeCollapsed = false; Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "shown"); // Need a subject to know which window this applies to. // Base browser patch can be dropped after bugzilla bug 1992036. Services.obs.notifyObservers( gNavToolbox, "fullscreen-nav-toolbox", "shown" ); }, hideNavToolbox(aAnimate = false) { Loading Loading @@ -942,7 +948,13 @@ var FullScreen = { gNavToolbox.style.marginTop = -gNavToolbox.getBoundingClientRect().height + "px"; this._isChromeCollapsed = true; Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "hidden"); // Need a subject to know which window this applies to. // Base browser patch can be dropped after bugzilla bug 1880918. Services.obs.notifyObservers( gNavToolbox, "fullscreen-nav-toolbox", "hidden" ); MousePosTracker.removeListener(this); }, Loading browser/components/tabbrowser/content/tabbrowser.js +4 −0 Original line number Diff line number Diff line Loading @@ -2240,6 +2240,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 @@ -277,6 +277,11 @@ position: absolute; inset: 0; /* 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 +320 −23 Original line number Diff line number Diff line Loading @@ -15,12 +15,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 @@ -51,6 +60,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 @@ -82,7 +95,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 @@ -104,6 +120,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 @@ -118,6 +143,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 @@ -133,8 +165,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 @@ -335,7 +373,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 @@ -542,26 +580,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 @@ -594,12 +628,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 @@ -608,7 +661,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 @@ -628,6 +681,247 @@ 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. * * @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) { let cssColor = style.getPropertyValue(property); if (!cssColor) { 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)) { const 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. * * @returns {InspectorRGBATuple} - The calculated color, which will be opaque. */ _calculateUrlbarContainerColor(win, style) { let colorRGBA; if (!Services.prefs.getBoolPref(kPrefVerticalTabs)) { 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 navbarStyle = win.getComputedStyle( win.document.getElementById("nav-bar") ); const containerRGBA = this._calculateUrlbarContainerColor( win, navbarStyle ); urlbarBackgroundRGBA = this._composeRGBA( this._convertToRGBA( win, navbarStyle, "--toolbar-field-background-color" ), containerRGBA ); urlbarTextRGBA = this._composeRGBA( this._convertToRGBA(win, navbarStyle, "--toolbar-field-color"), urlbarBackgroundRGBA ); /* Separator between the urlbar container #nav-bar and the tabbox. */ const tabboxStyle = win.getComputedStyle(win.gBrowser.tabbox); contentSeparatorRGBA = this._composeRGBA( this._convertToRGBA( win, tabboxStyle, "--chrome-content-separator-color" ), 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 @@ -659,13 +953,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 Loading
browser/app/profile/001-base-profile.js +4 −0 Original line number Diff line number Diff line Loading @@ -508,6 +508,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/base/content/browser-fullScreenAndPointerLock.js +14 −2 Original line number Diff line number Diff line Loading @@ -878,7 +878,13 @@ var FullScreen = { } this._isChromeCollapsed = false; Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "shown"); // Need a subject to know which window this applies to. // Base browser patch can be dropped after bugzilla bug 1992036. Services.obs.notifyObservers( gNavToolbox, "fullscreen-nav-toolbox", "shown" ); }, hideNavToolbox(aAnimate = false) { Loading Loading @@ -942,7 +948,13 @@ var FullScreen = { gNavToolbox.style.marginTop = -gNavToolbox.getBoundingClientRect().height + "px"; this._isChromeCollapsed = true; Services.obs.notifyObservers(null, "fullscreen-nav-toolbox", "hidden"); // Need a subject to know which window this applies to. // Base browser patch can be dropped after bugzilla bug 1880918. Services.obs.notifyObservers( gNavToolbox, "fullscreen-nav-toolbox", "hidden" ); MousePosTracker.removeListener(this); }, Loading
browser/components/tabbrowser/content/tabbrowser.js +4 −0 Original line number Diff line number Diff line Loading @@ -2240,6 +2240,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 @@ -277,6 +277,11 @@ position: absolute; inset: 0; /* 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 +320 −23 Original line number Diff line number Diff line Loading @@ -15,12 +15,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 @@ -51,6 +60,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 @@ -82,7 +95,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 @@ -104,6 +120,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 @@ -118,6 +143,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 @@ -133,8 +165,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 @@ -335,7 +373,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 @@ -542,26 +580,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 @@ -594,12 +628,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 @@ -608,7 +661,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 @@ -628,6 +681,247 @@ 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. * * @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) { let cssColor = style.getPropertyValue(property); if (!cssColor) { 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)) { const 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. * * @returns {InspectorRGBATuple} - The calculated color, which will be opaque. */ _calculateUrlbarContainerColor(win, style) { let colorRGBA; if (!Services.prefs.getBoolPref(kPrefVerticalTabs)) { 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 navbarStyle = win.getComputedStyle( win.document.getElementById("nav-bar") ); const containerRGBA = this._calculateUrlbarContainerColor( win, navbarStyle ); urlbarBackgroundRGBA = this._composeRGBA( this._convertToRGBA( win, navbarStyle, "--toolbar-field-background-color" ), containerRGBA ); urlbarTextRGBA = this._composeRGBA( this._convertToRGBA(win, navbarStyle, "--toolbar-field-color"), urlbarBackgroundRGBA ); /* Separator between the urlbar container #nav-bar and the tabbox. */ const tabboxStyle = win.getComputedStyle(win.gBrowser.tabbox); contentSeparatorRGBA = this._composeRGBA( this._convertToRGBA( win, tabboxStyle, "--chrome-content-separator-color" ), 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 @@ -659,13 +953,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