tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit b80e26a6f9420ab9c430915cd576c030c1259b81
parent 9be37de37d2441966d9b3e3d7ce7e5f636398ec6
Author: hackademix <giorgio@maone.net>
Date:   Thu, 23 Mar 2023 23:29:21 +0100

BB 41631: Prevent weird initial window dimensions caused by subpixel computations

Diffstat:
Mtoolkit/components/resistfingerprinting/RFPHelper.sys.mjs | 188++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
1 file changed, 138 insertions(+), 50 deletions(-)

diff --git a/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs b/toolkit/components/resistfingerprinting/RFPHelper.sys.mjs @@ -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"; @@ -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(); @@ -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(); } } @@ -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)); } } @@ -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)); } /** @@ -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)." : "." @@ -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) { @@ -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( @@ -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 => { @@ -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) { @@ -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; @@ -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; }