[tor-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 tor-commits mailing list