Pier Angelo Vendrame pushed to branch base-browser-102.9.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits: 4ad8902a by hackademix at 2023-03-29T15:14:42+02:00 Bug 41631: Prevent weird initial window dimensions caused by subpixel computations
- - - - - 9e31ce5b by hackademix at 2023-03-29T15:15:03+02:00 Bug 41695: Warn on window maximization without letterboxing in RFPHelper module
- - - - -
2 changed files:
- browser/locales/en-US/browser/languageNotification.ftl - toolkit/components/resistfingerprinting/RFPHelper.jsm
Changes:
===================================== browser/locales/en-US/browser/languageNotification.ftl ===================================== @@ -8,3 +8,7 @@ language-notification-label-system = { -brand-short-name } has set your display # $language is the language Tor Browser is displayed in (already translated). language-notification-label = { -brand-short-name } has set your display language to { $language }. language-notification-button = Change Language… + +basebrowser-rfp-maximize-warning-message = Maximizing the browser window can allow websites to determine your monitor size, which can be used to track you. We recommend that you leave browser windows in their original default size. +basebrowser-rfp-restore-window-size-button-label = Restore +basebrowser-rfp-restore-window-size-button-ak = R
===================================== toolkit/components/resistfingerprinting/RFPHelper.jsm ===================================== @@ -22,16 +22,110 @@ const kPrefLetterboxingTesting = "privacy.resistFingerprinting.letterboxing.testing"; const kTopicDOMWindowOpened = "domwindowopened";
-var logConsole; -function log(msg) { - if (!logConsole) { - logConsole = console.createInstance({ - prefix: "RFPHelper.jsm", - maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel", - }); +const kPrefResizeWarnings = "privacy.resistFingerprinting.resizeWarnings"; + +XPCOMUtils.defineLazyGetter(this, "logConsole", () => + console.createInstance({ + prefix: "RFPHelper.jsm", + maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel", + }) +); + +function log(...args) { + 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) { + logConsole.error(e); + } + } + } +} + + +async function windowResizeHandler(aEvent) { + if (RFPHelper.letterboxingEnabled) { + return; + } + if (Services.prefs.getIntPref(kPrefResizeWarnings, 3) <= 0) { + return; + } + + const window = aEvent.currentTarget; + + // Wait for end of execution queue to ensure we have correct windowState. + await new Promise(resolve => window.setTimeout(resolve, 0)); + switch (window.windowState) { + case window.STATE_MAXIMIZED: + case window.STATE_FULLSCREEN: + break; + default: + return; + } + + // Do not add another notification if one is already showing. + const kNotificationName = "rfp-window-resize-notification"; + let box = window.gNotificationBox; + if (box.getNotificationWithValue(kNotificationName)) { + return; }
- logConsole.log(msg); + // Rate-limit showing our notification if needed. + if (Date.now() - (windowResizeHandler.timestamp || 0) < 1000) { + return; + } + windowResizeHandler.timestamp = Date.now(); + + const decreaseWarningsCount = () => { + const currentCount = Services.prefs.getIntPref(kPrefResizeWarnings); + if (currentCount > 0) { + Services.prefs.setIntPref(kPrefResizeWarnings, currentCount - 1); + } + }; + + const [label, accessKey] = await window.document.l10n.formatValues([ + { id: "basebrowser-rfp-restore-window-size-button-label" }, + { id: "basebrowser-rfp-restore-window-size-button-ak" }, + ]); + + const buttons = [ + { + label, + accessKey, + popup: null, + callback() { + // reset notification timer to work-around resize race conditions + windowResizeHandler.timestamp = Date.now(); + // restore the original (rounded) size we had stored on window startup + let { _rfpOriginalSize } = window; + window.setTimeout(() => { + window.resizeTo(_rfpOriginalSize.width, _rfpOriginalSize.height); + }, 0); + }, + }, + ]; + + box.appendNotification( + kNotificationName, + { + label: { "l10n-id": "basebrowser-rfp-maximize-warning-message" }, + priority: box.PRIORITY_WARNING_LOW, + eventCallback(event) { + if (event === "dismissed") { + // user manually dismissed the notification + decreaseWarningsCount(); + } + }, + }, + buttons + ); }
class _RFPHelper { @@ -158,7 +252,11 @@ class _RFPHelper { _handleResistFingerprintingChanged() { if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) { this._addRFPObservers(); + Services.ww.registerNotification(this); + forEachWindow(win => this._attachWindow(win)); } else { + forEachWindow(win => this._detachWindow(win)); + Services.ww.unregisterNotification(this); this._removeRFPObservers(); } } @@ -295,13 +393,11 @@ class _RFPHelper { }
_handleLetterboxingPrefChanged() { - if (Services.prefs.getBoolPref(kPrefLetterboxing, false)) { - Services.ww.registerNotification(this); - this._attachAllWindows(); - } else { - this._detachAllWindows(); - Services.ww.unregisterNotification(this); - } + this.letterboxingEnabled = Services.prefs.getBoolPref( + kPrefLetterboxing, + false + ); + forEachWindow(win => this._updateSizeForTabsInWindow(win)); }
// The function to parse the dimension set from the pref value. The pref value @@ -402,11 +498,13 @@ class _RFPHelper { let logPrefix = `_roundContentSize[${Math.random()}]`; log(logPrefix); let win = aBrowser.ownerGlobal; + let browserContainer = aBrowser .getTabBrowser() .getBrowserContainer(aBrowser); let browserParent = aBrowser.parentElement; browserParent.classList.remove("exclude-letterboxing"); + let [ [contentWidth, contentHeight], [parentWidth, parentHeight], @@ -419,6 +517,27 @@ class _RFPHelper { ]) );
+ if ( + !win._rfpSizeOffset || + (win._rfpOriginalSize && + win.outerWidth === win._rfpOriginalSize.width && + win.outerHeight === win._rfpOriginalSize.height) + ) { + const BASELINE_ROUNDING = 10; + const offset = s => + s - Math.round(s / BASELINE_ROUNDING) * BASELINE_ROUNDING; + + win._rfpSizeOffset = { + width: offset(parentWidth), + height: offset(parentHeight), + }; + log( + `${logPrefix} Window size offsets %o (from %s, %s)`, + win._rfpSizeOffset, + parentWidth, + parentHeight + ); + } log( `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} parentWidth=${parentWidth} parentHeight=${parentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}${ isNewTab ? " (new tab)." : "." @@ -437,6 +556,16 @@ class _RFPHelper { });
let result; + + if (!this.letterboxingEnabled) { + const offset = win._rfpSizeOffset; + result = r(aWidth - offset.width, aHeight - offset.height); + log( + `${logPrefix} Letterboxing disabled, applying baseline rounding offsets: (${aWidth}, ${aHeight}) => ${result.width} x ${result.height})` + ); + return result; + } + log(`${logPrefix} roundDimensions(${aWidth}, ${aHeight})`); // If the set is empty, we will round the content with the default // stepping size. @@ -554,7 +683,6 @@ class _RFPHelper {
_updateSizeForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; - tabBrowser.tabpanels?.classList.add("letterboxing");
for (let tab of tabBrowser.tabs) { @@ -564,10 +692,18 @@ class _RFPHelper { // we need to add this class late because otherwise new windows get maximized aWindow.setTimeout(() => { tabBrowser.tabpanels?.classList.add("letterboxing-ready"); + if (!aWindow._rfpOriginalSize) { + aWindow._rfpOriginalSize = { + width: aWindow.outerWidth, + height: aWindow.outerHeight, + }; + log("Recording original window size", aWindow._rfpOriginalSize); + } }); }
_attachWindow(aWindow) { + aWindow.addEventListener("sizemodechange", windowResizeHandler); aWindow.gBrowser.addTabsProgressListener(this); aWindow.addEventListener("TabOpen", this); const resizeObserver = (aWindow._rfpResizeObserver = new aWindow.ResizeObserver( @@ -585,19 +721,7 @@ class _RFPHelper { this._updateSizeForTabsInWindow(aWindow); }
- _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 tabBrowser = aWindow.gBrowser; @@ -614,20 +738,7 @@ class _RFPHelper { let browser = tab.linkedBrowser; this._resetContentSize(browser); } - } - - _detachAllWindows() { - let windowList = Services.wm.getEnumerator("navigator:browser"); - - while (windowList.hasMoreElements()) { - let win = windowList.getNext(); - - if (win.closed || !win.gBrowser) { - continue; - } - - this._detachWindow(win); - } + aWindow.removeEventListener("sizemodechange", windowResizeHandler); }
_handleDOMWindowOpened(win) {
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/compare/f23c4fd...
tor-commits@lists.torproject.org