summaryrefslogtreecommitdiffstats
path: root/browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js
blob: 13dcec8ea1d25f84c480aaf9f5d6250adc2b652d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
/* 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.
 *
 * A helpful note: if this test starts randomly failing; it may be because the
 * zoom level was not reset by an earlier-run test. See Bug 1407366 for an
 * example.
 */

const { RFPHelper } = ChromeUtils.importESModule(
  "resource://gre/modules/RFPHelper.sys.mjs"
);

// 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 },
];

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
) {
  // We can get the rounded size by subtracting twice the margin.
  let targetWidth = aRealWidth - 2 * RFPHelper.steppedRange(aRealWidth);
  let targetHeight = aRealHeight - 2 * RFPHelper.steppedRange(aRealHeight);

  // 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 SpecialPowers.spawn(
        tab.linkedBrowser,
        [],
        () => {
          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 which 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);
}

// Tests that the findbar opening and closing causes a margin update.
async function test_findbar(aWindow) {
  // First, resize the window to a size which is not rounded.
  await new Promise(resolve => {
    aWindow.onresize = () => resolve();
    aWindow.resizeTo(701, 451);
  });

  let tab = await BrowserTestUtils.openNewForegroundTab(
    aWindow.gBrowser,
    TEST_PATH + "file_dummy.html"
  );

  let promiseRounding = TestUtils.topicObserved(
    "test:letterboxing:update-margin-finish"
  );

  let findBarOpenPromise = BrowserTestUtils.waitForEvent(
    aWindow,
    "findbaropen"
  );
  EventUtils.synthesizeKey("F", { accelKey: true }, aWindow);
  await findBarOpenPromise;
  await promiseRounding;

  ok(true, "Margin updated when findbar opened");

  promiseRounding = TestUtils.topicObserved(
    "test:letterboxing:update-margin-finish"
  );

  let findBarClosePromise = BrowserTestUtils.waitForEvent(
    aWindow,
    "findbarclose"
  );
  EventUtils.synthesizeKey("KEY_Escape", {}, aWindow);
  await findBarClosePromise;
  await promiseRounding;

  ok(true, "Margin updated when findbar closed");

  BrowserTestUtils.removeTab(tab);
}

add_setup(async function () {
  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);

  await test_findbar(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 test_findbar(win);

  await BrowserTestUtils.closeWindow(win);
});