Loading toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +138 −50 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import * as RFPTargetConstants from "resource://gre/modules/RFPTargetConstants.sys.mjs"; const kPrefResistFingerprinting = "privacy.resistFingerprinting"; Loading Loading @@ -41,6 +42,20 @@ function log(...args) { lazy.logConsole.log(...args); } function forEachWindow(callback) { const windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { const win = windowList.getNext(); if (win.gBrowser && !win.closed) { try { callback(win); } catch (e) { lazy.logConsole.error(e); } } } } class _RFPHelper { _resizeObservers = new WeakMap(); Loading Loading @@ -208,9 +223,13 @@ class _RFPHelper { _handleResistFingerprintingChanged() { this.rfpEnabled = Services.prefs.getBoolPref(kPrefResistFingerprinting); if (ChromeUtils.shouldResistFingerprinting("JSLocalePrompt", null)) { if (this.rfpEnabled) { this._addLanguagePrefObservers(); Services.ww.registerNotification(this); forEachWindow(win => this._attachWindow(win)); } else { forEachWindow(win => this._detachWindow(win)); Services.ww.unregisterNotification(this); this._removeLanguagePrefObservers(); } } Loading Loading @@ -342,18 +361,12 @@ class _RFPHelper { } _handleLetterboxingPrefChanged() { if (Services.prefs.getBoolPref(kPrefLetterboxing, false)) { if (!this._letterboxingPrefObserversAdded) { Services.ww.registerNotification(this); this._letterboxingPrefObserversAdded = true; } this._attachAllWindows(); } else { this._detachAllWindows(); if (this._letterboxingPrefObserversAdded) { Services.ww.unregisterNotification(this); this._letterboxingPrefObserversAdded = false; } this.letterboxingEnabled = Services.prefs.getBoolPref( kPrefLetterboxing, false ); if (this.rfpEnabled) { forEachWindow(win => this._updateSizeForTabsInWindow(win)); } } Loading Loading @@ -435,22 +448,23 @@ class _RFPHelper { } } stepping(aDimension, aIsWidth) { if (aDimension <= 500) { return 50; } else if (aDimension <= 1600) { return aIsWidth ? 200 : 100; } return 200; } /** * Given a width or height, rounds it with the proper stepping. */ steppedSize(aDimension, aIsWidth = false) { let stepping; if (aDimension <= 50) { return aDimension; } else if (aDimension <= 500) { stepping = 50; } else if (aDimension <= 1600) { stepping = aIsWidth ? 200 : 100; } else { stepping = 200; } return aDimension - (aDimension % stepping); return aDimension - (aDimension % this.stepping(aDimension, aIsWidth)); } /** Loading @@ -477,6 +491,17 @@ class _RFPHelper { ]) ); const isInitialSize = win._rfpOriginalSize && win.outerWidth === win._rfpOriginalSize.width && win.outerHeight === win._rfpOriginalSize.height; // We may need to shrink this window to rounded size if the browser container // area is taller than the original, meaning extra chrome (like the optional // "Only Show on New Tab" bookmarks toobar) was present and now gone. const needToShrink = isInitialSize && containerHeight > win._rfpOriginalSize.containerHeight; log( `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} parentWidth=${parentWidth} parentHeight=${parentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}${ isNewTab ? " (new tab)." : "." Loading Loading @@ -504,6 +529,11 @@ class _RFPHelper { log(`${logPrefix} roundDimensions(${aWidth}, ${aHeight})`); if (!this.letterboxingEnabled) { // just round size to int return r(aWidth, aHeight); } // If the set is empty, we will round the content with the default // stepping size. if (!this._letterboxingDimensions.length) { Loading Loading @@ -558,6 +588,17 @@ class _RFPHelper { lazy.logConsole.error(e); } } if (needToShrink && win.shrinkToLetterbox()) { win.addEventListener( "resize", () => { // We need to record the "new" initial size in this listener // because resized dimensions are not immediately available. RFPHelper._recordWindowSize(win); }, { once: true } ); } if (isTesting) { win.promiseDocumentFlushed(() => { Services.obs.notifyObservers( Loading Loading @@ -673,10 +714,41 @@ class _RFPHelper { // maximized. aWindow.setTimeout(() => { tabBrowser.tabbox.classList.add("letterboxing-ready"); if (!aWindow._rfpOriginalSize) { this._recordWindowSize(aWindow); } }); } _recordWindowSize(aWindow) { aWindow.promiseDocumentFlushed(() => { aWindow._rfpOriginalSize = { width: aWindow.outerWidth, height: aWindow.outerHeight, containerHeight: aWindow.gBrowser.getBrowserContainer()?.clientHeight, }; log("Recording original window size", aWindow._rfpOriginalSize); }); } // We will attach this method to each browser window. When called // it will instantly resize the window to exactly fit the selected // (possibly letterboxed) browser. // Returns true if a window resize will occur, false otherwise. shrinkToLetterbox() { let { selectedBrowser } = this.gBrowser; let stack = selectedBrowser.closest(".browserStack"); const outer = stack.getBoundingClientRect(); const inner = selectedBrowser.getBoundingClientRect(); if (inner.width !== outer.witdh || inner.height !== outer.height) { this.resizeBy(inner.width - outer.width, inner.height - outer.height); return true; } return false; } _attachWindow(aWindow) { this._fixRounding(aWindow); aWindow.gBrowser.addTabsProgressListener(this); aWindow.addEventListener("TabOpen", this); let resizeObserver = new aWindow.ResizeObserver(entries => { Loading Loading @@ -983,20 +1055,6 @@ class _RFPHelper { ); } _attachAllWindows() { let windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { let win = windowList.getNext(); if (win.closed || !win.gBrowser) { continue; } this._attachWindow(win); } } _detachWindow(aWindow) { let resizeObserver = this._resizeObservers.get(aWindow); if (resizeObserver) { Loading Loading @@ -1024,20 +1082,6 @@ class _RFPHelper { this._updateLetterboxingColors(aWindow, false); } _detachAllWindows() { let windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { let win = windowList.getNext(); if (win.closed || !win.gBrowser) { continue; } this._detachWindow(win); } } _handleDOMWindowOpened(win) { let self = this; Loading Loading @@ -1069,6 +1113,50 @@ class _RFPHelper { this._detachWindow(win); } _fixRounding(aWindow) { if (!this.rfpEnabled) { return; } // tor-browser#43205: in case of subpixels, new windows might have a wrong // size because of platform-specific bugs (e.g., Bug 1947439 on Windows). const contentContainer = aWindow.document.getElementById("browser"); const rect = contentContainer.getBoundingClientRect(); const steppingWidth = this.stepping(rect.width, true); const steppingHeight = this.stepping(rect.height, false); const deltaWidth = rect.width - steppingWidth * Math.round(rect.width / steppingWidth); const deltaHeight = rect.height - steppingHeight * Math.round(rect.height / steppingHeight); // It seems that under X11, a window cannot have all the possible (integer) // sizes (see the videos on tor-browser#43205 and Bug 1947439)... // We observed this behavior with 1.25 scaling, but we could not find // where it happens exactly, so this code might be wrong. // On the same system, this problem does not happen with Wayland. if (AppConstants.platform === "linux") { let targetWidth = aWindow.outerWidth - deltaWidth; let targetHeight = aWindow.outerHeight - deltaHeight; const x11Size = s => Math.floor( // This first rounding is done by Gecko, rather than X11. Math.round(s * aWindow.devicePixelRatio) / aWindow.devicePixelRatio ); const x11Width = x11Size(targetWidth); const x11Height = x11Size(targetHeight); if (x11Width < targetWidth) { targetWidth = x11Width + 2; } if (x11Height < targetHeight) { targetHeight = x11Height + 2; } // resizeTo truncates on X11, so we compensate. aWindow.resizeTo(Math.ceil(targetWidth), Math.ceil(targetHeight)); } else { aWindow.resizeBy(deltaWidth, deltaHeight); } } getTargets() { return RFPTargetConstants.Targets; } Loading Loading
toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +138 −50 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ * You can obtain one at https://mozilla.org/MPL/2.0/. */ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import * as RFPTargetConstants from "resource://gre/modules/RFPTargetConstants.sys.mjs"; const kPrefResistFingerprinting = "privacy.resistFingerprinting"; Loading Loading @@ -41,6 +42,20 @@ function log(...args) { lazy.logConsole.log(...args); } function forEachWindow(callback) { const windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { const win = windowList.getNext(); if (win.gBrowser && !win.closed) { try { callback(win); } catch (e) { lazy.logConsole.error(e); } } } } class _RFPHelper { _resizeObservers = new WeakMap(); Loading Loading @@ -208,9 +223,13 @@ class _RFPHelper { _handleResistFingerprintingChanged() { this.rfpEnabled = Services.prefs.getBoolPref(kPrefResistFingerprinting); if (ChromeUtils.shouldResistFingerprinting("JSLocalePrompt", null)) { if (this.rfpEnabled) { this._addLanguagePrefObservers(); Services.ww.registerNotification(this); forEachWindow(win => this._attachWindow(win)); } else { forEachWindow(win => this._detachWindow(win)); Services.ww.unregisterNotification(this); this._removeLanguagePrefObservers(); } } Loading Loading @@ -342,18 +361,12 @@ class _RFPHelper { } _handleLetterboxingPrefChanged() { if (Services.prefs.getBoolPref(kPrefLetterboxing, false)) { if (!this._letterboxingPrefObserversAdded) { Services.ww.registerNotification(this); this._letterboxingPrefObserversAdded = true; } this._attachAllWindows(); } else { this._detachAllWindows(); if (this._letterboxingPrefObserversAdded) { Services.ww.unregisterNotification(this); this._letterboxingPrefObserversAdded = false; } this.letterboxingEnabled = Services.prefs.getBoolPref( kPrefLetterboxing, false ); if (this.rfpEnabled) { forEachWindow(win => this._updateSizeForTabsInWindow(win)); } } Loading Loading @@ -435,22 +448,23 @@ class _RFPHelper { } } stepping(aDimension, aIsWidth) { if (aDimension <= 500) { return 50; } else if (aDimension <= 1600) { return aIsWidth ? 200 : 100; } return 200; } /** * Given a width or height, rounds it with the proper stepping. */ steppedSize(aDimension, aIsWidth = false) { let stepping; if (aDimension <= 50) { return aDimension; } else if (aDimension <= 500) { stepping = 50; } else if (aDimension <= 1600) { stepping = aIsWidth ? 200 : 100; } else { stepping = 200; } return aDimension - (aDimension % stepping); return aDimension - (aDimension % this.stepping(aDimension, aIsWidth)); } /** Loading @@ -477,6 +491,17 @@ class _RFPHelper { ]) ); const isInitialSize = win._rfpOriginalSize && win.outerWidth === win._rfpOriginalSize.width && win.outerHeight === win._rfpOriginalSize.height; // We may need to shrink this window to rounded size if the browser container // area is taller than the original, meaning extra chrome (like the optional // "Only Show on New Tab" bookmarks toobar) was present and now gone. const needToShrink = isInitialSize && containerHeight > win._rfpOriginalSize.containerHeight; log( `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} parentWidth=${parentWidth} parentHeight=${parentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}${ isNewTab ? " (new tab)." : "." Loading Loading @@ -504,6 +529,11 @@ class _RFPHelper { log(`${logPrefix} roundDimensions(${aWidth}, ${aHeight})`); if (!this.letterboxingEnabled) { // just round size to int return r(aWidth, aHeight); } // If the set is empty, we will round the content with the default // stepping size. if (!this._letterboxingDimensions.length) { Loading Loading @@ -558,6 +588,17 @@ class _RFPHelper { lazy.logConsole.error(e); } } if (needToShrink && win.shrinkToLetterbox()) { win.addEventListener( "resize", () => { // We need to record the "new" initial size in this listener // because resized dimensions are not immediately available. RFPHelper._recordWindowSize(win); }, { once: true } ); } if (isTesting) { win.promiseDocumentFlushed(() => { Services.obs.notifyObservers( Loading Loading @@ -673,10 +714,41 @@ class _RFPHelper { // maximized. aWindow.setTimeout(() => { tabBrowser.tabbox.classList.add("letterboxing-ready"); if (!aWindow._rfpOriginalSize) { this._recordWindowSize(aWindow); } }); } _recordWindowSize(aWindow) { aWindow.promiseDocumentFlushed(() => { aWindow._rfpOriginalSize = { width: aWindow.outerWidth, height: aWindow.outerHeight, containerHeight: aWindow.gBrowser.getBrowserContainer()?.clientHeight, }; log("Recording original window size", aWindow._rfpOriginalSize); }); } // We will attach this method to each browser window. When called // it will instantly resize the window to exactly fit the selected // (possibly letterboxed) browser. // Returns true if a window resize will occur, false otherwise. shrinkToLetterbox() { let { selectedBrowser } = this.gBrowser; let stack = selectedBrowser.closest(".browserStack"); const outer = stack.getBoundingClientRect(); const inner = selectedBrowser.getBoundingClientRect(); if (inner.width !== outer.witdh || inner.height !== outer.height) { this.resizeBy(inner.width - outer.width, inner.height - outer.height); return true; } return false; } _attachWindow(aWindow) { this._fixRounding(aWindow); aWindow.gBrowser.addTabsProgressListener(this); aWindow.addEventListener("TabOpen", this); let resizeObserver = new aWindow.ResizeObserver(entries => { Loading Loading @@ -983,20 +1055,6 @@ class _RFPHelper { ); } _attachAllWindows() { let windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { let win = windowList.getNext(); if (win.closed || !win.gBrowser) { continue; } this._attachWindow(win); } } _detachWindow(aWindow) { let resizeObserver = this._resizeObservers.get(aWindow); if (resizeObserver) { Loading Loading @@ -1024,20 +1082,6 @@ class _RFPHelper { this._updateLetterboxingColors(aWindow, false); } _detachAllWindows() { let windowList = Services.wm.getEnumerator("navigator:browser"); while (windowList.hasMoreElements()) { let win = windowList.getNext(); if (win.closed || !win.gBrowser) { continue; } this._detachWindow(win); } } _handleDOMWindowOpened(win) { let self = this; Loading Loading @@ -1069,6 +1113,50 @@ class _RFPHelper { this._detachWindow(win); } _fixRounding(aWindow) { if (!this.rfpEnabled) { return; } // tor-browser#43205: in case of subpixels, new windows might have a wrong // size because of platform-specific bugs (e.g., Bug 1947439 on Windows). const contentContainer = aWindow.document.getElementById("browser"); const rect = contentContainer.getBoundingClientRect(); const steppingWidth = this.stepping(rect.width, true); const steppingHeight = this.stepping(rect.height, false); const deltaWidth = rect.width - steppingWidth * Math.round(rect.width / steppingWidth); const deltaHeight = rect.height - steppingHeight * Math.round(rect.height / steppingHeight); // It seems that under X11, a window cannot have all the possible (integer) // sizes (see the videos on tor-browser#43205 and Bug 1947439)... // We observed this behavior with 1.25 scaling, but we could not find // where it happens exactly, so this code might be wrong. // On the same system, this problem does not happen with Wayland. if (AppConstants.platform === "linux") { let targetWidth = aWindow.outerWidth - deltaWidth; let targetHeight = aWindow.outerHeight - deltaHeight; const x11Size = s => Math.floor( // This first rounding is done by Gecko, rather than X11. Math.round(s * aWindow.devicePixelRatio) / aWindow.devicePixelRatio ); const x11Width = x11Size(targetWidth); const x11Height = x11Size(targetHeight); if (x11Width < targetWidth) { targetWidth = x11Width + 2; } if (x11Height < targetHeight) { targetHeight = x11Height + 2; } // resizeTo truncates on X11, so we compensate. aWindow.resizeTo(Math.ceil(targetWidth), Math.ceil(targetHeight)); } else { aWindow.resizeBy(deltaWidth, deltaHeight); } } getTargets() { return RFPTargetConstants.Targets; } Loading