Loading browser/base/content/browser.xhtml +4 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,10 @@ <link rel="stylesheet" href="chrome://browser/skin/translations/panel.css" /> <link rel="stylesheet" href="chrome://browser/skin/places/tree-icons.css" /> <link rel="stylesheet" href="chrome://browser/skin/places/editBookmark.css" /> <link rel="stylesheet" href="chrome://global/content/resistfingerprinting/letterboxing.css" /> <link rel="localization" href="branding/brand.ftl"/> <link rel="localization" href="browser/allTabsMenu.ftl"/> Loading toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +118 −51 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ class _RFPHelper { switch (aMessage.type) { case "TabOpen": { let tab = aMessage.target; this._addOrClearContentMargin(tab.linkedBrowser); this._addOrClearContentMargin(tab.linkedBrowser, /* isNewTab = */ true); break; } default: Loading Loading @@ -352,20 +352,50 @@ class _RFPHelper { }); } _addOrClearContentMargin(aBrowser) { let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser); getLetterboxingDefaultRule(aBrowser) { let document = aBrowser.ownerDocument; return (document._letterboxingMarginsRule ||= (() => { // 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 const LETTERBOX_CSS_URL = "chrome://global/content/resistfingerprinting/letterboxing.css"; const LETTERBOX_CSS_SELECTOR = ".letterboxing .browserStack > browser:not(.exclude-letterboxing)"; for (let ss of document.styleSheets) { if (ss.href !== LETTERBOX_CSS_URL) { continue; } for (let rule of ss.rules) { if (rule.selectorText === LETTERBOX_CSS_SELECTOR) { return rule; } } } return null; // shouldn't happen })()); } _noLetterBoxingFor({ contentPrincipal, currentURI }) { // we don't want letterboxing on... return ( // ... privileged pages contentPrincipal.isSystemPrincipal || // ... about: URIs EXCEPT about:blank (currentURI.schemeIs("about") && currentURI.filePath !== "blank") ); } _addOrClearContentMargin(aBrowser, isNewTab = false) { // We won't do anything for lazy browsers. if (!aBrowser.isConnected) { return; } // We should apply no margin around an empty tab or a tab with system // principal. if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) { if (this._noLetterBoxingFor(aBrowser)) { // this tab doesn't need letterboxing this._clearContentViewMargin(aBrowser); } else { this._roundContentView(aBrowser); this._roundContentView(aBrowser, isNewTab); } } Loading @@ -391,46 +421,38 @@ class _RFPHelper { * The function will round the given browser by adding margins around the * content viewport. */ async _roundContentView(aBrowser) { async _roundContentView(aBrowser, isNewTab = false) { let logPrefix = `_roundContentView[${Math.random()}]`; log(logPrefix); aBrowser.classList.remove("exclude-letterboxing"); let win = aBrowser.ownerGlobal; let browserContainer = aBrowser .getTabBrowser() .getBrowserContainer(aBrowser); let { contentWidth, contentHeight, containerWidth, containerHeight } = await win.promiseDocumentFlushed(() => { let contentWidth = aBrowser.clientWidth; let contentHeight = aBrowser.clientHeight; let containerWidth = browserContainer.clientWidth; let containerHeight = browserContainer.clientHeight; // If the findbar or devtools are out, we need to subtract their height (plus 1 // for the separator) from the container height, because we need to adjust our // letterboxing to account for it; however it is not included in that dimension // (but rather is subtracted from the content height.) let findBar = win.gFindBarInitialized ? win.gFindBar : undefined; let findBarOffset = findBar && !findBar.hidden ? findBar.clientHeight + 1 : 0; let devtools = browserContainer.getElementsByClassName( "devtools-toolbox-bottom-iframe" let browserParent = aBrowser.parentElement; let [ [contentWidth, contentHeight], [parentWidth, parentHeight], [containerWidth, containerHeight], ] = await win.promiseDocumentFlushed(() => // Read layout info only inside this callback and do not write, to avoid additional reflows [aBrowser, browserParent, browserContainer] .map(e => e.getBoundingClientRect()) .map(r => [r.width, r.height]) ); let devtoolsOffset = devtools.length ? devtools[0].clientHeight : 0; return { contentWidth, contentHeight, containerWidth, containerHeight: containerHeight - findBarOffset - devtoolsOffset, }; }); log( `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}.` `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} parentWidth=${parentWidth} parentHeight=${parentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}${ isNewTab ? " (new tab)." : "." }` ); let calcMargins = (aWidth, aHeight) => { if (containerWidth === 0) { // race condition: tab already be closed, bail out return; } const calcMargins = (aWidth, aHeight) => { let result; log(`${logPrefix} calcMargins(${aWidth}, ${aHeight})`); // If the set is empty, we will round the content with the default Loading Loading @@ -490,10 +512,54 @@ class _RFPHelper { // Calculating the margins around the browser element in order to round the // content viewport. We will use a 200x100 stepping if the dimension set // is not given. let margins = calcMargins(containerWidth, containerHeight); const buildMarginStyleString = (aWidth, aHeight) => { const marginDims = calcMargins(aWidth, aHeight); // snap browser element to top const top = 0, // and leave 'double' margin at the bottom bottom = 2 * marginDims.height, // identical margins left and right left = marginDims.width, right = marginDims.width; return `${top}px ${right}px ${bottom}px ${left}px`; }; const marginChanges = Object.assign([], { queueIfNeeded({ style }, margin) { if (style.margin !== margin) { this.push(() => { style.margin = margin; }); } }, perform() { win.requestAnimationFrame(() => { for (let change of this) { change(); } }); }, }); marginChanges.queueIfNeeded( this.getLetterboxingDefaultRule(aBrowser), buildMarginStyleString(containerWidth, containerHeight) ); const marginStyleString = !isNewTab && // new tabs cannot have extra UI components (containerHeight > parentHeight || containerWidth > parentWidth) ? // optional UI components such as the notification box, the find bar // or devtools are constraining this browser's size: recompute custom buildMarginStyleString(parentWidth, parentHeight) : ""; // otherwise we can keep the default letterboxing margins marginChanges.queueIfNeeded(aBrowser, marginStyleString); // If the size of the content is already quantized, we do nothing. if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) { if (!marginChanges.length) { log(`${logPrefix} is_rounded == true`); if (this._isLetterboxingTesting) { log( Loading @@ -507,24 +573,22 @@ class _RFPHelper { return; } win.requestAnimationFrame(() => { log(`${logPrefix} setting margins to ${margins.width} x ${margins.height}`); log(`${logPrefix} setting margins to ${marginStyleString}`); // One cannot (easily) control the color of a margin unfortunately. // An initial attempt to use a border instead of a margin resulted // in offset event dispatching; so for now we use a colorless margin. aBrowser.style.margin = `${margins.height}px ${margins.width}px`; }); marginChanges.perform(); } _clearContentViewMargin(aBrowser) { aBrowser.ownerGlobal.requestAnimationFrame(() => { aBrowser.style.margin = ""; }); aBrowser.classList.add("exclude-letterboxing"); } _updateMarginsForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; tabBrowser.tabpanels?.classList.add("letterboxing"); for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._addOrClearContentMargin(browser); Loading Loading @@ -558,7 +622,10 @@ class _RFPHelper { tabBrowser.removeTabsProgressListener(this); aWindow.removeEventListener("TabOpen", this); // Clear all margins and tooltip for all browsers. // revert tabpanel's style to default tabBrowser.tabpanels?.classList.remove("letterboxing"); // and restore default margins on each browser element for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._clearContentViewMargin(browser); Loading toolkit/components/resistfingerprinting/content/letterboxing.css 0 → 100644 +16 −0 Original line number Diff line number Diff line /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Never modify the following selector without synchronizing LETTERBOX_CSS_SELECTOR in RFPHelper.sys.mjs! And you will need to update the JS also if you move the rules to another file. **/ .letterboxing .browserStack > browser:not(.exclude-letterboxing) { margin: 0; /* to be dynamically set by RFHelper.sys.mjs */ } browser.exclude-letterboxing { margin: 0 !important; } toolkit/components/resistfingerprinting/jar.mn +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. toolkit.jar: content/global/resistfingerprinting/letterboxing.css (content/letterboxing.css) content/global/usercharacteristics/gl-matrix.js (content/gl-matrix.js) content/global/usercharacteristics/ssdeep.js (content/ssdeep.js) content/global/usercharacteristics/usercharacteristics.css (content/usercharacteristics.css) Loading Loading
browser/base/content/browser.xhtml +4 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,10 @@ <link rel="stylesheet" href="chrome://browser/skin/translations/panel.css" /> <link rel="stylesheet" href="chrome://browser/skin/places/tree-icons.css" /> <link rel="stylesheet" href="chrome://browser/skin/places/editBookmark.css" /> <link rel="stylesheet" href="chrome://global/content/resistfingerprinting/letterboxing.css" /> <link rel="localization" href="branding/brand.ftl"/> <link rel="localization" href="browser/allTabsMenu.ftl"/> Loading
toolkit/components/resistfingerprinting/RFPHelper.sys.mjs +118 −51 Original line number Diff line number Diff line Loading @@ -106,7 +106,7 @@ class _RFPHelper { switch (aMessage.type) { case "TabOpen": { let tab = aMessage.target; this._addOrClearContentMargin(tab.linkedBrowser); this._addOrClearContentMargin(tab.linkedBrowser, /* isNewTab = */ true); break; } default: Loading Loading @@ -352,20 +352,50 @@ class _RFPHelper { }); } _addOrClearContentMargin(aBrowser) { let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser); getLetterboxingDefaultRule(aBrowser) { let document = aBrowser.ownerDocument; return (document._letterboxingMarginsRule ||= (() => { // 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 const LETTERBOX_CSS_URL = "chrome://global/content/resistfingerprinting/letterboxing.css"; const LETTERBOX_CSS_SELECTOR = ".letterboxing .browserStack > browser:not(.exclude-letterboxing)"; for (let ss of document.styleSheets) { if (ss.href !== LETTERBOX_CSS_URL) { continue; } for (let rule of ss.rules) { if (rule.selectorText === LETTERBOX_CSS_SELECTOR) { return rule; } } } return null; // shouldn't happen })()); } _noLetterBoxingFor({ contentPrincipal, currentURI }) { // we don't want letterboxing on... return ( // ... privileged pages contentPrincipal.isSystemPrincipal || // ... about: URIs EXCEPT about:blank (currentURI.schemeIs("about") && currentURI.filePath !== "blank") ); } _addOrClearContentMargin(aBrowser, isNewTab = false) { // We won't do anything for lazy browsers. if (!aBrowser.isConnected) { return; } // We should apply no margin around an empty tab or a tab with system // principal. if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) { if (this._noLetterBoxingFor(aBrowser)) { // this tab doesn't need letterboxing this._clearContentViewMargin(aBrowser); } else { this._roundContentView(aBrowser); this._roundContentView(aBrowser, isNewTab); } } Loading @@ -391,46 +421,38 @@ class _RFPHelper { * The function will round the given browser by adding margins around the * content viewport. */ async _roundContentView(aBrowser) { async _roundContentView(aBrowser, isNewTab = false) { let logPrefix = `_roundContentView[${Math.random()}]`; log(logPrefix); aBrowser.classList.remove("exclude-letterboxing"); let win = aBrowser.ownerGlobal; let browserContainer = aBrowser .getTabBrowser() .getBrowserContainer(aBrowser); let { contentWidth, contentHeight, containerWidth, containerHeight } = await win.promiseDocumentFlushed(() => { let contentWidth = aBrowser.clientWidth; let contentHeight = aBrowser.clientHeight; let containerWidth = browserContainer.clientWidth; let containerHeight = browserContainer.clientHeight; // If the findbar or devtools are out, we need to subtract their height (plus 1 // for the separator) from the container height, because we need to adjust our // letterboxing to account for it; however it is not included in that dimension // (but rather is subtracted from the content height.) let findBar = win.gFindBarInitialized ? win.gFindBar : undefined; let findBarOffset = findBar && !findBar.hidden ? findBar.clientHeight + 1 : 0; let devtools = browserContainer.getElementsByClassName( "devtools-toolbox-bottom-iframe" let browserParent = aBrowser.parentElement; let [ [contentWidth, contentHeight], [parentWidth, parentHeight], [containerWidth, containerHeight], ] = await win.promiseDocumentFlushed(() => // Read layout info only inside this callback and do not write, to avoid additional reflows [aBrowser, browserParent, browserContainer] .map(e => e.getBoundingClientRect()) .map(r => [r.width, r.height]) ); let devtoolsOffset = devtools.length ? devtools[0].clientHeight : 0; return { contentWidth, contentHeight, containerWidth, containerHeight: containerHeight - findBarOffset - devtoolsOffset, }; }); log( `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}.` `${logPrefix} contentWidth=${contentWidth} contentHeight=${contentHeight} parentWidth=${parentWidth} parentHeight=${parentHeight} containerWidth=${containerWidth} containerHeight=${containerHeight}${ isNewTab ? " (new tab)." : "." }` ); let calcMargins = (aWidth, aHeight) => { if (containerWidth === 0) { // race condition: tab already be closed, bail out return; } const calcMargins = (aWidth, aHeight) => { let result; log(`${logPrefix} calcMargins(${aWidth}, ${aHeight})`); // If the set is empty, we will round the content with the default Loading Loading @@ -490,10 +512,54 @@ class _RFPHelper { // Calculating the margins around the browser element in order to round the // content viewport. We will use a 200x100 stepping if the dimension set // is not given. let margins = calcMargins(containerWidth, containerHeight); const buildMarginStyleString = (aWidth, aHeight) => { const marginDims = calcMargins(aWidth, aHeight); // snap browser element to top const top = 0, // and leave 'double' margin at the bottom bottom = 2 * marginDims.height, // identical margins left and right left = marginDims.width, right = marginDims.width; return `${top}px ${right}px ${bottom}px ${left}px`; }; const marginChanges = Object.assign([], { queueIfNeeded({ style }, margin) { if (style.margin !== margin) { this.push(() => { style.margin = margin; }); } }, perform() { win.requestAnimationFrame(() => { for (let change of this) { change(); } }); }, }); marginChanges.queueIfNeeded( this.getLetterboxingDefaultRule(aBrowser), buildMarginStyleString(containerWidth, containerHeight) ); const marginStyleString = !isNewTab && // new tabs cannot have extra UI components (containerHeight > parentHeight || containerWidth > parentWidth) ? // optional UI components such as the notification box, the find bar // or devtools are constraining this browser's size: recompute custom buildMarginStyleString(parentWidth, parentHeight) : ""; // otherwise we can keep the default letterboxing margins marginChanges.queueIfNeeded(aBrowser, marginStyleString); // If the size of the content is already quantized, we do nothing. if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) { if (!marginChanges.length) { log(`${logPrefix} is_rounded == true`); if (this._isLetterboxingTesting) { log( Loading @@ -507,24 +573,22 @@ class _RFPHelper { return; } win.requestAnimationFrame(() => { log(`${logPrefix} setting margins to ${margins.width} x ${margins.height}`); log(`${logPrefix} setting margins to ${marginStyleString}`); // One cannot (easily) control the color of a margin unfortunately. // An initial attempt to use a border instead of a margin resulted // in offset event dispatching; so for now we use a colorless margin. aBrowser.style.margin = `${margins.height}px ${margins.width}px`; }); marginChanges.perform(); } _clearContentViewMargin(aBrowser) { aBrowser.ownerGlobal.requestAnimationFrame(() => { aBrowser.style.margin = ""; }); aBrowser.classList.add("exclude-letterboxing"); } _updateMarginsForTabsInWindow(aWindow) { let tabBrowser = aWindow.gBrowser; tabBrowser.tabpanels?.classList.add("letterboxing"); for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._addOrClearContentMargin(browser); Loading Loading @@ -558,7 +622,10 @@ class _RFPHelper { tabBrowser.removeTabsProgressListener(this); aWindow.removeEventListener("TabOpen", this); // Clear all margins and tooltip for all browsers. // revert tabpanel's style to default tabBrowser.tabpanels?.classList.remove("letterboxing"); // and restore default margins on each browser element for (let tab of tabBrowser.tabs) { let browser = tab.linkedBrowser; this._clearContentViewMargin(browser); Loading
toolkit/components/resistfingerprinting/content/letterboxing.css 0 → 100644 +16 −0 Original line number Diff line number Diff line /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** Never modify the following selector without synchronizing LETTERBOX_CSS_SELECTOR in RFPHelper.sys.mjs! And you will need to update the JS also if you move the rules to another file. **/ .letterboxing .browserStack > browser:not(.exclude-letterboxing) { margin: 0; /* to be dynamically set by RFHelper.sys.mjs */ } browser.exclude-letterboxing { margin: 0 !important; }
toolkit/components/resistfingerprinting/jar.mn +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. toolkit.jar: content/global/resistfingerprinting/letterboxing.css (content/letterboxing.css) content/global/usercharacteristics/gl-matrix.js (content/gl-matrix.js) content/global/usercharacteristics/ssdeep.js (content/ssdeep.js) content/global/usercharacteristics/usercharacteristics.css (content/usercharacteristics.css) Loading