[tbb-commits] [tor-browser/tor-browser-60.6.1esr-9.0-1] Bug 1407366 - Part 4: Adding a test case for testing letterboxing. r=johannh
gk at torproject.org
gk at torproject.org
Fri May 17 07:43:38 UTC 2019
commit 610ad333716499f5f9cf704a1dd97e07d276f572
Author: Tom Ritter <tom at mozilla.com>
Date: Wed Apr 24 09:36:29 2019 -0500
Bug 1407366 - Part 4: Adding a test case for testing letterboxing. r=johannh
This patch adds a test for ensuring the letterboxing works as we expect.
It will open a tab and resize its window into several different sizes
and to see if the margins are correctly apply. And it will also check
that no margin should apply to a tab with chrome privilege.
---
.../resistfingerprinting/test/browser/browser.ini | 1 +
.../browser/browser_dynamical_window_rounding.js | 277 +++++++++++++++++++++
modules/libpref/init/all.js | 3 +
.../components/resistfingerprinting/RFPHelper.jsm | 35 ++-
4 files changed, 314 insertions(+), 2 deletions(-)
diff --git a/browser/components/resistfingerprinting/test/browser/browser.ini b/browser/components/resistfingerprinting/test/browser/browser.ini
index 024ee29907b4..1aa918b4574b 100644
--- a/browser/components/resistfingerprinting/test/browser/browser.ini
+++ b/browser/components/resistfingerprinting/test/browser/browser.ini
@@ -11,6 +11,7 @@ support-files =
head.js
[browser_block_mozAddonManager.js]
+[browser_dynamical_window_rounding.js]
[browser_navigator.js]
[browser_netInfo.js]
[browser_performanceAPI.js]
diff --git a/browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js b/browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js
new file mode 100644
index 000000000000..ea261b7820d7
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js
@@ -0,0 +1,277 @@
+/* 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/.
+ *
+ * Bug 1407366 - A test case for reassuring the size of the content viewport is
+ * rounded if the window is resized when letterboxing is enabled.
+ */
+
+const TEST_PATH = "http://example.net/browser/browser/components/resistfingerprinting/test/browser/";
+
+const DEFAULT_ROUNDED_WIDTH_STEP = 200;
+const DEFAULT_ROUNDED_HEIGHT_STEP = 100;
+
+// A set of test cases which defines the width and the height of the outer window.
+const TEST_CASES = [
+ {width: 1250, height: 1000},
+ {width: 1500, height: 1050},
+ {width: 1120, height: 760},
+ {width: 800, height: 600},
+ {width: 640, height: 400},
+ {width: 500, height: 350},
+ {width: 300, height: 170},
+];
+
+function getPlatform() {
+ const {OS} = Services.appinfo;
+ if (OS == "WINNT") {
+ return "win";
+ } else if (OS == "Darwin") {
+ return "mac";
+ }
+ return "linux";
+}
+
+function handleOSFuzziness(aContent, aTarget) {
+ /*
+ * On Windows, we observed off-by-one pixel differences that
+ * couldn't be expained. When manually setting the window size
+ * to try to reproduce it; it did not occur.
+ */
+ if (getPlatform() == "win") {
+ return Math.abs(aContent - aTarget) <= 1;
+ }
+ return aContent == aTarget;
+}
+
+function checkForDefaultSetting(
+ aContentWidth, aContentHeight, aRealWidth, aRealHeight) {
+ // The default behavior for rounding is to round window with 200x100 stepping.
+ // So, we can get the rounded size by subtracting the remainder.
+ let targetWidth = aRealWidth - (aRealWidth % DEFAULT_ROUNDED_WIDTH_STEP);
+ let targetHeight = aRealHeight - (aRealHeight % DEFAULT_ROUNDED_HEIGHT_STEP);
+
+ // This platform-specific code is explained in the large comment below.
+ if (getPlatform() != "linux") {
+ ok(handleOSFuzziness(aContentWidth, targetWidth),
+ `Default Dimensions: The content window width is correctly rounded into. ${aRealWidth}px -> ${aContentWidth}px should equal ${targetWidth}px`);
+
+ ok(handleOSFuzziness(aContentHeight, targetHeight),
+ `Default Dimensions: The content window height is correctly rounded into. ${aRealHeight}px -> ${aContentHeight}px should equal ${targetHeight}px`);
+
+ // Using ok() above will cause Win/Mac to fail on even the first test, we don't need to repeat it, return true so waitForCondition ends
+ return true;
+ }
+ // Returning true or false depending on if the test succeeded will cause Linux to repeat until it succeeds.
+ return handleOSFuzziness(aContentWidth, targetWidth) && handleOSFuzziness(aContentHeight, targetHeight);
+}
+
+async function test_dynamical_window_rounding(aWindow, aCheckFunc) {
+ // We need to wait for the updating the margins for the newly opened tab, or
+ // it will affect the following tests.
+ let promiseForTheFirstRounding =
+ TestUtils.topicObserved("test:letterboxing:update-margin-finish");
+
+ info("Open a content tab for testing.");
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ aWindow.gBrowser, TEST_PATH + "file_dummy.html");
+
+ info("Wait until the margins are applied for the opened tab.");
+ await promiseForTheFirstRounding;
+
+ let getContainerSize = (aTab) => {
+ let browserContainer = aWindow.gBrowser
+ .getBrowserContainer(aTab.linkedBrowser);
+ return {
+ containerWidth: browserContainer.clientWidth,
+ containerHeight: browserContainer.clientHeight,
+ };
+ };
+
+ for (let {width, height} of TEST_CASES) {
+ let caseString = "Case " + width + "x" + height + ": ";
+ // Create a promise for waiting for the margin update.
+ let promiseRounding =
+ TestUtils.topicObserved("test:letterboxing:update-margin-finish");
+
+ let {containerWidth, containerHeight} = getContainerSize(tab);
+
+ info(caseString + "Resize the window and wait until resize event happened (currently " +
+ containerWidth + "x" + containerHeight + ")");
+ await new Promise(resolve => {
+ ({containerWidth, containerHeight} = getContainerSize(tab));
+ info(caseString + "Resizing (currently " + containerWidth + "x" + containerHeight + ")");
+
+ aWindow.onresize = () => {
+ ({containerWidth, containerHeight} = getContainerSize(tab));
+ info(caseString + "Resized (currently " + containerWidth + "x" + containerHeight + ")");
+ if (getPlatform() == "linux" && containerWidth != width) {
+ /*
+ * We observed frequent test failures that resulted from receiving an onresize
+ * event where the browser was resized to an earlier requested dimension. This
+ * resize event happens on Linux only, and is an artifact of the asynchronous
+ * resizing. (See more discussion on 1407366#53)
+ *
+ * We cope with this problem in two ways.
+ *
+ * 1: If we detect that the browser was resized to the wrong value; we
+ * redo the resize. (This is the lines of code immediately following this
+ * comment)
+ * 2: We repeat the test until it works using waitForCondition(). But we still
+ * test Win/Mac more thoroughly: they do not loop in waitForCondition more
+ * than once, and can fail the test on the first attempt (because their
+ * check() functions use ok() while on Linux, we do not all ok() and instead
+ * rely on waitForCondition to fail).
+ *
+ * The logging statements in this test, and RFPHelper.jsm, help narrow down and
+ * illustrate the issue.
+ */
+ info(caseString + "We hit the weird resize bug. Resize it again.");
+ aWindow.resizeTo(width, height);
+ } else {
+ resolve();
+ }
+ };
+ aWindow.resizeTo(width, height);
+ });
+
+ ({containerWidth, containerHeight} = getContainerSize(tab));
+ info(caseString + "Waiting until margin has been updated on browser element. (currently " +
+ containerWidth + "x" + containerHeight + ")");
+ await promiseRounding;
+
+ info(caseString + "Get innerWidth/Height from the content.");
+ await BrowserTestUtils.waitForCondition(async () => {
+ let {contentWidth, contentHeight} = await ContentTask.spawn(
+ tab.linkedBrowser, null, () => {
+ return {
+ contentWidth: content.innerWidth,
+ contentHeight: content.innerHeight,
+ };
+ });
+
+ info(caseString + "Check the result.");
+ return aCheckFunc(contentWidth, contentHeight, containerWidth, containerHeight);
+ }, "Default Dimensions: The content window width is correctly rounded into.");
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function test_customize_width_and_height(aWindow) {
+ const test_dimensions = `120x80, 200x143, 335x255, 600x312, 742x447, 813x558,
+ 990x672, 1200x733, 1470x858`;
+
+ await SpecialPowers.pushPrefEnv({"set":
+ [
+ ["privacy.resistFingerprinting.letterboxing.dimensions", test_dimensions],
+ ],
+ });
+
+ let dimensions_set = test_dimensions.split(",").map(item => {
+ let sizes = item.split("x").map(size => parseInt(size, 10));
+
+ return {
+ width: sizes[0],
+ height: sizes[1],
+ };
+ });
+
+ let checkDimension =
+ (aContentWidth, aContentHeight, aRealWidth, aRealHeight) => {
+ let matchingArea = aRealWidth * aRealHeight;
+ let minWaste = Number.MAX_SAFE_INTEGER;
+ let targetDimensions = undefined;
+
+ // Find the dimensions which waste the least content area.
+ for (let dim of dimensions_set) {
+ if (dim.width > aRealWidth || dim.height > aRealHeight) {
+ continue;
+ }
+
+ let waste = matchingArea - dim.width * dim.height;
+
+ if (waste >= 0 && waste < minWaste) {
+ targetDimensions = dim;
+ minWaste = waste;
+ }
+ }
+
+ // This platform-specific code is explained in the large comment above.
+ if (getPlatform() != "linux") {
+ ok(handleOSFuzziness(aContentWidth, targetDimensions.width),
+ `Custom Dimension: The content window width is correctly rounded into. ${aRealWidth}px -> ${aContentWidth}px should equal ${targetDimensions.width}`);
+
+ ok(handleOSFuzziness(aContentHeight, targetDimensions.height),
+ `Custom Dimension: The content window height is correctly rounded into. ${aRealHeight}px -> ${aContentHeight}px should equal ${targetDimensions.height}`);
+
+ // Using ok() above will cause Win/Mac to fail on even the first test, we don't need to repeat it, return true so waitForCondition ends
+ return true;
+ }
+ // Returning true or false depending on if the test succeeded will cause Linux to repeat until it succeeds.
+ return handleOSFuzziness(aContentWidth, targetDimensions.width) && handleOSFuzziness(aContentHeight, targetDimensions.height);
+ };
+
+ await test_dynamical_window_rounding(aWindow, checkDimension);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+async function test_no_rounding_for_chrome(aWindow) {
+ // First, resize the window to a size with is not rounded.
+ await new Promise(resolve => {
+ aWindow.onresize = () => resolve();
+ aWindow.resizeTo(700, 450);
+ });
+
+ // open a chrome privilege tab, like about:config.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ aWindow.gBrowser, "about:config");
+
+ // Check that the browser element should not have a margin.
+ is(tab.linkedBrowser.style.margin, "", "There is no margin around chrome tab.");
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({"set":
+ [
+ ["privacy.resistFingerprinting.letterboxing", true],
+ ["privacy.resistFingerprinting.letterboxing.testing", true],
+ ],
+ });
+});
+
+add_task(async function do_tests() {
+ // Store the original window size before testing.
+ let originalOuterWidth = window.outerWidth;
+ let originalOuterHeight = window.outerHeight;
+
+ info("Run test for the default window rounding.");
+ await test_dynamical_window_rounding(window, checkForDefaultSetting);
+
+ info("Run test for the window rounding with customized dimensions.");
+ await test_customize_width_and_height(window);
+
+ info("Run test for no margin around tab with the chrome privilege.");
+ await test_no_rounding_for_chrome(window);
+
+ // Restore the original window size.
+ window.outerWidth = originalOuterWidth;
+ window.outerHeight = originalOuterHeight;
+
+ // Testing that whether the dynamical rounding works for new windows.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ info("Run test for the default window rounding in new window.");
+ await test_dynamical_window_rounding(win, checkForDefaultSetting);
+
+ info("Run test for the window rounding with customized dimensions in new window.");
+ await test_customize_width_and_height(win);
+
+ info("Run test for no margin around tab with the chrome privilege in new window.");
+ await test_no_rounding_for_chrome(win);
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 9dc30b8efe44..5b1d0a1c8972 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1414,6 +1414,9 @@ pref("privacy.resistFingerprinting", false);
// If you do set it, to work around some broken website, please file a bug with
// information so we can understand why it is needed.
pref("privacy.resistFingerprinting.autoDeclineNoUserInputCanvasPrompts", true);
+// The log level for browser console messages logged in RFPHelper.jsm
+// Change to 'All' and restart to see the messages
+pref("privacy.resistFingerprinting.jsmloglevel", "Warn");
// A subset of Resist Fingerprinting protections focused specifically on timers for testing
// This affects the Animation API, the performance APIs, Date.getTime, Event.timestamp,
// File.lastModified, audioContext.currentTime, canvas.captureStream.currentTime
diff --git a/toolkit/components/resistfingerprinting/RFPHelper.jsm b/toolkit/components/resistfingerprinting/RFPHelper.jsm
index 4fb889ab16fe..2f3a1dd0e659 100755
--- a/toolkit/components/resistfingerprinting/RFPHelper.jsm
+++ b/toolkit/components/resistfingerprinting/RFPHelper.jsm
@@ -16,12 +16,26 @@ const kTopicHttpOnModifyRequest = "http-on-modify-request";
const kPrefLetterboxing = "privacy.resistFingerprinting.letterboxing";
const kPrefLetterboxingDimensions =
"privacy.resistFingerprinting.letterboxing.dimensions";
+const kPrefLetterboxingTesting =
+ "privacy.resistFingerprinting.letterboxing.testing";
const kTopicDOMWindowOpened = "domwindowopened";
const kEventLetterboxingSizeUpdate = "Letterboxing:ContentSizeUpdated";
const kDefaultWidthStepping = 200;
const kDefaultHeightStepping = 100;
+var logConsole;
+function log(msg) {
+ if (!logConsole) {
+ logConsole = console.createInstance({
+ prefix: "RFPHelper.jsm",
+ maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel",
+ });
+ }
+
+ logConsole.log(msg);
+}
+
class _RFPHelper {
// ============================================================================
// Shared Setup
@@ -41,6 +55,8 @@ class _RFPHelper {
Services.prefs.addObserver(kPrefLetterboxing, this);
XPCOMUtils.defineLazyPreferenceGetter(this, "_letterboxingDimensions",
kPrefLetterboxingDimensions, "", null, this._parseLetterboxingDimensions);
+ XPCOMUtils.defineLazyPreferenceGetter(this, "_isLetterboxingTesting",
+ kPrefLetterboxingTesting, false);
// Add RFP and Letterboxing observers if prefs are enabled
this._handleResistFingerprintingChanged();
@@ -326,6 +342,8 @@ class _RFPHelper {
* content viewport.
*/
async _roundContentView(aBrowser) {
+ let logId = Math.random();
+ log("_roundContentView[" + logId + "]");
let win = aBrowser.ownerGlobal;
let browserContainer = aBrowser.getTabBrowser()
.getBrowserContainer(aBrowser);
@@ -345,14 +363,21 @@ class _RFPHelper {
};
});
+ log("_roundContentView[" + logId + "] contentWidth=" + contentWidth + " contentHeight=" + contentHeight +
+ " containerWidth=" + containerWidth + " containerHeight=" + containerHeight + " ");
+
let calcMargins = (aWidth, aHeight) => {
+ let result;
+ log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ")");
// If the set is empty, we will round the content with the default
// stepping size.
if (!this._letterboxingDimensions.length) {
- return {
+ result = {
width: (aWidth % kDefaultWidthStepping) / 2,
height: (aHeight % kDefaultHeightStepping) / 2,
};
+ log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ") = " + result.width + " x " + result.height);
+ return result;
}
let matchingArea = aWidth * aHeight;
@@ -375,7 +400,6 @@ class _RFPHelper {
}
}
- let result;
// If we cannot find any dimensions match to the real content window, this
// means the content area is smaller the smallest size in the set. In this
// case, we won't apply any margins.
@@ -391,6 +415,7 @@ class _RFPHelper {
};
}
+ log("_roundContentView[" + logId + "] calcMargins(" + aWidth + ", " + aHeight + ") = " + result.width + " x " + result.height);
return result;
};
@@ -401,10 +426,16 @@ class _RFPHelper {
// If the size of the content is already quantized, we do nothing.
if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) {
+ log("_roundContentView[" + logId + "] is_rounded == true");
+ if (this._isLetterboxingTesting) {
+ log("_roundContentView[" + logId + "] is_rounded == true test:letterboxing:update-margin-finish");
+ Services.obs.notifyObservers(null, "test:letterboxing:update-margin-finish");
+ }
return;
}
win.requestAnimationFrame(() => {
+ log("_roundContentView[" + logId + "] setting margins to " + margins.width + " x " + margins.height);
// 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.
More information about the tbb-commits
mailing list