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.

--HG--
extra : rebase_source : 45e5c93b1845c9c9ca9b35b74f8a0b70c93a5bef
This commit is contained in:
Tim Huang 2019-02-03 14:20:18 -06:00
Родитель eed36e8544
Коммит 79972aef69
4 изменённых файлов: 314 добавлений и 2 удалений

Просмотреть файл

@ -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]

Просмотреть файл

@ -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);
});

Просмотреть файл

@ -1442,6 +1442,9 @@ pref("privacy.firstparty.isolate.restrict_opener_access", true);
// 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

Просмотреть файл

@ -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.