summaryrefslogtreecommitdiffstats
path: root/browser/components/resistfingerprinting
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/resistfingerprinting/moz.build16
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser.ini95
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_animationapi_iframes.js235
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_block_mozAddonManager.js43
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_bug1369357_site_specific_zoom_level.js77
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_animation_api.js159
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_performance_api.js171
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_reduce_time_precision.js497
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js397
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_etp_iframes.js126
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutblank.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutsrcdoc.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blob.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blobcrossorigin.js110
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_data.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_sandboxediframe.js96
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups.js76
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_aboutblank.js67
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob.js67
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob_noopener.js81
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data.js67
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data_noopener.js81
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_noopener.js89
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_math.js115
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_navigator.js465
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js392
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_netInfo.js64
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js166
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_performanceAPIWorkers.js79
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_reduceTimePrecision_iframes.js242
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js51
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_roundedWindow_newWindow.js62
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_max_inner.js26
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_mid_inner.js26
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_inner.js20
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js2268
-rw-r--r--browser/components/resistfingerprinting/test/browser/browser_timezone.js154
-rw-r--r--browser/components/resistfingerprinting/test/browser/coop_header.sjs77
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_animationapi_iframee.html85
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_animationapi_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_dummy.html10
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframee.html28
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_popupmaker.html65
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframee.html29
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframee.html35
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html67
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframee.html20
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframer.html29
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframee.html38
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html58
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html29
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html55
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_double_framee.html19
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframee.html25
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframer.html30
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_keyBoardEvent.sjs64
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigator.html33
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigatorWorker.js19
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs12
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigator_iframe_worker.sjs25
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigator_iframee.html60
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html37
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframe_worker.sjs34
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframee.html52
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframer.html31
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_workerNetInfo.js30
-rw-r--r--browser/components/resistfingerprinting/test/browser/file_workerPerformance.js122
-rw-r--r--browser/components/resistfingerprinting/test/browser/head.js1063
-rw-r--r--browser/components/resistfingerprinting/test/browser/shared_test_funcs.js10
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/.eslintrc.js7
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/decode_error.mp4bin0 -> 344124 bytes
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/file_animation_api.html104
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/mochitest.ini29
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_animation_api.html78
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_bug1354633_media_error.html53
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_bug1382499_touch_api.html70
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html43
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_device_sensor_event.html50
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_geolocation.html68
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info.html23
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info_iframe.html45
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_iframe.html18
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_keyboard_event.html61
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_pointer_event.html242
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/test_speech_synthesis.html105
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/worker_child.js28
-rw-r--r--browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js10
92 files changed, 10640 insertions, 0 deletions
diff --git a/browser/components/resistfingerprinting/moz.build b/browser/components/resistfingerprinting/moz.build
new file mode 100644
index 0000000000..cdc8d2e393
--- /dev/null
+++ b/browser/components/resistfingerprinting/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Privacy: Anti-Tracking")
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "test/mochitest/mochitest.ini",
+]
diff --git a/browser/components/resistfingerprinting/test/browser/browser.ini b/browser/components/resistfingerprinting/test/browser/browser.ini
new file mode 100644
index 0000000000..d909b06af6
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser.ini
@@ -0,0 +1,95 @@
+[DEFAULT]
+prefs =
+ # bug 1803611
+ dom.window_position_size_properties_replaceable.enabled=false
+ privacy.resistFingerprinting.testing.setTZtoUTC=false
+tags = resistfingerprinting
+support-files =
+ coop_header.sjs
+ file_dummy.html
+ file_keyBoardEvent.sjs
+ file_navigator.html
+ file_navigatorWorker.js
+ file_workerNetInfo.js
+ file_workerPerformance.js
+ head.js
+ file_navigator_header.sjs
+ file_navigator_iframer.html
+ file_navigator_iframee.html
+ file_navigator_iframe_worker.sjs
+ file_hwconcurrency_aboutblank_iframer.html
+ file_hwconcurrency_aboutblank_iframee.html
+ file_hwconcurrency_aboutblank_popupmaker.html
+ file_hwconcurrency_aboutsrcdoc_iframer.html
+ file_hwconcurrency_aboutsrcdoc_iframee.html
+ file_hwconcurrency_blob_iframer.html
+ file_hwconcurrency_blob_iframee.html
+ file_hwconcurrency_blob_popupmaker.html
+ file_hwconcurrency_blobcrossorigin_iframer.html
+ file_hwconcurrency_blobcrossorigin_iframee.html
+ file_hwconcurrency_data_iframee.html
+ file_hwconcurrency_data_iframer.html
+ file_hwconcurrency_data_popupmaker.html
+ file_hwconcurrency_iframer.html
+ file_hwconcurrency_iframee.html
+ file_hwconcurrency_sandboxediframe_double_framee.html
+ file_hwconcurrency_sandboxediframe_iframer.html
+ file_hwconcurrency_sandboxediframe_iframee.html
+ file_reduceTimePrecision_iframer.html
+ file_reduceTimePrecision_iframee.html
+ file_reduceTimePrecision_iframe_worker.sjs
+ file_animationapi_iframer.html
+ file_animationapi_iframee.html
+ shared_test_funcs.js
+
+[browser_animationapi_iframes.js]
+[browser_block_mozAddonManager.js]
+[browser_bug1369357_site_specific_zoom_level.js]
+https_first_disabled = true
+[browser_cross_origin_isolated_animation_api.js]
+[browser_cross_origin_isolated_performance_api.js]
+[browser_cross_origin_isolated_reduce_time_precision.js]
+[browser_dynamical_window_rounding.js]
+https_first_disabled = true
+skip-if =
+ (os == "mac") #Bug 1570812
+ os == 'linux' && bits == 64 && !debug # Bug 1570812
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_hwconcurrency_etp_iframes.js]
+[browser_hwconcurrency_iframes.js]
+[browser_hwconcurrency_iframes_aboutblank.js]
+[browser_hwconcurrency_iframes_aboutsrcdoc.js]
+[browser_hwconcurrency_iframes_blob.js]
+[browser_hwconcurrency_iframes_blobcrossorigin.js]
+[browser_hwconcurrency_iframes_data.js]
+[browser_hwconcurrency_iframes_sandboxediframe.js]
+[browser_hwconcurrency_popups.js]
+[browser_hwconcurrency_popups_aboutblank.js]
+[browser_hwconcurrency_popups_blob.js]
+[browser_hwconcurrency_popups_blob_noopener.js]
+[browser_hwconcurrency_popups_data.js]
+[browser_hwconcurrency_popups_data_noopener.js]
+[browser_hwconcurrency_popups_noopener.js]
+[browser_math.js]
+[browser_navigator.js]
+https_first_disabled = true
+skip-if =
+ os == "win" && bits == 32 # fails on win10-32
+[browser_navigator_iframes.js]
+https_first_disabled = true
+skip-if =
+ os == "win" && bits == 32 # fails on win10-32 also
+[browser_netInfo.js]
+https_first_disabled = true
+[browser_performanceAPI.js]
+[browser_performanceAPIWorkers.js]
+[browser_reduceTimePrecision_iframes.js]
+https_first_disabled = true
+[browser_roundedWindow_dialogWindow.js]
+[browser_roundedWindow_newWindow.js]
+[browser_roundedWindow_open_max_inner.js]
+[browser_roundedWindow_open_mid_inner.js]
+[browser_roundedWindow_open_min_inner.js]
+[browser_spoofing_keyboard_event.js]
+skip-if = (debug || asan) && os == "linux" && bits == 64 #Bug 1518179
+[browser_timezone.js]
diff --git a/browser/components/resistfingerprinting/test/browser/browser_animationapi_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_animationapi_iframes.js
new file mode 100644
index 0000000000..29080ce3f0
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_animationapi_iframes.js
@@ -0,0 +1,235 @@
+/**
+ * This test only tests values in the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ */
+
+"use strict";
+
+requestLongerTimeout(3);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+});
+
+// =============================================================================================
+// =============================================================================================
+
+async function testTimePrecision(results, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+ let precision = undefined;
+
+ if (!expectedResults.shouldRFPApply) {
+ precision = extraData.Unconditional_Precision;
+ } else {
+ precision = extraData.RFP_Precision;
+ }
+
+ for (let result of results) {
+ if ("error" in result) {
+ ok(false, result.error);
+ continue;
+ }
+
+ let isRounded = isTimeValueRounded(result.value, precision);
+
+ ok(
+ isRounded,
+ "Test: " +
+ testDesc +
+ " - '" +
+ "'" +
+ result.name +
+ "' should be rounded to nearest " +
+ precision +
+ " ms; saw " +
+ result.value
+ );
+ }
+}
+
+const RFP_TIME_ATOM_MS = 16.667;
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_animationapi_iframer.html`;
+
+// The first three variables are defined here; and then set for test banks below.
+let extraData = {};
+let extraPrefs = {};
+let precision = 100;
+let expectedResults = {}; // In this test, we don't have explicit expected values, but rather we expect them to be rounded
+
+// ========================================================================================================================
+// Create a function that defines all the tests
+function addAllTests(extraData_, extraPrefs_) {
+ add_task(
+ defaultsTest.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ add_task(
+ simpleRFPTest.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ add_task(
+ testA.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ add_task(
+ testB.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ add_task(
+ testC.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ add_task(
+ testD.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ add_task(
+ testE.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ add_task(
+ testF.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ add_task(
+ testG.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ add_task(
+ testH.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+}
+
+// ========================================================================================================================
+// First we run through all the tests with RTP's precision set to 100 ms and 133 ms.
+// Animation does _not_ obey RTP's timestamp, instead it falls back to the unconditional
+// rounding which is 20 microseconds.
+extraData = {
+ RFP_Precision: 100,
+ Unconditional_Precision: 0.02,
+};
+
+extraPrefs = [
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ extraData.RFP_Precision * 1000,
+ ],
+ ["dom.animations-api.timelines.enabled", true],
+];
+
+addAllTests(extraData, extraPrefs);
+
+extraData = {
+ RFP_Precision: 133,
+ Unconditional_Precision: 0.02,
+};
+
+extraPrefs = [
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ extraData.RFP_Precision * 1000,
+ ],
+ ["dom.animations-api.timelines.enabled", true],
+];
+
+addAllTests(extraData, extraPrefs);
+
+// ========================================================================================================================
+// Then we run through all the tests with the precision set to its normal value.
+// This will mean that in some cases we expect RFP to apply and in some we don't.
+extraData = {
+ RFP_Precision: RFP_TIME_ATOM_MS,
+ Unconditional_Precision: 0.02,
+};
+extraPrefs = [["dom.animations-api.timelines.enabled", true]];
+addAllTests(extraData, extraPrefs);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_block_mozAddonManager.js b/browser/components/resistfingerprinting/test/browser/browser_block_mozAddonManager.js
new file mode 100644
index 0000000000..e9f55ef667
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_block_mozAddonManager.js
@@ -0,0 +1,43 @@
+/**
+ * Bug 1384330 - A test case for making sure the navigator.mozAddonManager will
+ * be blocked when pref 'privacy.resistFingerprinting.block_mozAddonManager' is true.
+ */
+
+const HTTPS_TEST_PATH =
+ "https://example.com/browser/browser/" +
+ "components/resistfingerprinting/test/browser/";
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.webapi.testing", true]],
+ });
+
+ for (let pref of [false, true]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting.block_mozAddonManager", pref]],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ HTTPS_TEST_PATH + "file_dummy.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [pref], function (aPref) {
+ if (aPref) {
+ is(
+ content.navigator.mozAddonManager,
+ undefined,
+ "The navigator.mozAddonManager should not exist when the pref is on."
+ );
+ } else {
+ ok(
+ content.navigator.mozAddonManager,
+ "The navigator.mozAddonManager should exist when the pref is off."
+ );
+ }
+ });
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+ }
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_bug1369357_site_specific_zoom_level.js b/browser/components/resistfingerprinting/test/browser/browser_bug1369357_site_specific_zoom_level.js
new file mode 100644
index 0000000000..f549c71bf0
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_bug1369357_site_specific_zoom_level.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const PATH_NET = TEST_PATH + "file_dummy.html";
+const PATH_ORG = PATH_NET.replace("example.net", "example.org");
+
+add_task(async function () {
+ let tab1, tab1Zoom, tab2, tab2Zoom, tab3, tab3Zoom;
+
+ tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
+ await FullZoom.enlarge();
+ tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+
+ tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
+ tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+
+ is(
+ tab2Zoom,
+ tab1Zoom,
+ "privacy.resistFingerprinting is false, site-specific zoom level should be enabled"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+
+ tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
+ tab3Zoom = ZoomManager.getZoomForBrowser(tab3.linkedBrowser);
+
+ isnot(
+ tab3Zoom,
+ tab1Zoom,
+ "privacy.resistFingerprinting is true, site-specific zoom level should be disabled"
+ );
+
+ await FullZoom.reset();
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+ BrowserTestUtils.removeTab(tab3);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function exempt_domain() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
+ await FullZoom.enlarge();
+ const netZoomOriginal = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ is(netZoomOriginal, 1.1, "Initial zoom is 110%");
+ await BrowserTestUtils.removeTab(tab);
+
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_ORG);
+ await FullZoom.enlarge();
+ const orgZoomOriginal = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ is(orgZoomOriginal, 1.1, "Initial zoom is 110%");
+ await BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting.exemptedDomains", "example.net"],
+ ["privacy.resistFingerprinting", true],
+ ],
+ });
+
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_NET);
+ const netZoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ is(netZoom, 1.1, "exempted example.net tab should have kept zoom level");
+ await FullZoom.reset();
+ await BrowserTestUtils.removeTab(tab);
+
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PATH_ORG);
+ const orgZoom = ZoomManager.getZoomForBrowser(tab.linkedBrowser);
+ is(orgZoom, 1.0, "example.org tab has its zoom reset to default 100%");
+ await FullZoom.reset();
+ await BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_animation_api.js b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_animation_api.js
new file mode 100644
index 0000000000..f55a492984
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_animation_api.js
@@ -0,0 +1,159 @@
+/**
+ * Bug 1621677 - A test for making sure getting the correct (higher) precision
+ * when it's cross-origin-isolated on animation APIs.
+ */
+
+// ================================================================================================
+// ================================================================================================
+// This test case is mostly copy-and-paste from the test case for window in
+// test_animation_api.html. The main difference is this test case
+// verifies animation has more precsion when it's in cross-origin-isolated and
+// cross-origin-isolated doesn't affect RFP.
+add_task(async function runRTPTestAnimation() {
+ let runTests = async function (data) {
+ function waitForCondition(aCond, aCallback, aErrorMsg) {
+ var tries = 0;
+ var interval = content.setInterval(() => {
+ if (tries >= 30) {
+ ok(false, aErrorMsg);
+ moveOn();
+ return;
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = aCond();
+ } catch (e) {
+ ok(false, `${e}\n${e.stack}`);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = () => {
+ content.clearInterval(interval);
+ aCallback();
+ };
+ }
+
+ let expectedPrecision = data.precision;
+ // eslint beleives that isrounded is available in this scope, but if you
+ // remove the assignment, you will see it is not
+ // eslint-disable-next-line
+ let isRounded = eval(data.isRoundedFunc);
+
+ const testDiv = content.document.getElementById("testDiv");
+ const animation = testDiv.animate({ opacity: [0, 1] }, 100000);
+ animation.play();
+
+ let done;
+ let promise = new Promise(resolve => {
+ done = resolve;
+ });
+
+ waitForCondition(
+ () => animation.currentTime > 100,
+ () => {
+ // We have disabled Time Precision Reduction for CSS Animations, so we
+ // expect those tests to fail.
+ // If we are testing that preference, we accept either rounded or not
+ // rounded values as A-OK.
+ var maybeAcceptEverything = function (value) {
+ if (
+ data.options.reduceTimerPrecision &&
+ !data.options.resistFingerprinting
+ ) {
+ return true;
+ }
+
+ return value;
+ };
+
+ ok(
+ maybeAcceptEverything(
+ isRounded(animation.startTime, expectedPrecision)
+ ),
+ `Animation.startTime with precision ${expectedPrecision} is not ` +
+ `rounded: ${animation.startTime}`
+ );
+ ok(
+ maybeAcceptEverything(
+ isRounded(animation.currentTime, expectedPrecision)
+ ),
+ `Animation.currentTime with precision ${expectedPrecision} is ` +
+ `not rounded: ${animation.currentTime}`
+ );
+ ok(
+ maybeAcceptEverything(
+ isRounded(animation.timeline.currentTime, expectedPrecision)
+ ),
+ `Animation.timeline.currentTime with precision ` +
+ `${expectedPrecision} is not rounded: ` +
+ `${animation.timeline.currentTime}`
+ );
+ if (content.document.timeline) {
+ ok(
+ maybeAcceptEverything(
+ isRounded(
+ content.document.timeline.currentTime,
+ expectedPrecision
+ )
+ ),
+ `Document.timeline.currentTime with precision ` +
+ `${expectedPrecision} is not rounded: ` +
+ `${content.document.timeline.currentTime}`
+ );
+ }
+ done();
+ },
+ "animation failed to start"
+ );
+
+ await promise;
+ };
+
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 100,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ },
+ 50,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ },
+ 0.1,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 0.013,
+ runTests
+ );
+
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 0.005,
+ runTests
+ );
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_performance_api.js b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_performance_api.js
new file mode 100644
index 0000000000..fa53e3a1d5
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_performance_api.js
@@ -0,0 +1,171 @@
+/**
+ * Bug 1621677 - A test for making sure getting the correct (higher) precision
+ * when it's cross-origin-isolated for performance APIs.
+ */
+
+// ================================================================================================
+// ================================================================================================
+// This test case is mostly copy-and-paste from the forth test case in
+// browser_performanceAPI.js. The main difference is this test case verifies
+// performance API have more precsion when it's in cross-origin-isolated and
+// cross-origin-isolated doesn't affect RFP.
+let runWorkerTest = async function (data) {
+ let expectedPrecision = data.precision;
+ await new Promise(resolve => {
+ // eslint beleives that isrounded is available in this scope, but if you
+ // remove the assignment, you will see it is not
+ // eslint-disable-next-line
+ let isRounded = eval(data.isRoundedFunc);
+
+ let worker = new content.Worker(
+ "coop_header.sjs?crossOriginIsolated=true&worker=true"
+ );
+
+ worker.postMessage({
+ type: "runCmdAndGetResult",
+ cmd: `performance.timeOrigin`,
+ });
+
+ const expectedAllEntriesLength = 3;
+ const expectedResourceEntriesLength = 2;
+ const expectedTestAndMarkEntriesLength = 1;
+
+ worker.onmessage = function (e) {
+ if (e.data.type == "result") {
+ if (e.data.resultOf == "performance.timeOrigin") {
+ ok(
+ isRounded(e.data.result, expectedPrecision),
+ `In a worker, performance.timeOrigin is` +
+ ` not correctly rounded: ${e.data.result}`
+ );
+
+ worker.postMessage({
+ type: "runCmds",
+ cmds: [
+ `performance.mark("Test");`,
+ `performance.mark("Test-End");`,
+ `performance.measure("Test-Measure", "Test", "Test-End");`,
+ ],
+ });
+ } else if (e.data.resultOf == "entriesLength") {
+ is(
+ e.data.result,
+ expectedAllEntriesLength,
+ `In a worker: Incorrect number of ` +
+ `entries for performance.getEntries() for workers: ` +
+ `${e.data.result}`
+ );
+
+ worker.postMessage({
+ type: "getResult",
+ resultOf: "startTimeAndDuration",
+ num: 0,
+ });
+ } else if (e.data.resultOf == "startTimeAndDuration") {
+ let index = e.data.result.index;
+ let startTime = e.data.result.startTime;
+ let duration = e.data.result.duration;
+ ok(
+ isRounded(startTime, expectedPrecision),
+ `In a worker, for precision(${expectedPrecision}, ` +
+ `performance.getEntries(${index}).startTime is not rounded: ` +
+ `${startTime}`
+ );
+ ok(
+ isRounded(duration, expectedPrecision),
+ `In a worker, for precision(${expectedPrecision}), ` +
+ `performance.getEntries(${index}).duration is not rounded: ` +
+ `${duration}`
+ );
+
+ if (index < 2) {
+ worker.postMessage({
+ type: "getResult",
+ resultOf: "startTimeAndDuration",
+ num: index + 1,
+ });
+ } else {
+ worker.postMessage({
+ type: "getResult",
+ resultOf: "getEntriesByTypeLength",
+ });
+ }
+ } else if (e.data.resultOf == "entriesByTypeLength") {
+ is(
+ e.data.result.markLength,
+ expectedResourceEntriesLength,
+ `In a worker: Incorrect number of ` +
+ `entries for performance.getEntriesByType() for workers: ` +
+ `${e.data.result.resourceLength}`
+ );
+ is(
+ e.data.result.testAndMarkLength,
+ expectedTestAndMarkEntriesLength,
+ `In a worker: Incorrect number of ` +
+ `entries for performance.getEntriesByName() for workers: ` +
+ `${e.data.result.testAndMarkLength}`
+ );
+
+ worker.terminate();
+ resolve();
+ }
+ } else {
+ ok(false, `Unknown message type got ${e.data.type}`);
+ worker.terminate();
+ resolve();
+ }
+ };
+ });
+};
+
+add_task(async function runTestsForWorker() {
+ // RFP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 100,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ },
+ 13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 0.13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+
+ // RTP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ },
+ 0.13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ },
+ 0.005,
+ runWorkerTest,
+ "runTimerTests"
+ );
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_reduce_time_precision.js b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_reduce_time_precision.js
new file mode 100644
index 0000000000..1366604b4c
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_cross_origin_isolated_reduce_time_precision.js
@@ -0,0 +1,497 @@
+/**
+ * Bug 1621677 - A test for making sure getting the correct (higher) precision
+ * when it's cross-origin-isolated.
+ */
+
+const TEST_SCENARIO_1 = 1;
+const TEST_SCENARIO_2 = 2;
+const TEST_SCENARIO_3 = 3;
+const TEST_SCENARIO_4 = 4;
+const TEST_SCENARIO_5 = 5;
+const TEST_SCENARIO_6 = 6;
+const TEST_SCENARIO_7 = 7;
+const TEST_SCENARIO_8 = 8;
+const TEST_SCENARIO_9 = 9;
+const TEST_SCENARIO_10 = 10;
+const TEST_SCENARIO_11 = 11;
+
+const TEST_SCENARIO_101 = 101;
+const TEST_SCENARIO_102 = 102;
+const TEST_SCENARIO_103 = 103;
+const TEST_SCENARIO_104 = 104;
+const TEST_SCENARIO_105 = 105;
+const TEST_SCENARIO_106 = 106;
+const TEST_SCENARIO_107 = 107;
+const TEST_SCENARIO_108 = 108;
+const TEST_SCENARIO_109 = 109;
+const TEST_SCENARIO_110 = 110;
+const TEST_SCENARIO_111 = 111;
+
+requestLongerTimeout(2);
+
+let processResultsGlobal = (data, successes, failures) => {
+ let expectedPrecision = data.precision;
+ let scenario = data.options.scenario;
+ let shouldBeRounded = data.options.shouldBeRounded;
+ for (let success of successes) {
+ ok(
+ true,
+ (shouldBeRounded ? "Should " : "Should not ") +
+ `have rounded '${success[0]}' to nearest ${expectedPrecision} ms; saw ${success[1]}. ` +
+ `scenario: TEST_SCENARIO_${scenario}`
+ );
+ }
+ if (failures.length > 2) {
+ for (let failure of failures) {
+ ok(
+ false,
+ (shouldBeRounded ? "Should " : "Should not ") +
+ `have rounded '${failure[0]}' to nearest ${expectedPrecision} ms; saw ${failure[1]}. ` +
+ `scenario: TEST_SCENARIO_${scenario}`
+ );
+ }
+ } else if (
+ failures.length == 2 &&
+ expectedPrecision < 10 &&
+ failures[0][0].indexOf("Date().getTime()") > 0 &&
+ failures[1][0].indexOf('File([], "").lastModified') > 0
+ ) {
+ /*
+ * At high precisions, the epoch-based timestamps are large enough that their expected
+ * rounding values lie directly between two integers; and floating point math is imprecise enough
+ * that we need to accept these failures
+ */
+ ok(
+ true,
+ "Two Free Failures that " +
+ (data.options.shouldBeRounded ? "ahould " : "should not ") +
+ `be rounded on the epoch dates and precision: ${expectedPrecision}. ` +
+ `scenario: TEST_SCENARIO_${data.options.scenario}`
+ );
+ } else if (failures.length == 1) {
+ ok(
+ true,
+ "Free Failure: " +
+ (data.options.shouldBeRounded ? "Should " : "Should not ") +
+ `have rounded '${failures[0][0]}' to nearest ${expectedPrecision} ms; saw ${failures[0][1]}. ` +
+ `scenario: TEST_SCENARIO_${data.options.scenario}`
+ );
+ }
+};
+
+// ================================================================================================
+// ================================================================================================
+// This test case is mostly copy-and-paste from the test case for window in
+// test_reduce_time_precision.html. The main difference is this test case
+// verifies DOM API has more precsion when it's in cross-origin-isolated and
+// cross-origin-isolated doesn't affect RFP.
+add_task(async function runRTPTestDOM() {
+ let runTests = async function (data) {
+ let expectedPrecision = data.precision;
+ // eslint beleives that isrounded is available in this scope, but if you
+ // remove the assignment, you will see it is not
+ // eslint-disable-next-line
+ let isRounded = eval(data.isRoundedFunc);
+ // eslint-disable-next-line
+ let processResults = eval(data.options.processResultsFunc);
+
+ // Prepare for test of AudioContext.currentTime
+ // eslint-disable-next-line
+ let audioContext = new content.AudioContext();
+
+ // Known ways to generate time stamps, in milliseconds
+ const timeStampCodes = [
+ "new content.Date().getTime()",
+ 'new content.File([], "").lastModified',
+ "content.performance.now()",
+ 'new content.Event("").timeStamp',
+ ];
+ // These are measured in seconds, so we need to scale them up
+ var timeStampCodesDOM = timeStampCodes.concat([
+ "audioContext.currentTime * 1000",
+ ]);
+
+ // If we are not rounding values, this function will invert the return value
+ let resultSwitchisRounded = function (timeStamp) {
+ if (timeStamp == 0) {
+ return true;
+ }
+ let result = isRounded(timeStamp, expectedPrecision, content.console);
+ return data.options.shouldBeRounded ? result : !result;
+ };
+
+ // It's possible that even if we shouldn't be rounding something, we get a timestamp that is rounded.
+ // If we have only one of these outliers, we are okay with that. This should significantly
+ // reduce intermittents, although it's still likely there will be some intermittents. We can increase
+ // this if we need to
+ let successes = [],
+ failures = [];
+
+ // Loop through each timeStampCode, evaluate it,
+ // and check if it is rounded
+ for (let timeStampCode of timeStampCodesDOM) {
+ // eslint-disable-next-line
+ let timeStamp = eval(timeStampCode);
+
+ // Audio Contexts increment in intervals of (minimum) 5.4ms, so we don't
+ // clamp/jitter if the timer precision is les than that.
+ // (Technically on MBPs they increment in intervals of 2.6 but this is
+ // non-standard and will eventually be changed. We don't cover this situation
+ // because we don't really support arbitrary Timer Precision, especially in
+ // the 2.6 - 5.4ms interval.)
+ if (timeStampCode.includes("audioContext") && expectedPrecision < 5.4) {
+ continue;
+ }
+
+ if (timeStamp != 0 && resultSwitchisRounded(timeStamp)) {
+ successes = successes.concat([[timeStampCode, timeStamp]]);
+ } else if (timeStamp != 0) {
+ failures = failures.concat([[timeStampCode, timeStamp]]);
+ }
+ }
+
+ processResults(data, successes, failures);
+ };
+
+ // RFP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_1,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_2,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_3,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runTests
+ );
+
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_4,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runTests
+ );
+ /*
+ Disabled because it causes too many intermittents
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_5,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runTests
+ );
+ */
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_6,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runTests
+ );
+
+ // We cannot run the tests with too fine a precision, or it becomes very likely
+ // to get false results that a number 'should not been rounded', when it really
+ // wasn't, we had just gotten an accidental match
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_7,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runTests
+ );
+ /*
+ Disabled because it causes too many intermittents
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_8,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runTests
+ );
+ */
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_9,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runTests
+ );
+
+ // RTP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ scenario: TEST_SCENARIO_10,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runTests
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_11,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 0.005,
+ runTests
+ );
+});
+
+// ================================================================================================
+// ================================================================================================
+// This test case is mostly copy-and-paste from the test case for worker in
+// test_reduce_time_precision.html. The main difference is this test case
+// verifies DOM API has more precsion when it's in cross-origin-isolated and
+// cross-origin-isolated doesn't affect RFP.
+let runWorkerTest = async function (data) {
+ let expectedPrecision = data.precision;
+ await new Promise(resolve => {
+ // eslint beleives that isrounded is available in this scope, but if you
+ // remove the assignment, you will see it is not
+ // eslint-disable-next-line
+ let isRounded = eval(data.isRoundedFunc);
+ // eslint-disable-next-line
+ let processResults = eval(data.options.processResultsFunc);
+
+ let worker = new content.Worker(
+ "coop_header.sjs?crossOriginIsolated=true&worker=true"
+ );
+
+ // Known ways to generate time stamps, in milliseconds
+ const timeStampCodes = [
+ "new Date().getTime()",
+ 'new File([], "").lastModified',
+ "performance.now()",
+ 'new Event("").timeStamp',
+ ];
+
+ let promises = [],
+ successes = [],
+ failures = [];
+ for (let timeStampCode of timeStampCodes) {
+ promises.push(
+ new Promise(res => {
+ worker.postMessage({
+ type: "runCmdAndGetResult",
+ cmd: timeStampCode,
+ });
+
+ worker.addEventListener("message", function (e) {
+ // If we are not rounding values, this function will invert the return value
+ let resultSwitchisRounded = function (timeStamp) {
+ if (timeStamp == 0) {
+ return true;
+ }
+ let result = isRounded(timeStamp, expectedPrecision);
+ return data.options.shouldBeRounded ? result : !result;
+ };
+
+ if (e.data.type == "result") {
+ if (e.data.resultOf == timeStampCode) {
+ if (resultSwitchisRounded(e.data.result)) {
+ successes = successes.concat([
+ [timeStampCode, e.data.result],
+ ]);
+ } else {
+ failures = failures.concat([[timeStampCode, e.data.result]]);
+ }
+ worker.removeEventListener("message", this);
+ res();
+ }
+
+ return;
+ }
+
+ ok(false, `Unknown message type. Got ${e.data.type}`);
+ res();
+ });
+ })
+ );
+ }
+
+ Promise.all(promises).then(_ => {
+ worker.terminate();
+ processResults(data, successes, failures);
+ resolve();
+ });
+ });
+};
+
+add_task(async function runRTPTestsForWorker() {
+ // RFP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_101,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runWorkerTest
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_102,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runWorkerTest
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_103,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 100,
+ runWorkerTest
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_104,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runWorkerTest
+ );
+ /*
+ Disabled because it causes too many intermittents
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_105,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runWorkerTest
+ );
+ */
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_106,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 13,
+ runWorkerTest
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprinting: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_107,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runWorkerTest
+ );
+ /* Disabled because it causes too many intermittents
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ crossOriginIsolated: true,
+ shouldBeRounded: false,
+ scenario: TEST_SCENARIO_108,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runWorkerTest
+ );
+ */
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ resistFingerprintingPBMOnly: true,
+ openPrivateWindow: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_109,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runWorkerTest
+ );
+
+ // RTP
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ scenario: TEST_SCENARIO_110,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 7.97,
+ runWorkerTest
+ );
+ await setupAndRunCrossOriginIsolatedTest(
+ {
+ reduceTimerPrecision: true,
+ crossOriginIsolated: true,
+ scenario: TEST_SCENARIO_111,
+ processResultsFunc: processResultsGlobal.toString(),
+ },
+ 0.005,
+ runWorkerTest
+ );
+});
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 0000000000..13dcec8ea1
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_dynamical_window_rounding.js
@@ -0,0 +1,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);
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_etp_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_etp_iframes.js
new file mode 100644
index 0000000000..47914e098e
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_etp_iframes.js
@@ -0,0 +1,126 @@
+/**
+ * This test only tests values in the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely in normal and PBM
+ * - FPP is enabled entirely in normal and PBM
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html?mode=iframe`;
+
+requestLongerTimeout(2);
+
+let extraData = {
+ etp_reload: true,
+};
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ defaultsTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+// The ETP toggle _does not_ override RFP, only FPP.
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simplePBMRFPTest.bind(
+ null,
+ uri,
+ testHWConcurrency,
+ expectedResults,
+ extraData
+ )
+);
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ simplePBMFPPTest.bind(
+ null,
+ uri,
+ testHWConcurrency,
+ expectedResults,
+ extraData
+ )
+);
+
+// These test cases exercise the unusual configuration of RFP enabled in PBM and FPP enabled
+// in normal browsing mode.
+let extraPrefs = [
+ ["privacy.resistFingerprinting.pbmode", true],
+ ["privacy.fingerprintingProtection", true],
+ ["privacy.fingerprintingProtection.overrides", "+HardwareConcurrency"],
+];
+
+let this_extraData = structuredClone(extraData);
+this_extraData.testDesc =
+ "RFP enabled in PBM, FPP enabled globally, testing in normal browsing mode";
+expectedResults = allNotSpoofed;
+add_task(
+ defaultsTest.bind(
+ null,
+ uri,
+ testHWConcurrency,
+ structuredClone(expectedResults),
+ this_extraData,
+ structuredClone(extraPrefs)
+ )
+);
+
+this_extraData = structuredClone(extraData);
+this_extraData.testDesc =
+ "RFP enabled in PBM, FPP enabled globally, testing in PBM";
+this_extraData.private_window = true;
+expectedResults = allSpoofed;
+add_task(
+ defaultsTest.bind(
+ null,
+ uri,
+ testHWConcurrency,
+ structuredClone(expectedResults),
+ this_extraData,
+ structuredClone(extraPrefs)
+ )
+);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes.js
new file mode 100644
index 0000000000..ec69d436c8
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html?mode=iframe`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutblank.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutblank.js
new file mode 100644
index 0000000000..fd76d52da1
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutblank.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in an about:blank document that is created by the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutsrcdoc.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutsrcdoc.js
new file mode 100644
index 0000000000..62c2363cd2
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_aboutsrcdoc.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in an about:srcdoc document that is created by the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blob.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blob.js
new file mode 100644
index 0000000000..6ab0be2b9a
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blob.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in a blob document that is created by the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blobcrossorigin.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blobcrossorigin.js
new file mode 100644
index 0000000000..6a643000bd
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_blobcrossorigin.js
@@ -0,0 +1,110 @@
+/**
+ * This test only tests values in a blob document that is created by the iframe on one domain, then passed
+ * to a cross-origin domain to embed.
+ * It is a bit different from all the other tests in this series, because instead of the framer doing nothing
+ * except frame the framee; the framer creates the blob document, and the framee embeds the blob document.
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+// In theory this should be Not Spoofed, however, in this test there is a blob: document that
+// has a content principal and a reference to the iframe's parent (when Fission is disabled anyway.)
+// The blob's principal does not match the parent's principal, so it is up to the blob to determine
+// if it should resist fingerprinting on its own.
+// It decides _not_ to resist fingerprinting, but only because nsContentUtils::IsURIInList has
+// a check `if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { return false; }`
+// We could in theory, modify that check to check the blob's creation uri, and that would work.
+// But I am nervous about changing that code.
+// expectedResults = structuredClone(allNotSpoofed);
+expectedResults = structuredClone(allSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+// Same as A above.
+//expectedResults = structuredClone(allNotSpoofed);
+expectedResults = structuredClone(allSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_data.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_data.js
new file mode 100644
index 0000000000..72d5ec9ba0
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_data.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in a data document that is created by the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_sandboxediframe.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_sandboxediframe.js
new file mode 100644
index 0000000000..684ae7ba5a
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_iframes_sandboxediframe.js
@@ -0,0 +1,96 @@
+/**
+ * This test only tests values in a sandboxed iframe that is created by a parent iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testD.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups.js
new file mode 100644
index 0000000000..13e7147236
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups.js
@@ -0,0 +1,76 @@
+/**
+ * This test tests values in a popup, it does not test them on the page that made the popup
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the maker and popup
+ * - (C) RFP is exempted on the maker but not the popup
+ * - (E) RFP is not exempted on the maker nor the popup
+ * - (G) RFP is not exempted on the maker but is on the popup
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html?mode=popup`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the maker and popup
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (C) RFP is exempted on the maker but not the popup
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the maker nor the popup
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (G) RFP is not exempted on the maker but is on the popup
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_aboutblank.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_aboutblank.js
new file mode 100644
index 0000000000..982f256e76
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_aboutblank.js
@@ -0,0 +1,67 @@
+/**
+ * This test only tests values in an about:blank document that opened in a popup
+ * Because there is no interaction with a third party domain, there's a lot fewer tests
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the popup maker
+ * - (E) RFP is not exempted on the popup maker
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_popupmaker.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the popup maker
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the popup maker
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob.js
new file mode 100644
index 0000000000..7a81b9980c
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob.js
@@ -0,0 +1,67 @@
+/**
+ * This test only tests values in a blob document that is opened in a popup
+ * Because there is no interaction with a third party domain, there's a lot fewer tests
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the popup maker
+ * - (E) RFP is not exempted on the popup maker
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the popup maker
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the popup maker
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob_noopener.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob_noopener.js
new file mode 100644
index 0000000000..a8046e00d1
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_blob_noopener.js
@@ -0,0 +1,81 @@
+/**
+ * This test only tests values in a blob document that is opened in a popup
+ * Because there is no interaction with a third party domain, there's a lot fewer tests
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the popup maker
+ * - (E) RFP is not exempted on the popup maker
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html?submode=noopener`;
+const await_uri = loadedURL => loadedURL.startsWith("blob:");
+
+requestLongerTimeout(2);
+
+let extraData = {
+ noopener: true,
+ await_uri,
+};
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ defaultsTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+// (A) RFP is exempted on the popup maker
+// Ordinarily, RFP would be exempted, however because the opener relationship is severed
+// there is nothing to grant it an exemption, so it is not exempted.
+expectedResults = structuredClone(allSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults, extraData));
+
+// (E) RFP is not exempted on the popup maker
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults, extraData));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data.js
new file mode 100644
index 0000000000..175d4c57af
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data.js
@@ -0,0 +1,67 @@
+/**
+ * This test only tests values in a data document that is opened in a popup
+ * Because there is no interaction with a third party domain, there's a lot fewer tests
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the popup maker
+ * - (E) RFP is not exempted on the popup maker
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (A) RFP is exempted on the popup maker
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
+
+// (E) RFP is not exempted on the popup maker
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data_noopener.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data_noopener.js
new file mode 100644
index 0000000000..99d1a82937
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_data_noopener.js
@@ -0,0 +1,81 @@
+/**
+ * This test only tests values in a data document that is opened in a popup with noopener
+ * Because there is no interaction with a third party domain, there's a lot fewer tests
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+
+ *
+ * - (A) RFP is exempted on the popup maker
+ * - (E) RFP is not exempted on the popup maker
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html?submode=noopener`;
+const await_uri = loadedURL => loadedURL.startsWith("data:");
+
+requestLongerTimeout(2);
+
+let extraData = {
+ noopener: true,
+ await_uri,
+};
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ defaultsTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+// (A) RFP is exempted on the popup maker
+// Ordinarily, RFP would be exempted, however because the opener relationship is severed
+// there is nothing to grant it an exemption, so it is not exempted.
+expectedResults = structuredClone(allSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults, extraData));
+
+// (E) RFP is not exempted on the popup maker
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults, extraData));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_noopener.js b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_noopener.js
new file mode 100644
index 0000000000..f27c625356
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_hwconcurrency_popups_noopener.js
@@ -0,0 +1,89 @@
+/**
+ * This test tests values in a popup that is opened with noopener, it does not test them on the page that made the popup
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ * - FPP is enabled entirely
+ *
+ * - (A) RFP is exempted on the maker and popup
+ * - (C) RFP is exempted on the maker but not the popup
+ * - (E) RFP is not exempted on the maker nor the popup
+ * - (G) RFP is not exempted on the maker but is on the popup
+ *
+ */
+
+"use strict";
+
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testHWConcurrency(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+}
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ hardwareConcurrency: DEFAULT_HARDWARE_CONCURRENCY,
+};
+const allSpoofed = {
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html?mode=popup&submode=noopener`;
+const await_uri = `https://${IFRAME_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html?mode=popup`;
+
+requestLongerTimeout(2);
+
+let extraData = {
+ noopener: true,
+ await_uri,
+};
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(
+ defaultsTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleRFPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+expectedResults = structuredClone(allSpoofed);
+add_task(
+ simpleFPPTest.bind(null, uri, testHWConcurrency, expectedResults, extraData)
+);
+
+// (A) RFP is exempted on the maker and popup
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testHWConcurrency, expectedResults, extraData));
+
+// (C) RFP is exempted on the maker but not the popup
+expectedResults = structuredClone(allSpoofed);
+add_task(testC.bind(null, uri, testHWConcurrency, expectedResults, extraData));
+
+// (E) RFP is not exempted on the maker nor the popup
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testHWConcurrency, expectedResults, extraData));
+
+// (G) RFP is not exempted on the maker but is on the popup
+// Ordinarily, RFP would not be exempted, however because the opener relationship is severed
+// it is safe to exempt the popup
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testG.bind(null, uri, testHWConcurrency, expectedResults, extraData));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_math.js b/browser/components/resistfingerprinting/test/browser/browser_math.js
new file mode 100644
index 0000000000..1d10ad9888
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_math.js
@@ -0,0 +1,115 @@
+/**
+ * Bug 531915 - A test for verifying that the JS Math fingerprint is constant
+ * when using fdlibm for Math.sin, Math.cos, and Math.tan.
+ */
+
+async function test_math(rfp_pref, fdlibm_pref) {
+ info(
+ `privacy.resistFingerprinting: ${rfp_pref} javascript.options.use_fdlibm_for_sin_cos_tan: ${fdlibm_pref}`
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["javascript.options.use_fdlibm_for_sin_cos_tan", fdlibm_pref],
+ ["privacy.resistFingerprinting", rfp_pref],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ // prettier-ignore
+ function test() {
+ //
+ // Tests adapted from https://github.com/arkenfox/TZP/blob/5e3f5ff2c64b4edc7beecd8308aa4f7a3efb49e3/tests/math.html#L158-L319
+ //
+ is(Math.cos(1e251), -0.37419577499634155, "Math.cos(1e251)");
+ is(Math.cos(1e140), -0.7854805190645291, "Math.cos(1e140)");
+ is(Math.cos(1e12), 0.7914463018528902, "Math.cos(1e12)");
+ is(Math.cos(1e130), -0.767224894221913, "Math.cos(1e130)");
+ is(Math.cos(1e272), -0.7415825695514536, "Math.cos(1e272)");
+ is(Math.cos(1), 0.5403023058681398, "Math.cos(1)");
+ is(Math.cos(1e284), 0.7086865671674247, "Math.cos(1e284)");
+ is(Math.cos(1e75), -0.7482651726250321, "Math.cos(1e75)");
+ is(Math.cos(Math.PI), -1, "Math.cos(Math.PI)");
+ is(Math.cos(-1e308), -0.8913089376870335, "Math.cos(-1e308)");
+ is(Math.cos(13 * Math.E), -0.7108118501064332, "Math.cos(13 * Math.E)");
+ is(Math.cos(57 * Math.E), -0.536911695749024, "Math.cos(57 * Math.E)");
+ is(Math.cos(21 * Math.LN2), -0.4067775970251724, "Math.cos(21 * Math.LN2)");
+ is(Math.cos(51 * Math.LN2), -0.7017203400855446, "Math.cos(51 * Math.LN2)");
+ is(Math.cos(21 * Math.LOG2E), 0.4362848063618998, "Math.cos(21 * Math.LOG2E)");
+ is(Math.cos(25 * Math.SQRT2), -0.6982689820462377, "Math.cos(25 * Math.SQRT2)");
+ is(Math.cos(50 * Math.SQRT1_2), -0.6982689820462377, "Math.cos(50 * Math.SQRT1_2)");
+ is(Math.cos(21 * Math.SQRT1_2), -0.6534063185820198, "Math.cos(21 * Math.SQRT1_2)");
+ is(Math.cos(17 * Math.LOG10E), 0.4537557425982784, "Math.cos(17 * Math.LOG10E)");
+ is(Math.cos(2 * Math.LOG10E), 0.6459044007438142, "Math.cos(2 * Math.LOG10E)");
+
+ is(Math.sin(1e251), -0.9273497301314576, "Math.sin(1e251)");
+ is(Math.sin(1e140), -0.6188863822787813, "Math.sin(1e140)");
+ is(Math.sin(1e12), -0.6112387023768895, "Math.sin(1e12)");
+ is(Math.sin(1e130), 0.6413781736901984, "Math.sin(1e130)");
+ is(Math.sin(1e272), 0.6708616046081811, "Math.sin(1e272)");
+ is(Math.sin(1), 0.8414709848078965, "Math.sin(1)");
+ is(Math.sin(1e284), -0.7055234578073583, "Math.sin(1e284)");
+ is(Math.sin(1e75), 0.66339975236386, "Math.sin(1e75)");
+ is(Math.sin(Math.PI), 1.2246467991473532e-16, "Math.sin(Math.PI)");
+ is(Math.sin(39 * Math.E), -0.7181630308570678, "Math.sin(39 * Math.E)");
+ is(Math.sin(35 * Math.LN2), -0.765996413898051, "Math.sin(35 * Math.LN2)");
+ is(Math.sin(110 * Math.LOG2E), 0.9989410140273757, "Math.sin(110 * Math.LOG2E)");
+ is(Math.sin(7 * Math.LOG10E), 0.10135692924965616, "Math.sin(7 * Math.LOG10E)");
+ is(Math.sin(35 * Math.SQRT1_2), -0.3746357547858202, "Math.sin(35 * Math.SQRT1_2)");
+ is(Math.sin(21 * Math.SQRT2), -0.9892668187780497, "Math.sin(21 * Math.SQRT2)");
+
+ is(Math.tan(1e251), 2.478247463217681, "Math.tan(1e251)");
+ is(Math.tan(1e140), 0.7879079967710036, "Math.tan(1e140)");
+ is(Math.tan(1e12), -0.7723059681318761, "Math.tan(1e12)");
+ is(Math.tan(1e130), -0.8359715365344825, "Math.tan(1e130)");
+ is(Math.tan(1e272), -0.904635076595654, "Math.tan(1e272)");
+ is(Math.tan(1), 1.5574077246549023, "Math.tan(1)");
+ is(Math.tan(1e284), -0.9955366596368418, "Math.tan(1e284)");
+ is(Math.tan(1e75), -0.8865837628611647, "Math.tan(1e75)");
+ is(Math.tan(-1e308), 0.5086861259107568, "Math.tan(-1e308)");
+ is(Math.tan(Math.PI), -1.2246467991473532e-16, "Math.tan(Math.PI)");
+ is(Math.tan(6 * Math.E), 0.6866761546452431, "Math.tan(6 * Math.E)");
+ is(Math.tan(6 * Math.LN2), 1.6182817135715877, "Math.tan(6 * Math.LN2)");
+ is(Math.tan(10 * Math.LOG2E), -3.3537128705376014, "Math.tan(10 * Math.LOG2E)");
+ is(Math.tan(17 * Math.SQRT2), -1.9222955461799982, "Math.tan(17 * Math.SQRT2)");
+ is(Math.tan(34 * Math.SQRT1_2), -1.9222955461799982, "Math.tan(34 * Math.SQRT1_2)");
+ is(Math.tan(10 * Math.LOG10E), 2.5824856130712432, "Math.tan(10 * Math.LOG10E)");
+
+ //
+ // Tests adapted from https://github.com/fingerprintjs/fingerprintjs/blob/7096a5589af495f1f46067963e13ad27d887d185/src/sources/math.ts#L32-L64
+ //
+ is(Math.acos(0.123124234234234242), 1.4473588658278522, "Math.acos(0.123124234234234242)");
+ is(Math.acosh(1e308), 709.889355822726, "Math.acosh(1e308)");
+ is(Math.asin(0.123124234234234242), 0.12343746096704435, "Math.asin(0.123124234234234242)");
+ is(Math.asinh(1), 0.881373587019543, "Math.asinh(1)");
+ is(Math.atanh(0.5), 0.5493061443340548, "Math.atanh(0.5)");
+ is(Math.atan(0.5), 0.4636476090008061, "Math.atan(0.5)");
+ is(Math.sin(-1e300), 0.8178819121159085, "Math.sin(-1e300)");
+ is(Math.sinh(1), 1.1752011936438014, "Math.sinh(1)");
+ is(Math.cos(10.000000000123), -0.8390715290095377, "Math.cos(10.000000000123)");
+ is(Math.cosh(1), 1.5430806348152437, "Math.cosh(1)");
+ is(Math.tan(-1e300), -1.4214488238747245, "Math.tan(-1e300)");
+ is(Math.tanh(1), 0.7615941559557649, "Math.tanh(1)");
+ is(Math.exp(1), 2.718281828459045, "Math.exp(1)");
+ is(Math.expm1(1), 1.718281828459045, "Math.expm1(1)");
+ is(Math.log1p(10), 2.3978952727983707, "Math.log1p(10)");
+ }
+
+ // Run test in the context of the page.
+ Cu.exportFunction(is, content, { defineAs: "is" });
+ content.eval(`(${test})()`);
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+add_task(test_math.bind(null, false, true));
+add_task(test_math.bind(null, true, false));
+add_task(test_math.bind(null, true, true));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_navigator.js b/browser/components/resistfingerprinting/test/browser/browser_navigator.js
new file mode 100644
index 0000000000..9cb0aa9e8d
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_navigator.js
@@ -0,0 +1,465 @@
+/**
+ * Bug 1333651 - A test case for making sure the navigator object has been
+ * spoofed/disabled correctly.
+ */
+
+"use strict";
+
+const CC = Components.Constructor;
+
+ChromeUtils.defineESModuleGetters(this, {
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+});
+
+let expectedResults;
+
+let osVersion = Services.sysinfo.get("version");
+if (AppConstants.platform == "macosx") {
+ // Convert Darwin version to macOS version: 19.x.x -> 10.15 etc.
+ // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29
+ let DarwinVersionParts = osVersion.split(".");
+ let DarwinMajorVersion = +DarwinVersionParts[0];
+ let macOsMinorVersion = DarwinMajorVersion - 4;
+ if (macOsMinorVersion > 15) {
+ macOsMinorVersion = 15;
+ }
+ osVersion = `10.${macOsMinorVersion}`;
+}
+
+const DEFAULT_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: `5.0 (Android ${osVersion})`,
+ other: "5.0 (X11)",
+};
+
+const SPOOFED_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: "5.0 (Android 10)",
+ other: "5.0 (X11)",
+};
+
+let cpuArch = Services.sysinfo.get("arch");
+if (cpuArch == "x86-64") {
+ // Convert CPU arch "x86-64" to "x86_64" used in Linux and Android UAs.
+ cpuArch = "x86_64";
+}
+
+const DEFAULT_PLATFORM = {
+ linux: `Linux ${cpuArch}`,
+ win: "Win32",
+ macosx: "MacIntel",
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_PLATFORM = {
+ linux: "Linux x86_64",
+ win: "Win32",
+ macosx: "MacIntel",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+// If comparison with the WindowsOscpu value fails in the future, it's time to
+// evaluate if exposing a new Windows version to the Web is appropriate. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1693295
+let WindowsOscpu = null;
+if (AppConstants.platform == "win") {
+ let isWin11 = WindowsVersionInfo.get().buildNumber >= 22000;
+ WindowsOscpu =
+ cpuArch == "x86_64" || (cpuArch == "aarch64" && isWin11)
+ ? `Windows NT ${osVersion}; Win64; x64`
+ : `Windows NT ${osVersion}`;
+}
+
+const DEFAULT_OSCPU = {
+ linux: `Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Intel Mac OS X ${osVersion}`,
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_OSCPU = {
+ linux: "Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Intel Mac OS X 10.15",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+const DEFAULT_UA_OS = {
+ linux: `X11; Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Macintosh; Intel Mac OS X ${osVersion}`,
+ android: `Android ${osVersion}; Mobile`,
+ other: `X11; Linux ${cpuArch}`,
+};
+
+const SPOOFED_UA_NAVIGATOR_OS = {
+ linux: "X11; Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Macintosh; Intel Mac OS X 10.15",
+ android: "Android 10; Mobile",
+ other: "X11; Linux x86_64",
+};
+const SPOOFED_UA_HTTPHEADER_OS = {
+ linux: "Windows NT 10.0",
+ win: "Windows NT 10.0",
+ macosx: "Windows NT 10.0",
+ android: "Android 10; Mobile",
+ other: "Windows NT 10.0",
+};
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const CONST_APPCODENAME = "Mozilla";
+const CONST_APPNAME = "Netscape";
+const CONST_PRODUCT = "Gecko";
+const CONST_PRODUCTSUB = "20100101";
+const CONST_VENDOR = "";
+const CONST_VENDORSUB = "";
+
+const appVersion = parseInt(Services.appinfo.version);
+const rvVersion =
+ parseInt(
+ Services.prefs.getIntPref("network.http.useragent.forceRVOnly", 0),
+ 0
+ ) || appVersion;
+const spoofedVersion = AppConstants.platform == "android" ? "115" : appVersion;
+
+const LEGACY_UA_GECKO_TRAIL = "20100101";
+
+const DEFAULT_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${appVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+const SPOOFED_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${spoofedVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+async function testUserAgentHeader() {
+ const BASE =
+ "http://mochi.test:8888/browser/browser/components/resistfingerprinting/test/browser/";
+ const TEST_TARGET_URL = `${BASE}file_navigator_header.sjs?`;
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_TARGET_URL
+ );
+
+ let result = await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ return content.document.body.textContent;
+ });
+
+ is(
+ result,
+ expectedResults.userAgentHeader,
+ `Checking ${expectedResults.testDesc} User Agent HTTP Header.`
+ );
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testNavigator() {
+ // Open a tab to collect result.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_navigator.html"
+ );
+
+ let result = await SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ return content.document.getElementById("result").innerHTML;
+ });
+
+ result = JSON.parse(result);
+
+ let testDesc = expectedResults.testDesc;
+
+ is(
+ result.appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} navigator.appVersion.`
+ );
+ is(
+ result.platform,
+ expectedResults.platform,
+ `Checking ${testDesc} navigator.platform.`
+ );
+ is(
+ result.userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} navigator.userAgent.`
+ );
+ is(
+ result.mimeTypesLength,
+ expectedResults.mimeTypesLength,
+ `Navigator.mimeTypes has a length of ${expectedResults.mimeTypesLength}.`
+ );
+ is(
+ result.pluginsLength,
+ expectedResults.pluginsLength,
+ `Navigator.plugins has a length of ${expectedResults.pluginsLength}.`
+ );
+ is(
+ result.oscpu,
+ expectedResults.oscpu,
+ `Checking ${testDesc} navigator.oscpu.`
+ );
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.appCodeName,
+ CONST_APPCODENAME,
+ "Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.appName,
+ CONST_APPNAME,
+ "Navigator.appName reports correct constant value."
+ );
+ is(
+ result.product,
+ CONST_PRODUCT,
+ "Navigator.product reports correct constant value."
+ );
+ is(
+ result.productSub,
+ CONST_PRODUCTSUB,
+ "Navigator.productSub reports correct constant value."
+ );
+ is(
+ result.vendor,
+ CONST_VENDOR,
+ "Navigator.vendor reports correct constant value."
+ );
+ is(
+ result.vendorSub,
+ CONST_VENDORSUB,
+ "Navigator.vendorSub reports correct constant value."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testWorkerNavigator() {
+ // Open a tab to collect result from worker.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ let result = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ let worker = new content.SharedWorker(
+ "file_navigatorWorker.js",
+ "WorkerNavigatorTest"
+ );
+
+ let res = await new Promise(resolve => {
+ worker.port.onmessage = function (e) {
+ resolve(e.data);
+ };
+ });
+
+ return res;
+ }
+ );
+
+ result = JSON.parse(result);
+
+ let testDesc = expectedResults.testDesc;
+
+ is(
+ result.appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} worker navigator.appVersion.`
+ );
+ is(
+ result.platform,
+ expectedResults.platform,
+ `Checking ${testDesc} worker navigator.platform.`
+ );
+ is(
+ result.userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} worker navigator.userAgent.`
+ );
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} worker navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.appCodeName,
+ CONST_APPCODENAME,
+ "worker Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.appName,
+ CONST_APPNAME,
+ "worker Navigator.appName reports correct constant value."
+ );
+ is(
+ result.product,
+ CONST_PRODUCT,
+ "worker Navigator.product reports correct constant value."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Ensure the content process is shut down entirely before we start the next
+ // test in Fission.
+ if (SpecialPowers.useRemoteSubframes) {
+ await new Promise(resolve => {
+ let observer = (subject, topic, data) => {
+ if (topic === "ipc:content-shutdown") {
+ Services.obs.removeObserver(observer, "ipc:content-shutdown");
+ resolve();
+ }
+ };
+ Services.obs.addObserver(observer, "ipc:content-shutdown");
+ });
+ }
+}
+
+add_task(async function setupDefaultUserAgent() {
+ let defaultUserAgent = `Mozilla/5.0 (${
+ DEFAULT_UA_OS[AppConstants.platform]
+ }; rv:${rvVersion}.0) Gecko/${
+ DEFAULT_UA_GECKO_TRAIL[AppConstants.platform]
+ } Firefox/${appVersion}.0`;
+ expectedResults = {
+ testDesc: "default",
+ appVersion: DEFAULT_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: navigator.hardwareConcurrency,
+ mimeTypesLength: 2,
+ oscpu: DEFAULT_OSCPU[AppConstants.platform],
+ platform: DEFAULT_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: defaultUserAgent,
+ userAgentHeader: defaultUserAgent,
+ };
+
+ await testNavigator();
+
+ await testUserAgentHeader();
+
+ await testWorkerNavigator();
+});
+
+add_task(async function setupRFPExemptions() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ "example.net, mochi.test",
+ ],
+ ],
+ });
+
+ let defaultUserAgent = `Mozilla/5.0 (${
+ DEFAULT_UA_OS[AppConstants.platform]
+ }; rv:${rvVersion}.0) Gecko/${
+ DEFAULT_UA_GECKO_TRAIL[AppConstants.platform]
+ } Firefox/${appVersion}.0`;
+
+ expectedResults = {
+ testDesc: "RFP Exempted Domain",
+ appVersion: DEFAULT_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: navigator.hardwareConcurrency,
+ mimeTypesLength: 2,
+ oscpu: DEFAULT_OSCPU[AppConstants.platform],
+ platform: DEFAULT_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: defaultUserAgent,
+ userAgentHeader: defaultUserAgent,
+ };
+
+ await testNavigator();
+
+ await testUserAgentHeader();
+
+ await testWorkerNavigator();
+
+ // Pop exempted domains
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function setupResistFingerprinting() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+
+ let spoofedGeckoTrail = SPOOFED_UA_GECKO_TRAIL[AppConstants.platform];
+
+ let spoofedUserAgentNavigator = `Mozilla/5.0 (${
+ SPOOFED_UA_NAVIGATOR_OS[AppConstants.platform]
+ }; rv:${rvVersion}.0) Gecko/${spoofedGeckoTrail} Firefox/${appVersion}.0`;
+
+ let spoofedUserAgentHeader = `Mozilla/5.0 (${
+ SPOOFED_UA_HTTPHEADER_OS[AppConstants.platform]
+ }; rv:${rvVersion}.0) Gecko/${spoofedGeckoTrail} Firefox/${appVersion}.0`;
+
+ expectedResults = {
+ testDesc: "spoofed",
+ appVersion: SPOOFED_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+ mimeTypesLength: 2,
+ oscpu: SPOOFED_OSCPU[AppConstants.platform],
+ platform: SPOOFED_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: spoofedUserAgentNavigator,
+ userAgentHeader: spoofedUserAgentHeader,
+ };
+
+ await testNavigator();
+
+ await testUserAgentHeader();
+
+ await testWorkerNavigator();
+});
+
+// This tests that 'general.*.override' should not override spoofed values.
+add_task(async function runOverrideTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["general.appname.override", "appName overridden"],
+ ["general.appversion.override", "appVersion overridden"],
+ ["general.platform.override", "platform overridden"],
+ ["general.useragent.override", "userAgent overridden"],
+ ["general.oscpu.override", "oscpu overridden"],
+ ],
+ });
+
+ await testNavigator();
+
+ await testWorkerNavigator();
+
+ await testUserAgentHeader();
+
+ // Pop general.appname.override etc
+ await SpecialPowers.popPrefEnv();
+
+ // Pop privacy.resistFingerprinting
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js
new file mode 100644
index 0000000000..6d6e836cd4
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_navigator_iframes.js
@@ -0,0 +1,392 @@
+/**
+ * Bug 1737829 and Bug 1770498 - A test case for making sure the navigator object has been
+ * spoofed/disabled correctly respecting cross-origin resources, iframes
+ * and exemption behavior.
+ *
+ * This test only tests values in the iframe, it does not test them on the framer
+ * We use the cross-origin domain as the base URI of a resource we fetch (on both the framer and framee)
+ * so we can check that the HTTP header is as expected.
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ WindowsVersionInfo:
+ "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
+});
+
+let osVersion = Services.sysinfo.get("version");
+if (AppConstants.platform == "macosx") {
+ // Convert Darwin version to macOS version: 19.x.x -> 10.15 etc.
+ // https://en.wikipedia.org/wiki/Darwin_%28operating_system%29
+ let DarwinVersionParts = osVersion.split(".");
+ let DarwinMajorVersion = +DarwinVersionParts[0];
+ let macOsMinorVersion = DarwinMajorVersion - 4;
+ if (macOsMinorVersion > 15) {
+ macOsMinorVersion = 15;
+ }
+ osVersion = `10.${macOsMinorVersion}`;
+}
+
+const DEFAULT_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: `5.0 (Android ${osVersion})`,
+ other: "5.0 (X11)",
+};
+
+const SPOOFED_APPVERSION = {
+ linux: "5.0 (X11)",
+ win: "5.0 (Windows)",
+ macosx: "5.0 (Macintosh)",
+ android: "5.0 (Android 10)",
+ other: "5.0 (X11)",
+};
+
+let cpuArch = Services.sysinfo.get("arch");
+if (cpuArch == "x86-64") {
+ // Convert CPU arch "x86-64" to "x86_64" used in Linux and Android UAs.
+ cpuArch = "x86_64";
+}
+
+const DEFAULT_PLATFORM = {
+ linux: `Linux ${cpuArch}`,
+ win: "Win32",
+ macosx: "MacIntel",
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_PLATFORM = {
+ linux: "Linux x86_64",
+ win: "Win32",
+ macosx: "MacIntel",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+// If comparison with the WindowsOscpu value fails in the future, it's time to
+// evaluate if exposing a new Windows version to the Web is appropriate. See
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1693295
+let WindowsOscpu = null;
+if (AppConstants.platform == "win") {
+ let isWin11 = WindowsVersionInfo.get().buildNumber >= 22000;
+ WindowsOscpu =
+ cpuArch == "x86_64" || (cpuArch == "aarch64" && isWin11)
+ ? `Windows NT ${osVersion}; Win64; x64`
+ : `Windows NT ${osVersion}`;
+}
+
+const DEFAULT_OSCPU = {
+ linux: `Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Intel Mac OS X ${osVersion}`,
+ android: `Linux ${cpuArch}`,
+ other: `Linux ${cpuArch}`,
+};
+
+const SPOOFED_OSCPU = {
+ linux: "Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Intel Mac OS X 10.15",
+ android: "Linux aarch64",
+ other: "Linux x86_64",
+};
+
+const DEFAULT_UA_OS = {
+ linux: `X11; Linux ${cpuArch}`,
+ win: WindowsOscpu,
+ macosx: `Macintosh; Intel Mac OS X ${osVersion}`,
+ android: `Android ${osVersion}; Mobile`,
+ other: `X11; Linux ${cpuArch}`,
+};
+
+const SPOOFED_UA_NAVIGATOR_OS = {
+ linux: "X11; Linux x86_64",
+ win: "Windows NT 10.0; Win64; x64",
+ macosx: "Macintosh; Intel Mac OS X 10.15",
+ android: "Android 10; Mobile",
+ other: "X11; Linux x86_64",
+};
+const SPOOFED_UA_HTTPHEADER_OS = {
+ linux: "Windows NT 10.0",
+ win: "Windows NT 10.0",
+ macosx: "Windows NT 10.0",
+ android: "Android 10; Mobile",
+ other: "Windows NT 10.0",
+};
+const SPOOFED_HW_CONCURRENCY = 2;
+
+const CONST_APPCODENAME = "Mozilla";
+const CONST_APPNAME = "Netscape";
+const CONST_PRODUCT = "Gecko";
+const CONST_PRODUCTSUB = "20100101";
+const CONST_VENDOR = "";
+const CONST_VENDORSUB = "";
+
+const appVersion = parseInt(Services.appinfo.version);
+const rvVersion =
+ parseInt(
+ Services.prefs.getIntPref("network.http.useragent.forceRVOnly", 0),
+ 0
+ ) || appVersion;
+const spoofedVersion = AppConstants.platform == "android" ? "115" : appVersion;
+
+const LEGACY_UA_GECKO_TRAIL = "20100101";
+
+const DEFAULT_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${appVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+const SPOOFED_UA_GECKO_TRAIL = {
+ linux: LEGACY_UA_GECKO_TRAIL,
+ win: LEGACY_UA_GECKO_TRAIL,
+ macosx: LEGACY_UA_GECKO_TRAIL,
+ android: `${spoofedVersion}.0`,
+ other: LEGACY_UA_GECKO_TRAIL,
+};
+
+const DEFAULT_HARDWARE_CONCURRENCY = navigator.hardwareConcurrency;
+
+// =============================================================================================
+// =============================================================================================
+
+async function testNavigator(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ is(
+ result.appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} navigator.appVersion.`
+ );
+ is(
+ result.platform,
+ expectedResults.platform,
+ `Checking ${testDesc} navigator.platform.`
+ );
+ is(
+ result.userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} navigator.userAgent.`
+ );
+ is(
+ result.userAgentHTTPHeader,
+ expectedResults.userAgentHTTPHeader,
+ `Checking ${testDesc} userAgentHTTPHeader.`
+ );
+ is(
+ result.framer_crossOrigin_userAgentHTTPHeader,
+ expectedResults.framer_crossOrigin_userAgentHTTPHeader,
+ `Checking ${testDesc} framer contacting cross-origin userAgentHTTPHeader.`
+ );
+ is(
+ result.framee_crossOrigin_userAgentHTTPHeader,
+ expectedResults.framee_crossOrigin_userAgentHTTPHeader,
+ `Checking ${testDesc} framee contacting cross-origin userAgentHTTPHeader.`
+ );
+ is(
+ result.mimeTypesLength,
+ expectedResults.mimeTypesLength,
+ `Navigator.mimeTypes has a length of ${expectedResults.mimeTypesLength}.`
+ );
+ is(
+ result.pluginsLength,
+ expectedResults.pluginsLength,
+ `Navigator.plugins has a length of ${expectedResults.pluginsLength}.`
+ );
+ is(
+ result.oscpu,
+ expectedResults.oscpu,
+ `Checking ${testDesc} navigator.oscpu.`
+ );
+ is(
+ result.hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.appCodeName,
+ CONST_APPCODENAME,
+ "Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.appName,
+ CONST_APPNAME,
+ "Navigator.appName reports correct constant value."
+ );
+ is(
+ result.product,
+ CONST_PRODUCT,
+ "Navigator.product reports correct constant value."
+ );
+ is(
+ result.productSub,
+ CONST_PRODUCTSUB,
+ "Navigator.productSub reports correct constant value."
+ );
+ is(
+ result.vendor,
+ CONST_VENDOR,
+ "Navigator.vendor reports correct constant value."
+ );
+ is(
+ result.vendorSub,
+ CONST_VENDORSUB,
+ "Navigator.vendorSub reports correct constant value."
+ );
+
+ is(
+ result.worker_appVersion,
+ expectedResults.appVersion,
+ `Checking ${testDesc} worker navigator.appVersion.`
+ );
+ is(
+ result.worker_platform,
+ expectedResults.platform,
+ `Checking ${testDesc} worker navigator.platform.`
+ );
+ is(
+ result.worker_userAgent,
+ expectedResults.userAgentNavigator,
+ `Checking ${testDesc} worker navigator.userAgent.`
+ );
+ is(
+ result.worker_hardwareConcurrency,
+ expectedResults.hardwareConcurrency,
+ `Checking ${testDesc} worker navigator.hardwareConcurrency.`
+ );
+
+ is(
+ result.worker_appCodeName,
+ CONST_APPCODENAME,
+ "worker Navigator.appCodeName reports correct constant value."
+ );
+ is(
+ result.worker_appName,
+ CONST_APPNAME,
+ "worker Navigator.appName reports correct constant value."
+ );
+ is(
+ result.worker_product,
+ CONST_PRODUCT,
+ "worker Navigator.product reports correct constant value."
+ );
+}
+
+const defaultUserAgent = `Mozilla/5.0 (${
+ DEFAULT_UA_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ DEFAULT_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+const spoofedUserAgentNavigator = `Mozilla/5.0 (${
+ SPOOFED_UA_NAVIGATOR_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ SPOOFED_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+const spoofedUserAgentHeader = `Mozilla/5.0 (${
+ SPOOFED_UA_HTTPHEADER_OS[AppConstants.platform]
+}; rv:${rvVersion}.0) Gecko/${
+ SPOOFED_UA_GECKO_TRAIL[AppConstants.platform]
+} Firefox/${appVersion}.0`;
+
+// The following are convenience objects that allow you to quickly see what is
+// and is not modified from a logical set of values.
+// Be sure to always use `let expectedResults = structuredClone(allNotSpoofed)` to do a
+// deep copy and avoiding corrupting the original 'const' object
+const allNotSpoofed = {
+ appVersion: DEFAULT_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: navigator.hardwareConcurrency,
+ mimeTypesLength: 2,
+ oscpu: DEFAULT_OSCPU[AppConstants.platform],
+ platform: DEFAULT_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: defaultUserAgent,
+ userAgentHTTPHeader: defaultUserAgent,
+ framer_crossOrigin_userAgentHTTPHeader: defaultUserAgent,
+ framee_crossOrigin_userAgentHTTPHeader: defaultUserAgent,
+};
+const allSpoofed = {
+ appVersion: SPOOFED_APPVERSION[AppConstants.platform],
+ hardwareConcurrency: SPOOFED_HW_CONCURRENCY,
+ mimeTypesLength: 2,
+ oscpu: SPOOFED_OSCPU[AppConstants.platform],
+ platform: SPOOFED_PLATFORM[AppConstants.platform],
+ pluginsLength: 5,
+ userAgentNavigator: spoofedUserAgentNavigator,
+ userAgentHTTPHeader: spoofedUserAgentHeader,
+ framer_crossOrigin_userAgentHTTPHeader: spoofedUserAgentHeader,
+ framee_crossOrigin_userAgentHTTPHeader: spoofedUserAgentHeader,
+};
+
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html`;
+
+requestLongerTimeout(2);
+
+let expectedResults = {};
+
+expectedResults = structuredClone(allNotSpoofed);
+add_task(defaultsTest.bind(null, uri, testNavigator, expectedResults));
+
+expectedResults = structuredClone(allSpoofed);
+add_task(simpleRFPTest.bind(null, uri, testNavigator, expectedResults));
+
+// In the below tests, we use the cross-origin domain as the base URI of a resource we fetch (on both the framer and framee)
+// so we can check that the HTTP header is as expected.
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testA.bind(null, uri, testNavigator, expectedResults));
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+expectedResults = structuredClone(allNotSpoofed);
+add_task(testB.bind(null, uri, testNavigator, expectedResults));
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+expectedResults = structuredClone(allSpoofed);
+expectedResults.framer_crossOrigin_userAgentHTTPHeader = defaultUserAgent;
+expectedResults.framee_crossOrigin_userAgentHTTPHeader = spoofedUserAgentHeader;
+add_task(testC.bind(null, uri, testNavigator, expectedResults));
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+expectedResults.framer_crossOrigin_userAgentHTTPHeader = defaultUserAgent;
+expectedResults.framee_crossOrigin_userAgentHTTPHeader = spoofedUserAgentHeader;
+add_task(testD.bind(null, uri, testNavigator, expectedResults));
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testE.bind(null, uri, testNavigator, expectedResults));
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testF.bind(null, uri, testNavigator, expectedResults));
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+expectedResults = structuredClone(allSpoofed);
+add_task(testG.bind(null, uri, testNavigator, expectedResults));
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+expectedResults = structuredClone(allSpoofed);
+add_task(testH.bind(null, uri, testNavigator, expectedResults));
diff --git a/browser/components/resistfingerprinting/test/browser/browser_netInfo.js b/browser/components/resistfingerprinting/test/browser/browser_netInfo.js
new file mode 100644
index 0000000000..c91caa5bc3
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_netInfo.js
@@ -0,0 +1,64 @@
+/**
+ * Bug 1372072 - A test case for check whether network information API has been
+ * spoofed correctly when 'privacy.resistFingerprinting' is true;
+ */
+
+async function testWindow() {
+ // Open a tab to test network information in a content.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ ok("connection" in content.navigator, "navigator.connection should exist");
+
+ is(
+ content.navigator.connection.type,
+ "unknown",
+ "The connection type is spoofed correctly"
+ );
+ });
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testWorker() {
+ // Open a tab to test network information in a worker.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await new Promise(resolve => {
+ let worker = new content.Worker("file_workerNetInfo.js");
+
+ worker.onmessage = function (e) {
+ if (e.data.type == "status") {
+ ok(e.data.status, e.data.msg);
+ } else if (e.data.type == "finish") {
+ resolve();
+ } else {
+ ok(false, "Unknown message type");
+ resolve();
+ }
+ };
+ worker.postMessage({ type: "runTests" });
+ });
+ });
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function runTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["dom.netinfo.enabled", true],
+ ],
+ });
+
+ await testWindow();
+ await testWorker();
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js b/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
new file mode 100644
index 0000000000..9126fe4690
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
@@ -0,0 +1,166 @@
+// ================================================================================================
+// ================================================================================================
+add_task(async function runRFPandRTPTests() {
+ // RFP = ResistFingerprinting / RTP = ReduceTimePrecision
+ let runTests = async function (data) {
+ let timerlist = data.list;
+ let labelType = data.resistFingerprinting
+ ? "ResistFingerprinting"
+ : "ReduceTimePrecision";
+ let expectedPrecision = data.precision;
+ // eslint beleives that isrounded is available in this scope, but if you
+ // remove the assignment, you will see it is not
+ // eslint-disable-next-line
+ let isRounded = eval(data.isRoundedFunc);
+
+ ok(
+ isRounded(content.performance.timeOrigin, expectedPrecision),
+ `For ${labelType}, performance.timeOrigin is not correctly rounded: ` +
+ content.performance.timeOrigin
+ );
+
+ // Check that whether the performance timing API is correctly spoofed.
+ for (let time of timerlist) {
+ if (
+ data.resistFingerprinting &&
+ (time == "domainLookupStart" || time == "domainLookupEnd")
+ ) {
+ is(
+ content.performance.timing[time],
+ content.performance.timing.fetchStart,
+ `For resistFingerprinting, the timing(${time}) is not correctly spoofed.`
+ );
+ } else {
+ ok(
+ isRounded(content.performance.timing[time], expectedPrecision),
+ `For ${labelType}(` +
+ expectedPrecision +
+ `), the timing(${time}) is not correctly rounded: ` +
+ content.performance.timing[time]
+ );
+ }
+ }
+
+ // Try to add some entries.
+ content.performance.mark("Test");
+ content.performance.mark("Test-End");
+ content.performance.measure("Test-Measure", "Test", "Test-End");
+
+ // Check the entries for performance.getEntries/getEntriesByType/getEntriesByName.
+ await new Promise(resolve => {
+ const paintObserver = new content.PerformanceObserver(() => {
+ resolve();
+ });
+ paintObserver.observe({ type: "paint", buffered: true });
+ });
+
+ is(
+ content.performance.getEntries().length,
+ 5,
+ `For ${labelType}, there should be 4 entries for performance.getEntries()`
+ // PerformancePaintTiming, PerformanceNavigationTiming, PerformanceMark, PerformanceMark, PerformanceMeasure
+ );
+ for (var i = 0; i < 5; i++) {
+ let startTime = content.performance.getEntries()[i].startTime;
+ let duration = content.performance.getEntries()[i].duration;
+ ok(
+ isRounded(startTime, expectedPrecision),
+ `For ${labelType}(` +
+ expectedPrecision +
+ "), performance.getEntries(" +
+ i +
+ ").startTime is not rounded: " +
+ startTime
+ );
+ ok(
+ isRounded(duration, expectedPrecision),
+ `For ${labelType}(` +
+ expectedPrecision +
+ "), performance.getEntries(" +
+ i +
+ ").duration is not rounded: " +
+ duration
+ );
+ }
+ is(
+ content.performance.getEntriesByType("mark").length,
+ 2,
+ `For ${labelType}, there should be 2 entries for performance.getEntriesByType()`
+ );
+ is(
+ content.performance.getEntriesByName("Test", "mark").length,
+ 1,
+ `For ${labelType}, there should be 1 entry for performance.getEntriesByName()`
+ );
+ content.performance.clearMarks();
+ content.performance.clearMeasures();
+ content.performance.clearResourceTimings();
+ };
+
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ true,
+ false,
+ 200,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ true,
+ false,
+ 100,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ false,
+ false,
+ 13,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ false,
+ false,
+ 0.13,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(true, true, true, 100, runTests);
+ await setupPerformanceAPISpoofAndDisableTest(true, false, true, 13, runTests);
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ false,
+ true,
+ 0.13,
+ runTests
+ );
+
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 100,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 13,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 0.13,
+ runTests
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ true,
+ 0.005,
+ runTests
+ );
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_performanceAPIWorkers.js b/browser/components/resistfingerprinting/test/browser/browser_performanceAPIWorkers.js
new file mode 100644
index 0000000000..0abb38d532
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_performanceAPIWorkers.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// ================================================================================================
+// ================================================================================================
+let runWorkerTest = async function (data) {
+ let expectedPrecision = data.precision;
+ let workerCall = data.workerCall;
+ await new Promise(resolve => {
+ let worker = new content.Worker("file_workerPerformance.js");
+ worker.onmessage = function (e) {
+ if (e.data.type == "status") {
+ ok(e.data.status, e.data.msg);
+ } else if (e.data.type == "finish") {
+ worker.terminate();
+ resolve();
+ } else {
+ ok(false, "Unknown message type");
+ worker.terminate();
+ resolve();
+ }
+ };
+ worker.postMessage({ type: workerCall, precision: expectedPrecision });
+ });
+};
+
+add_task(async function runRFPestsForWorker() {
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ true,
+ false,
+ 100,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ false,
+ false,
+ 13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ true,
+ true,
+ false,
+ 0.13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+});
+
+add_task(async function runRTPTestsForWorker() {
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 100,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+ await setupPerformanceAPISpoofAndDisableTest(
+ false,
+ true,
+ false,
+ 0.13,
+ runWorkerTest,
+ "runTimerTests"
+ );
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_reduceTimePrecision_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_reduceTimePrecision_iframes.js
new file mode 100644
index 0000000000..69d32872ab
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_reduceTimePrecision_iframes.js
@@ -0,0 +1,242 @@
+/**
+ * This test only tests values in the iframe, it does not test them on the framer
+ *
+ * Covers the following cases:
+ * - RFP is disabled entirely
+ * - RFP is enabled entirely
+ *
+ * - (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ * - (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ * - (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ * - (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ * - (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ * - (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ * - (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ * - (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ */
+
+"use strict";
+
+requestLongerTimeout(3);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+});
+
+// =============================================================================================
+// =============================================================================================
+
+async function testTimePrecision(results, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+ let precision = undefined;
+
+ if (!expectedResults.shouldRFPApply) {
+ precision = extraData.RTP_Precision;
+ } else {
+ precision = extraData.RFP_Precision;
+ }
+
+ for (let result of results) {
+ let isRounded = isTimeValueRounded(result.value, precision);
+
+ ok(
+ isRounded,
+ "Test: " +
+ testDesc +
+ " - '" +
+ "'" +
+ result.name +
+ "' should be rounded to nearest " +
+ precision +
+ " ms; saw " +
+ result.value
+ );
+ }
+}
+
+const RFP_TIME_ATOM_MS = 16.667;
+const uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframer.html`;
+
+// The first three variables are defined here; and then set for test banks below.
+let extraData = {};
+let extraPrefs = {};
+let precision = 100;
+let expectedResults = {}; // In this test, we don't have explicit expected values, but rather we expect them to be rounded
+
+// ========================================================================================================================
+// Create a function that defines all the tests
+function addAllTests(extraData_, extraPrefs_) {
+ add_task(
+ defaultsTest.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ add_task(
+ simpleRFPTest.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+ add_task(
+ testA.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+ add_task(
+ testB.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+ add_task(
+ testC.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+ add_task(
+ testD.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+ add_task(
+ testE.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+ add_task(
+ testF.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+ add_task(
+ testG.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+
+ // (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+ add_task(
+ testH.bind(
+ null,
+ uri,
+ testTimePrecision,
+ expectedResults,
+ extraData_,
+ extraPrefs_
+ )
+ );
+}
+
+// ========================================================================================================================
+// First we run through all the tests with RTP's precision set to 100 ms and 133 ms.
+// Because 100ms and 133ms are >= RFP's precision of 100ms, _all_ tests results should
+// be rounded.
+precision = 100;
+extraData = {
+ RFP_Precision: precision,
+ RTP_Precision: precision,
+};
+extraPrefs = [
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ precision * 1000,
+ ],
+];
+addAllTests(extraData, extraPrefs);
+
+precision = 133;
+extraData = {
+ RFP_Precision: precision,
+ RTP_Precision: precision,
+};
+extraPrefs = [
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ precision * 1000,
+ ],
+];
+addAllTests(extraData, extraPrefs);
+
+// ========================================================================================================================
+// Then we run through all the tests with the precision set to its normal value.
+// This will mean that in some cases we expect RFP to apply and in some we don't.
+
+precision = RFP_TIME_ATOM_MS;
+extraData = {
+ RFP_Precision: precision,
+ RTP_Precision: 1,
+};
+extraPrefs = [];
+addAllTests(extraData, extraPrefs);
+
+// ========================================================================================================================
+// Finally we run through all the tests with the precision set to an unusual value
+// This will mean that in some cases we expect RFP to apply and in some we don't.
+
+precision = RFP_TIME_ATOM_MS;
+extraData = {
+ RFP_Precision: RFP_TIME_ATOM_MS,
+ RTP_Precision: 7,
+};
+extraPrefs = [
+ ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", 7 * 1000],
+];
+addAllTests(extraData, extraPrefs);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js
new file mode 100644
index 0000000000..96ad858fb3
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_dialogWindow.js
@@ -0,0 +1,51 @@
+/**
+ * Bug 1352305 - A test case for dialog windows that it should not be rounded
+ * even after fingerprinting resistance is enabled.
+ */
+
+async function test_dialog_window() {
+ let diagWin;
+
+ await new Promise(resolve => {
+ // Open a dialog window which is not rounded size.
+ diagWin = window.openDialog(
+ "about:blank",
+ null,
+ "innerWidth=250,innerHeight=350"
+ );
+
+ diagWin.addEventListener(
+ "load",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+
+ is(diagWin.innerWidth, 250, "The dialog window doesn't have a rounded size.");
+ is(
+ diagWin.innerHeight,
+ 350,
+ "The dialog window doesn't have a rounded size."
+ );
+
+ await BrowserTestUtils.closeWindow(diagWin);
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+});
+
+add_task(test_dialog_window);
+
+add_task(async function test_dialog_window_without_resistFingerprinting() {
+ // Test dialog windows with 'privacy.resistFingerprinting' is false.
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", false]],
+ });
+
+ await test_dialog_window();
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_newWindow.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_newWindow.js
new file mode 100644
index 0000000000..ea22c7fa20
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_newWindow.js
@@ -0,0 +1,62 @@
+/*
+ * Bug 1330882 - A test case for opening new windows as rounded size when
+ * fingerprinting resistance is enabled.
+ */
+
+const CC = Components.Constructor;
+
+let gMaxAvailWidth;
+let gMaxAvailHeight;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+
+ // Calculate the maximum available size.
+ let maxAvailSize = await calcMaximumAvailSize();
+
+ gMaxAvailWidth = maxAvailSize.maxAvailWidth;
+ gMaxAvailHeight = maxAvailSize.maxAvailHeight;
+});
+
+add_task(async function test_new_window() {
+ // Open a new window.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ // Load a page and verify its window size.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ gMaxAvailWidth, gMaxAvailHeight }],
+ async function (input) {
+ is(
+ content.screen.width,
+ input.gMaxAvailWidth,
+ "The screen.width has a correct rounded value"
+ );
+ is(
+ content.screen.height,
+ input.gMaxAvailHeight,
+ "The screen.height has a correct rounded value"
+ );
+ is(
+ content.innerWidth,
+ input.gMaxAvailWidth,
+ "The window.innerWidth has a correct rounded value"
+ );
+ is(
+ content.innerHeight,
+ input.gMaxAvailHeight,
+ "The window.innerHeight has a correct rounded value"
+ );
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_max_inner.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_max_inner.js
new file mode 100644
index 0000000000..5d8ed74580
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_max_inner.js
@@ -0,0 +1,26 @@
+/*
+ * Bug 1330882 - A test case for opening new windows through window.open() as
+ * rounded size when fingerprinting resistance is enabled. This test is for
+ * maximum values.
+ */
+
+OpenTest.run([
+ {
+ settingWidth: 1025,
+ settingHeight: 1050,
+ targetWidth: 1000,
+ targetHeight: 1000,
+ },
+ {
+ settingWidth: 9999,
+ settingHeight: 9999,
+ targetWidth: 1000,
+ targetHeight: 1000,
+ },
+ {
+ settingWidth: 999,
+ settingHeight: 999,
+ targetWidth: 1000,
+ targetHeight: 1000,
+ },
+]);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_mid_inner.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_mid_inner.js
new file mode 100644
index 0000000000..a3a41829e0
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_mid_inner.js
@@ -0,0 +1,26 @@
+/*
+ * Bug 1330882 - A test case for opening new windows through window.open() as
+ * rounded size when fingerprinting resistance is enabled. This test is for
+ * middle values.
+ */
+
+OpenTest.run([
+ {
+ settingWidth: 600,
+ settingHeight: 600,
+ targetWidth: 600,
+ targetHeight: 600,
+ },
+ {
+ settingWidth: 599,
+ settingHeight: 599,
+ targetWidth: 600,
+ targetHeight: 600,
+ },
+ {
+ settingWidth: 401,
+ settingHeight: 501,
+ targetWidth: 600,
+ targetHeight: 600,
+ },
+]);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_inner.js b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_inner.js
new file mode 100644
index 0000000000..f016a560ba
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_roundedWindow_open_min_inner.js
@@ -0,0 +1,20 @@
+/*
+ * Bug 1330882 - A test case for opening new windows through window.open() as
+ * rounded size when fingerprinting resistance is enabled. This test is for
+ * minimum values.
+ */
+
+OpenTest.run([
+ {
+ settingWidth: 199,
+ settingHeight: 99,
+ targetWidth: 200,
+ targetHeight: 100,
+ },
+ {
+ settingWidth: 10,
+ settingHeight: 10,
+ targetWidth: 200,
+ targetHeight: 100,
+ },
+]);
diff --git a/browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js b/browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js
new file mode 100644
index 0000000000..457d5fce8b
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_spoofing_keyboard_event.js
@@ -0,0 +1,2268 @@
+/**
+ * Bug 1222285 - A test case for testing whether keyboard events be spoofed correctly
+ * when fingerprinting resistance is enable.
+ */
+
+const SHOULD_DELIVER_KEYDOWN = 0x1;
+const SHOULD_DELIVER_KEYPRESS = 0x2;
+const SHOULD_DELIVER_KEYUP = 0x4;
+const SHOULD_DELIVER_ALL_FOR_PRINTABLE =
+ SHOULD_DELIVER_KEYDOWN | SHOULD_DELIVER_KEYPRESS | SHOULD_DELIVER_KEYUP;
+const SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE =
+ SHOULD_DELIVER_KEYDOWN | SHOULD_DELIVER_KEYUP;
+
+// The test cases for english content.
+const TEST_CASES_EN = [
+ {
+ key: "KEY_ArrowDown",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "ArrowDown",
+ code: "ArrowDown",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_DOWN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_ArrowLeft",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "ArrowLeft",
+ code: "ArrowLeft",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_LEFT,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_ArrowRight",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "ArrowRight",
+ code: "ArrowRight",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_RIGHT,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_ArrowUp",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "ArrowUp",
+ code: "ArrowUp",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_UP,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_CapsLock",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_KEYDOWN,
+ result: {
+ key: "CapsLock",
+ code: "CapsLock",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_End",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "End",
+ code: "End",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_END,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_Enter",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "Enter",
+ code: "Enter",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_RETURN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_Escape",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "Escape",
+ code: "Escape",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_ESCAPE,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_Home",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "Home",
+ code: "Home",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_HOME,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_Meta",
+ modifiers: { location: KeyboardEvent.DOM_KEY_LOCATION_LEFT, metaKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_KEYDOWN,
+ result: {
+ key: "Meta",
+ code: "OSLeft",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_WIN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_Meta",
+ modifiers: {
+ location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ metaKey: true,
+ },
+ expectedKeyEvent: SHOULD_DELIVER_KEYDOWN,
+ result: {
+ key: "Meta",
+ code: "OSRight",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_WIN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_OS",
+ modifiers: { location: KeyboardEvent.DOM_KEY_LOCATION_LEFT, osKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_KEYDOWN,
+ result: {
+ key: "OS",
+ code: "OSLeft",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_WIN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_LEFT,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_OS",
+ modifiers: { location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT, osKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_KEYDOWN,
+ result: {
+ key: "OS",
+ code: "OSRight",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_WIN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_PageDown",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "PageDown",
+ code: "PageDown",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_PAGE_DOWN,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_PageUp",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "PageUp",
+ code: "PageUp",
+ charCode: 0,
+ keyCode: KeyboardEvent.DOM_VK_PAGE_UP,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: " ",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: " ",
+ code: "Space",
+ charCode: 32,
+ keyCode: KeyboardEvent.DOM_VK_SPACE,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ",",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ",",
+ code: "Comma",
+ charCode: 44,
+ keyCode: KeyboardEvent.DOM_VK_COMMA,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "<",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "<",
+ code: "Comma",
+ charCode: 60,
+ keyCode: KeyboardEvent.DOM_VK_COMMA,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "[",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "[",
+ code: "BracketLeft",
+ charCode: 91,
+ keyCode: KeyboardEvent.DOM_VK_OPEN_BRACKET,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "{",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "{",
+ code: "BracketLeft",
+ charCode: 123,
+ keyCode: KeyboardEvent.DOM_VK_OPEN_BRACKET,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "]",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "]",
+ code: "BracketRight",
+ charCode: 93,
+ keyCode: KeyboardEvent.DOM_VK_CLOSE_BRACKET,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "}",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "}",
+ code: "BracketRight",
+ charCode: 125,
+ keyCode: KeyboardEvent.DOM_VK_CLOSE_BRACKET,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "\\",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "\\",
+ code: "Backslash",
+ charCode: 92,
+ keyCode: KeyboardEvent.DOM_VK_BACK_SLASH,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "|",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "|",
+ code: "Backslash",
+ charCode: 124,
+ keyCode: KeyboardEvent.DOM_VK_BACK_SLASH,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ";",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ";",
+ code: "Semicolon",
+ charCode: 59,
+ keyCode: KeyboardEvent.DOM_VK_SEMICOLON,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ":",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ":",
+ code: "Semicolon",
+ charCode: 58,
+ keyCode: KeyboardEvent.DOM_VK_SEMICOLON,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ".",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ".",
+ code: "Period",
+ charCode: 46,
+ keyCode: KeyboardEvent.DOM_VK_PERIOD,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ">",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ">",
+ code: "Period",
+ charCode: 62,
+ keyCode: KeyboardEvent.DOM_VK_PERIOD,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "/",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "/",
+ code: "Slash",
+ charCode: 47,
+ keyCode: KeyboardEvent.DOM_VK_SLASH,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "?",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "?",
+ code: "Slash",
+ charCode: 63,
+ keyCode: KeyboardEvent.DOM_VK_SLASH,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "'",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "'",
+ code: "Quote",
+ charCode: 39,
+ keyCode: KeyboardEvent.DOM_VK_QUOTE,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: '"',
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: '"',
+ code: "Quote",
+ charCode: 34,
+ keyCode: KeyboardEvent.DOM_VK_QUOTE,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "-",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "-",
+ code: "Minus",
+ charCode: 45,
+ keyCode: KeyboardEvent.DOM_VK_HYPHEN_MINUS,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "_",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "_",
+ code: "Minus",
+ charCode: 95,
+ keyCode: KeyboardEvent.DOM_VK_HYPHEN_MINUS,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "=",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "=",
+ code: "Equal",
+ charCode: 61,
+ keyCode: KeyboardEvent.DOM_VK_EQUALS,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "+",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "+",
+ code: "Equal",
+ charCode: 43,
+ keyCode: KeyboardEvent.DOM_VK_EQUALS,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "a",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "a",
+ code: "KeyA",
+ charCode: 97,
+ keyCode: KeyboardEvent.DOM_VK_A,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "A",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "A",
+ code: "KeyA",
+ charCode: 65,
+ keyCode: KeyboardEvent.DOM_VK_A,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "b",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "b",
+ code: "KeyB",
+ charCode: 98,
+ keyCode: KeyboardEvent.DOM_VK_B,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "B",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "B",
+ code: "KeyB",
+ charCode: 66,
+ keyCode: KeyboardEvent.DOM_VK_B,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "c",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "c",
+ code: "KeyC",
+ charCode: 99,
+ keyCode: KeyboardEvent.DOM_VK_C,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "C",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "C",
+ code: "KeyC",
+ charCode: 67,
+ keyCode: KeyboardEvent.DOM_VK_C,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "d",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "d",
+ code: "KeyD",
+ charCode: 100,
+ keyCode: KeyboardEvent.DOM_VK_D,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "D",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "D",
+ code: "KeyD",
+ charCode: 68,
+ keyCode: KeyboardEvent.DOM_VK_D,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "e",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "e",
+ code: "KeyE",
+ charCode: 101,
+ keyCode: KeyboardEvent.DOM_VK_E,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "E",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "E",
+ code: "KeyE",
+ charCode: 69,
+ keyCode: KeyboardEvent.DOM_VK_E,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "f",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "f",
+ code: "KeyF",
+ charCode: 102,
+ keyCode: KeyboardEvent.DOM_VK_F,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "F",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "F",
+ code: "KeyF",
+ charCode: 70,
+ keyCode: KeyboardEvent.DOM_VK_F,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "g",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "g",
+ code: "KeyG",
+ charCode: 103,
+ keyCode: KeyboardEvent.DOM_VK_G,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "G",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "G",
+ code: "KeyG",
+ charCode: 71,
+ keyCode: KeyboardEvent.DOM_VK_G,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "h",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "h",
+ code: "KeyH",
+ charCode: 104,
+ keyCode: KeyboardEvent.DOM_VK_H,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "H",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "H",
+ code: "KeyH",
+ charCode: 72,
+ keyCode: KeyboardEvent.DOM_VK_H,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "i",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "i",
+ code: "KeyI",
+ charCode: 105,
+ keyCode: KeyboardEvent.DOM_VK_I,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "I",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "I",
+ code: "KeyI",
+ charCode: 73,
+ keyCode: KeyboardEvent.DOM_VK_I,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "j",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "j",
+ code: "KeyJ",
+ charCode: 106,
+ keyCode: KeyboardEvent.DOM_VK_J,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "J",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "J",
+ code: "KeyJ",
+ charCode: 74,
+ keyCode: KeyboardEvent.DOM_VK_J,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "k",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "k",
+ code: "KeyK",
+ charCode: 107,
+ keyCode: KeyboardEvent.DOM_VK_K,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "K",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "K",
+ code: "KeyK",
+ charCode: 75,
+ keyCode: KeyboardEvent.DOM_VK_K,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "l",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "l",
+ code: "KeyL",
+ charCode: 108,
+ keyCode: KeyboardEvent.DOM_VK_L,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "L",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "L",
+ code: "KeyL",
+ charCode: 76,
+ keyCode: KeyboardEvent.DOM_VK_L,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "m",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "m",
+ code: "KeyM",
+ charCode: 109,
+ keyCode: KeyboardEvent.DOM_VK_M,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "M",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "M",
+ code: "KeyM",
+ charCode: 77,
+ keyCode: KeyboardEvent.DOM_VK_M,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "n",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "n",
+ code: "KeyN",
+ charCode: 110,
+ keyCode: KeyboardEvent.DOM_VK_N,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "N",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "N",
+ code: "KeyN",
+ charCode: 78,
+ keyCode: KeyboardEvent.DOM_VK_N,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "o",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "o",
+ code: "KeyO",
+ charCode: 111,
+ keyCode: KeyboardEvent.DOM_VK_O,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "O",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "O",
+ code: "KeyO",
+ charCode: 79,
+ keyCode: KeyboardEvent.DOM_VK_O,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "p",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "p",
+ code: "KeyP",
+ charCode: 112,
+ keyCode: KeyboardEvent.DOM_VK_P,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "P",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "P",
+ code: "KeyP",
+ charCode: 80,
+ keyCode: KeyboardEvent.DOM_VK_P,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "q",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "q",
+ code: "KeyQ",
+ charCode: 113,
+ keyCode: KeyboardEvent.DOM_VK_Q,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "Q",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "Q",
+ code: "KeyQ",
+ charCode: 81,
+ keyCode: KeyboardEvent.DOM_VK_Q,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "r",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "r",
+ code: "KeyR",
+ charCode: 114,
+ keyCode: KeyboardEvent.DOM_VK_R,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "R",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "R",
+ code: "KeyR",
+ charCode: 82,
+ keyCode: KeyboardEvent.DOM_VK_R,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "s",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "s",
+ code: "KeyS",
+ charCode: 115,
+ keyCode: KeyboardEvent.DOM_VK_S,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "S",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "S",
+ code: "KeyS",
+ charCode: 83,
+ keyCode: KeyboardEvent.DOM_VK_S,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "t",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "t",
+ code: "KeyT",
+ charCode: 116,
+ keyCode: KeyboardEvent.DOM_VK_T,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "T",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "T",
+ code: "KeyT",
+ charCode: 84,
+ keyCode: KeyboardEvent.DOM_VK_T,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "u",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "u",
+ code: "KeyU",
+ charCode: 117,
+ keyCode: KeyboardEvent.DOM_VK_U,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "U",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "U",
+ code: "KeyU",
+ charCode: 85,
+ keyCode: KeyboardEvent.DOM_VK_U,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "v",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "v",
+ code: "KeyV",
+ charCode: 118,
+ keyCode: KeyboardEvent.DOM_VK_V,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "V",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "V",
+ code: "KeyV",
+ charCode: 86,
+ keyCode: KeyboardEvent.DOM_VK_V,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "w",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "w",
+ code: "KeyW",
+ charCode: 119,
+ keyCode: KeyboardEvent.DOM_VK_W,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "W",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "W",
+ code: "KeyW",
+ charCode: 87,
+ keyCode: KeyboardEvent.DOM_VK_W,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "x",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "x",
+ code: "KeyX",
+ charCode: 120,
+ keyCode: KeyboardEvent.DOM_VK_X,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "X",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "X",
+ code: "KeyX",
+ charCode: 88,
+ keyCode: KeyboardEvent.DOM_VK_X,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "y",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "y",
+ code: "KeyY",
+ charCode: 121,
+ keyCode: KeyboardEvent.DOM_VK_Y,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "Y",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "Y",
+ code: "KeyY",
+ charCode: 89,
+ keyCode: KeyboardEvent.DOM_VK_Y,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "z",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "z",
+ code: "KeyZ",
+ charCode: 122,
+ keyCode: KeyboardEvent.DOM_VK_Z,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "Z",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "Z",
+ code: "KeyZ",
+ charCode: 90,
+ keyCode: KeyboardEvent.DOM_VK_Z,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "0",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "0",
+ code: "Digit0",
+ charCode: 48,
+ keyCode: KeyboardEvent.DOM_VK_0,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "1",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "1",
+ code: "Digit1",
+ charCode: 49,
+ keyCode: KeyboardEvent.DOM_VK_1,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "2",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "2",
+ code: "Digit2",
+ charCode: 50,
+ keyCode: KeyboardEvent.DOM_VK_2,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "3",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "3",
+ code: "Digit3",
+ charCode: 51,
+ keyCode: KeyboardEvent.DOM_VK_3,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "4",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "4",
+ code: "Digit4",
+ charCode: 52,
+ keyCode: KeyboardEvent.DOM_VK_4,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "5",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "5",
+ code: "Digit5",
+ charCode: 53,
+ keyCode: KeyboardEvent.DOM_VK_5,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "6",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "6",
+ code: "Digit6",
+ charCode: 54,
+ keyCode: KeyboardEvent.DOM_VK_6,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "7",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "7",
+ code: "Digit7",
+ charCode: 55,
+ keyCode: KeyboardEvent.DOM_VK_7,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "8",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "8",
+ code: "Digit8",
+ charCode: 56,
+ keyCode: KeyboardEvent.DOM_VK_8,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "9",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "9",
+ code: "Digit9",
+ charCode: 57,
+ keyCode: KeyboardEvent.DOM_VK_9,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: ")",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: ")",
+ code: "Digit0",
+ charCode: 41,
+ keyCode: KeyboardEvent.DOM_VK_0,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "!",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "!",
+ code: "Digit1",
+ charCode: 33,
+ keyCode: KeyboardEvent.DOM_VK_1,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "@",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "@",
+ code: "Digit2",
+ charCode: 64,
+ keyCode: KeyboardEvent.DOM_VK_2,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "#",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "#",
+ code: "Digit3",
+ charCode: 35,
+ keyCode: KeyboardEvent.DOM_VK_3,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "$",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "$",
+ code: "Digit4",
+ charCode: 36,
+ keyCode: KeyboardEvent.DOM_VK_4,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "%",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "%",
+ code: "Digit5",
+ charCode: 37,
+ keyCode: KeyboardEvent.DOM_VK_5,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "^",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "^",
+ code: "Digit6",
+ charCode: 94,
+ keyCode: KeyboardEvent.DOM_VK_6,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "&",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "&",
+ code: "Digit7",
+ charCode: 38,
+ keyCode: KeyboardEvent.DOM_VK_7,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "*",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "*",
+ code: "Digit8",
+ charCode: 42,
+ keyCode: KeyboardEvent.DOM_VK_8,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "(",
+ modifiers: { shiftKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "(",
+ code: "Digit9",
+ charCode: 40,
+ keyCode: KeyboardEvent.DOM_VK_9,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: true,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F1",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F1",
+ code: "F1",
+ charCode: 112,
+ keyCode: KeyboardEvent.DOM_VK_F1,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F2",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F2",
+ code: "F2",
+ charCode: 113,
+ keyCode: KeyboardEvent.DOM_VK_F2,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F3",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F3",
+ code: "F3",
+ charCode: 114,
+ keyCode: KeyboardEvent.DOM_VK_F3,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F4",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F4",
+ code: "F4",
+ charCode: 115,
+ keyCode: KeyboardEvent.DOM_VK_F4,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F5",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F5",
+ code: "F5",
+ charCode: 116,
+ keyCode: KeyboardEvent.DOM_VK_F5,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F7",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F7",
+ code: "F7",
+ charCode: 118,
+ keyCode: KeyboardEvent.DOM_VK_F7,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F8",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F8",
+ code: "F8",
+ charCode: 119,
+ keyCode: KeyboardEvent.DOM_VK_F8,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F9",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F9",
+ code: "F9",
+ charCode: 120,
+ keyCode: KeyboardEvent.DOM_VK_F9,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F10",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F10",
+ code: "F10",
+ charCode: 121,
+ keyCode: KeyboardEvent.DOM_VK_F10,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F11",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F11",
+ code: "F11",
+ charCode: 122,
+ keyCode: KeyboardEvent.DOM_VK_F11,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "KEY_F12",
+ modifiers: {},
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "F12",
+ code: "F12",
+ charCode: 123,
+ keyCode: KeyboardEvent.DOM_VK_F12,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "a",
+ modifiers: { ctrlKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "a",
+ code: "KeyA",
+ charCode: 97,
+ keyCode: KeyboardEvent.DOM_VK_A,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: true,
+ altGraphKey: false,
+ },
+ },
+ {
+ key: "a",
+ modifiers: { altKey: true },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_NON_PRINTABLE,
+ result: {
+ key: "a",
+ code: "KeyA",
+ charCode: 97,
+ keyCode: KeyboardEvent.DOM_VK_A,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ },
+];
+
+async function testKeyEvent(aTab, aTestCase) {
+ // Prepare all expected key events.
+ let testEvents = [];
+
+ if (aTestCase.expectedKeyEvent & SHOULD_DELIVER_KEYDOWN) {
+ testEvents.push("keydown");
+ }
+
+ if (aTestCase.expectedKeyEvent & SHOULD_DELIVER_KEYPRESS) {
+ testEvents.push("keypress");
+ }
+
+ if (aTestCase.expectedKeyEvent & SHOULD_DELIVER_KEYUP) {
+ testEvents.push("keyup");
+ }
+
+ let allKeyEventPromises = [];
+
+ for (let testEvent of testEvents) {
+ let keyEventPromise = ContentTask.spawn(
+ aTab.linkedBrowser,
+ { testEvent, result: aTestCase.result },
+ async aInput => {
+ function verifyKeyboardEvent(
+ aEvent,
+ aResult,
+ aSameKeyCodeAndCharCodeValue
+ ) {
+ is(
+ aEvent.key,
+ aResult.key,
+ "KeyboardEvent.key is correctly spoofed."
+ );
+ is(
+ aEvent.code,
+ aResult.code,
+ "KeyboardEvent.code is correctly spoofed."
+ );
+ is(
+ aEvent.location,
+ aResult.location,
+ "KeyboardEvent.location is correctly spoofed."
+ );
+ is(
+ aEvent.altKey,
+ aResult.altKey,
+ "KeyboardEvent.altKey is correctly spoofed."
+ );
+ is(
+ aEvent.shiftKey,
+ aResult.shiftKey,
+ "KeyboardEvent.shiftKey is correctly spoofed."
+ );
+ is(
+ aEvent.ctrlKey,
+ aResult.ctrlKey,
+ "KeyboardEvent.ctrlKey is correctly spoofed."
+ );
+
+ // If the charCode is not 0, this is a character. The keyCode will be remained as 0.
+ // Otherwise, we should check the keyCode.
+ if (!aSameKeyCodeAndCharCodeValue) {
+ if (aEvent.charCode != 0) {
+ is(
+ aEvent.keyCode,
+ 0,
+ "KeyboardEvent.keyCode should be 0 for this case."
+ );
+ is(
+ aEvent.charCode,
+ aResult.charCode,
+ "KeyboardEvent.charCode is correctly spoofed."
+ );
+ } else {
+ is(
+ aEvent.keyCode,
+ aResult.keyCode,
+ "KeyboardEvent.keyCode is correctly spoofed."
+ );
+ is(
+ aEvent.charCode,
+ 0,
+ "KeyboardEvent.charCode should be 0 for this case."
+ );
+ }
+ } else if (aResult.charCode) {
+ is(
+ aEvent.keyCode,
+ aResult.charCode,
+ "KeyboardEvent.keyCode should be same as expected charCode for this case."
+ );
+ is(
+ aEvent.charCode,
+ aResult.charCode,
+ "KeyboardEvent.charCode is correctly spoofed."
+ );
+ } else {
+ is(
+ aEvent.keyCode,
+ aResult.keyCode,
+ "KeyboardEvent.keyCode is correctly spoofed."
+ );
+ is(
+ aEvent.charCode,
+ aResult.keyCode,
+ "KeyboardEvent.charCode should be same as expected keyCode for this case."
+ );
+ }
+
+ // Check getModifierState().
+ is(
+ aEvent.modifierState.Alt,
+ aResult.altKey,
+ "KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Alt'."
+ );
+ is(
+ aEvent.modifierState.AltGraph,
+ aResult.altGraphKey,
+ "KeyboardEvent.getModifierState() reports a correctly spoofed value for 'AltGraph'."
+ );
+ is(
+ aEvent.modifierState.Shift,
+ aResult.shiftKey,
+ `KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Shift'.`
+ );
+ is(
+ aEvent.modifierState.Control,
+ aResult.ctrlKey,
+ `KeyboardEvent.getModifierState() reports a correctly spoofed value for 'Control'.`
+ );
+ }
+
+ let { testEvent: eventType, result } = aInput;
+ let inputBox = content.document.getElementById("test");
+
+ // We need to put the real access of event object into the content page instead of
+ // here, ContentTask.spawn, since the script running here is under chrome privilege.
+ // So the fingerprinting resistance won't work here.
+ let resElement = content.document.getElementById("result-" + eventType);
+
+ // First, try to focus on the input box.
+ await new Promise(resolve => {
+ if (content.document.activeElement == inputBox) {
+ // the input box already got focused.
+ resolve();
+ } else {
+ inputBox.onfocus = () => {
+ resolve();
+ };
+ inputBox.focus();
+ }
+ });
+
+ // Once the result of the keyboard event ready, the content page will send
+ // a custom event 'resultAvailable' for informing the script to check the
+ // result.
+ await new Promise(resolve => {
+ function eventHandler(aEvent) {
+ verifyKeyboardEvent(
+ JSON.parse(resElement.value),
+ result,
+ eventType == "keypress"
+ );
+ resElement.removeEventListener(
+ "resultAvailable",
+ eventHandler,
+ true
+ );
+ resolve();
+ }
+
+ resElement.addEventListener("resultAvailable", eventHandler, true);
+ });
+ }
+ );
+
+ allKeyEventPromises.push(keyEventPromise);
+ }
+
+ // Send key event to the tab.
+ BrowserTestUtils.synthesizeKey(
+ aTestCase.key,
+ aTestCase.modifiers,
+ aTab.linkedBrowser
+ );
+
+ await Promise.all(allKeyEventPromises);
+}
+
+function eventConsumer(aEvent) {
+ aEvent.preventDefault();
+}
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+});
+
+add_task(async function runTestsForEnglishContent() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_keyBoardEvent.sjs?language=en-US"
+ );
+
+ // Prevent shortcut keys.
+ gBrowser.addEventListener("keypress", eventConsumer, true);
+
+ for (let test of TEST_CASES_EN) {
+ await testKeyEvent(tab, test);
+ }
+
+ // Test a key which doesn't exist in US English keyboard layout.
+ await testKeyEvent(tab, {
+ key: "\u00DF",
+ modifiers: { code: "Minus", keyCode: 63 },
+ expectedKeyEvent: SHOULD_DELIVER_ALL_FOR_PRINTABLE,
+ result: {
+ key: "\u00DF",
+ code: "",
+ charCode: 223,
+ keyCode: 0,
+ location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD,
+ altKey: false,
+ shiftKey: false,
+ ctrlKey: false,
+ altGraphKey: false,
+ },
+ });
+
+ gBrowser.removeEventListener("keypress", eventConsumer, true);
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function runTestForSuppressModifierKeys() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_keyBoardEvent.sjs?language=en-US"
+ );
+
+ // Prevent Alt key to trigger the menu item.
+ gBrowser.addEventListener("keydown", eventConsumer, true);
+
+ for (let eventType of ["keydown", "keyup"]) {
+ for (let modifierKey of ["Alt", "Shift", "Control"]) {
+ let testPromise = ContentTask.spawn(
+ tab.linkedBrowser,
+ eventType,
+ async aEventType => {
+ let inputBox = content.document.getElementById("test");
+
+ // First, try to focus on the input box.
+ await new Promise(resolve => {
+ if (content.document.activeElement == inputBox) {
+ // the input box already got focused.
+ resolve();
+ } else {
+ inputBox.onfocus = () => {
+ resolve();
+ };
+ inputBox.focus();
+ }
+ });
+
+ let event = await new Promise(resolve => {
+ inputBox.addEventListener(
+ aEventType,
+ aEvent => {
+ resolve(aEvent);
+ },
+ { once: true }
+ );
+ });
+
+ is(
+ event.key,
+ "x",
+ "'x' should be seen and the modifier key should be suppressed"
+ );
+ }
+ );
+
+ let modifierState;
+
+ if (modifierKey === "Alt") {
+ modifierState = { altKey: true };
+ } else if (modifierKey === "Shift") {
+ modifierState = { shiftKey: true };
+ } else {
+ modifierState = { ctrlKey: true };
+ }
+
+ // Generate a Alt or Shift key event.
+ BrowserTestUtils.synthesizeKey(
+ `KEY_${modifierKey}`,
+ modifierState,
+ tab.linkedBrowser
+ );
+
+ // Generate a dummy "x" key event that will only be handled if
+ // modifier key is successfully suppressed.
+ BrowserTestUtils.synthesizeKey("x", {}, tab.linkedBrowser);
+
+ await testPromise;
+ }
+ }
+
+ gBrowser.removeEventListener("keydown", eventConsumer, true);
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/browser/components/resistfingerprinting/test/browser/browser_timezone.js b/browser/components/resistfingerprinting/test/browser/browser_timezone.js
new file mode 100644
index 0000000000..66647ac787
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/browser_timezone.js
@@ -0,0 +1,154 @@
+/**
+ * Bug 1330890 - A test case for verifying Date() object of javascript will use
+ * UTC timezone after fingerprinting resistance is enabled.
+ */
+
+async function verifySpoofed() {
+ ok(true, "Running on " + content.location.origin);
+
+ SpecialPowers.Cu.getJSTestingFunctions().setTimeZone("PST8PDT");
+ is(
+ Intl.DateTimeFormat("en-US").resolvedOptions().timeZone,
+ "PST8PDT",
+ "Default time zone should have changed"
+ );
+
+ // Running in content:
+ function test() {
+ let date = new Date();
+ ok(
+ date.toString().endsWith("(Coordinated Universal Time)"),
+ "The date toString() is in UTC timezone."
+ );
+ ok(
+ date.toTimeString().endsWith("(Coordinated Universal Time)"),
+ "The date toTimeString() is in UTC timezone."
+ );
+ let dateTimeFormat = Intl.DateTimeFormat("en-US", {
+ dateStyle: "full",
+ timeStyle: "full",
+ });
+ is(
+ dateTimeFormat.resolvedOptions().timeZone,
+ "UTC",
+ "The Intl.DateTimeFormat is in UTC timezone."
+ );
+ ok(
+ dateTimeFormat.format(date).endsWith("Coordinated Universal Time"),
+ "The Intl.DateTimeFormat is formatting with the UTC timezone."
+ );
+ is(
+ date.getFullYear(),
+ date.getUTCFullYear(),
+ "The full year reports in UTC timezone."
+ );
+ is(
+ date.getMonth(),
+ date.getUTCMonth(),
+ "The month reports in UTC timezone."
+ );
+ is(date.getDate(), date.getUTCDate(), "The month reports in UTC timezone.");
+ is(date.getDay(), date.getUTCDay(), "The day reports in UTC timezone.");
+ is(
+ date.getHours(),
+ date.getUTCHours(),
+ "The hours reports in UTC timezone."
+ );
+ is(date.getTimezoneOffset(), 0, "The difference with UTC timezone is 0.");
+ }
+
+ // Run test in the context of the page.
+ Cu.exportFunction(is, content, { defineAs: "is" });
+ Cu.exportFunction(ok, content, { defineAs: "ok" });
+ content.eval(`(${test})()`);
+}
+
+add_task(async function test_timezone() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+
+ // Load a page and verify the timezone.
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "file_dummy.html",
+ forceNewProcess: true,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], verifySpoofed);
+
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Verify that exempted domain is not spoofed.
+add_task(async function test_timezone_exempt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting.exemptedDomains", "example.net"],
+ ["privacy.resistFingerprinting", true],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: TEST_PATH + "file_dummy.html",
+ forceNewProcess: true,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ SpecialPowers.Cu.getJSTestingFunctions().setTimeZone("PST8PDT");
+ is(
+ Intl.DateTimeFormat("en-US").resolvedOptions().timeZone,
+ "PST8PDT",
+ "Default time zone should have changed"
+ );
+
+ function test() {
+ let date = new Date(0);
+ ok(
+ date.toString().endsWith("(Pacific Standard Time)"),
+ "The date toString() is in PT timezone"
+ );
+
+ is(
+ Intl.DateTimeFormat("en-US").resolvedOptions().timeZone,
+ "PST8PDT",
+ "Content should use default time zone"
+ );
+ }
+
+ // Run test in the context of the page.
+ Cu.exportFunction(is, content, { defineAs: "is" });
+ Cu.exportFunction(ok, content, { defineAs: "ok" });
+ content.eval(`(${test})()`);
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Verify that we are still spoofing for domains not `exemptedDomains` list.
+add_task(async function test_timezone_exempt_wrong_domain() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting.exemptedDomains", "example.net"],
+ ["privacy.resistFingerprinting", true],
+ ],
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening:
+ TEST_PATH.replace("example.net", "example.org") + "file_dummy.html",
+ forceNewProcess: true,
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], verifySpoofed);
+
+ BrowserTestUtils.removeTab(tab);
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/components/resistfingerprinting/test/browser/coop_header.sjs b/browser/components/resistfingerprinting/test/browser/coop_header.sjs
new file mode 100644
index 0000000000..16c8eded66
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/coop_header.sjs
@@ -0,0 +1,77 @@
+const HTML_DATA = `
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+ <title>Dummy test page</title>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+ </head>
+ <body>
+ <p>Dummy test page</p>
+ <div id="testDiv">test</div>
+ </body>
+ </html>
+ `;
+
+const WORKER = `
+ self.onmessage = function(e) {
+ if (e.data.type == "runCmdAndGetResult") {
+ let result = eval(e.data.cmd);
+
+ postMessage({ type: "result", resultOf: e.data.cmd, result: result });
+ return;
+ } else if (e.data.type == "runCmds") {
+ for (let cmd of e.data.cmds) {
+ eval(cmd);
+ }
+
+ postMessage({ type: "result", resultOf: "entriesLength", result: performance.getEntries().length });
+ return;
+ } else if (e.data.type == "getResult") {
+ if (e.data.resultOf == "startTimeAndDuration") {
+ postMessage({ type: "result",
+ resultOf: "startTimeAndDuration",
+ result: {
+ index: e.data.num,
+ startTime: performance.getEntries()[e.data.num].startTime,
+ duration: performance.getEntries()[e.data.num].duration,
+ }
+ });
+ return;
+ } else if (e.data.resultOf == "getEntriesByTypeLength") {
+ postMessage({
+ type: "result",
+ resultOf: "entriesByTypeLength",
+ result: {
+ markLength: performance.getEntriesByType("mark").length,
+ resourceLength: performance.getEntriesByType("resource").length,
+ testAndMarkLength: performance.getEntriesByName("Test", "mark").length,
+ }
+ });
+ return;
+ }
+ }
+
+ postMessage({ type: "unexpectedMessageType", data: e.data.type });
+ };
+ `;
+
+function handleRequest(request, response) {
+ Cu.importGlobalProperties(["URLSearchParams"]);
+ let query = new URLSearchParams(request.queryString);
+
+ if (query.get("crossOriginIsolated") === "true") {
+ response.setHeader("Cross-Origin-Opener-Policy", "same-origin", false);
+ response.setHeader("Cross-Origin-Embedder-Policy", "require-corp", false);
+ }
+
+ if (query.get("worker")) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/javascript");
+ response.write(WORKER);
+ return;
+ }
+
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(HTML_DATA);
+}
diff --git a/browser/components/resistfingerprinting/test/browser/file_animationapi_iframee.html b/browser/components/resistfingerprinting/test/browser/file_animationapi_iframee.html
new file mode 100644
index 0000000000..da86656bd4
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_animationapi_iframee.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf8">
+<script>
+function waitForCondition(aCond, aCallback, aErrorMsg) {
+ var tries = 0;
+ var interval = setInterval(() => {
+ if (tries >= 30) {
+ result.push({
+ 'error': `Exceeded 30 tries waiting for animation`
+ })
+ clearInterval(interval);
+ parent.postMessage(result, "*");
+ return;
+ }
+
+ var conditionPassed;
+ try {
+ conditionPassed = aCond();
+ } catch (e) {
+ result.push({
+ 'error': `${e}\n${e.stack}`
+ })
+ clearInterval(interval);
+ parent.postMessage(result, "*")
+ return;
+ }
+
+ if (conditionPassed) {
+ clearInterval(interval);
+ aCallback();
+ }
+
+ tries++;
+ }, 100);
+ }
+
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+var result = [];
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+
+ const testDiv = document.getElementById("testDiv");
+ const animation = testDiv.animate({ opacity: [0, 1] }, 100000);
+ animation.play();
+
+ waitForCondition(
+ () => animation.currentTime > 100,
+ () => {
+
+ result.push({
+ 'name': 'animation.startTime',
+ 'value': animation.startTime
+ });
+ result.push({
+ 'name': 'animation.currentTime',
+ 'value': animation.currentTime
+ });
+ result.push({
+ 'name': 'animation.timeline.currentTime',
+ 'value': animation.timeline.currentTime
+ });
+
+ if (document.timeline) {
+ result.push({
+ 'name': 'document.timeline.currentTime',
+ 'value': document.timeline.currentTime
+ });
+ }
+
+ parent.postMessage(result, "*")
+ },
+ "animation failed to start");
+ }
+});
+</script>
+</head>
+<body>
+<div id="testDiv">test</div>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_animationapi_iframer.html b/browser/components/resistfingerprinting/test/browser/file_animationapi_iframer.html
new file mode 100644
index 0000000000..234661a6a9
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_animationapi_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain, extraData) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_animationapi_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_dummy.html b/browser/components/resistfingerprinting/test/browser/file_dummy.html
new file mode 100644
index 0000000000..000a9a9694
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_dummy.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframee.html
new file mode 100644
index 0000000000..79edbefe24
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframee.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script>
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ var iframe = document.createElement("iframe");
+ iframe.src = "about:blank?foo";
+ document.body.append(iframe);
+
+ function test() {
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+
+ window.parent.document.querySelector("#result").textContent = JSON.stringify(result);
+ }
+
+ iframe.contentWindow.eval(`(${test})()`);
+
+ parent.postMessage(JSON.parse(document.querySelector("#result").textContent), "*")
+ }
+});
+</script>
+<output id="result"></output>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframer.html
new file mode 100644
index 0000000000..89725fe157
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_popupmaker.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_popupmaker.html
new file mode 100644
index 0000000000..23fd058c44
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutblank_popupmaker.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="shared_test_funcs.js"></script>
+<script>
+var popup = undefined;
+function createPopup() {
+ if(popup === undefined) {
+ console.log("TKTK: Creating popup");
+ let s = `
+ window.addEventListener('message', async function listener(event) {
+ if (event.data[0] == 'popup_is_ready') {
+ window.opener.postMessage(["popup_ready"], "*");
+ } else if (event.data[0] == 'popup_request') {
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ window.opener.postMessage(['popup_response', result], '*');
+ window.close();
+ }
+ });
+ setInterval(function() {
+ if(!window.opener || window.opener.closed) {
+ window.close();
+ }
+ }, 50);`;
+
+ popup = window.open("about:blank", "");
+ popup.eval(s);
+ } else {
+ console.log("TKTK: popup already exists");
+ }
+}
+window.addEventListener("load", createPopup);
+console.log("TKTK: Adding initial load");
+
+async function runTheTest(iframe_domain, cross_origin_domain, mode) {
+ await new Promise(r => setTimeout(r, 2000));
+ console.log("TKTK: runTheTest() popup =", (popup === undefined ? "undefined" : "something"));
+ if (document.readyState !== 'complete') {
+ console.log("TKTK: !== complete");
+ createPopup();
+ } else if(popup === undefined) {
+ console.log("TKTK: popup is undefined");
+ createPopup();
+ }
+ console.log("TKTK: now popup =", (popup === undefined ? "undefined" : "something"));
+ popup.postMessage(["popup_is_ready", cross_origin_domain], "*");
+ await waitForMessage("popup_ready", `*`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ resolve(event.data[1]);
+ }, { once: true });
+ });
+
+ popup.postMessage(["popup_request", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ popup.close();
+
+ return result;
+}
+
+</script>
+<output id="result"></output>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframee.html
new file mode 100644
index 0000000000..79958d0a3f
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframee.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script>
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ var s = `<html><script>
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ window.parent.document.querySelector('#result').textContent = JSON.stringify(result);
+ window.parent.postMessage(["frame_response"], "*");`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ var iframe = document.createElement("iframe");
+ iframe.srcdoc = s;
+ document.body.append(iframe);
+
+ } else if (event.data[0] == "frame_response") {
+ let result = document.querySelector("#result").textContent;
+ parent.postMessage(JSON.parse(result), "*");
+ }
+});
+</script>
+<output id="result"></output>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframer.html
new file mode 100644
index 0000000000..2f44be0f5a
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_aboutsrcdoc_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframee.html
new file mode 100644
index 0000000000..adc2e93789
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframee.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script type="text/javascript">
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+//window.addEventListener("load", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ // eslint-disable-next-line
+ var s = `<html><script>
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ window.parent.document.querySelector('#result').textContent = JSON.stringify(result);
+ window.parent.postMessage(["frame_response"], "*");`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ let b = new Blob([s], { type: "text/html" });
+ let url = URL.createObjectURL(b);
+
+ var iframe = document.createElement("iframe");
+ iframe.src = url;
+ document.body.append(iframe);
+ } else if (event.data[0] == "frame_response") {
+ let result = document.querySelector("#result").textContent;
+ parent.postMessage(JSON.parse(result), "*");
+ }
+});
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframer.html
new file mode 100644
index 0000000000..e8e1c22990
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html
new file mode 100644
index 0000000000..ae08111e61
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blob_popupmaker.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="shared_test_funcs.js"></script>
+<script type="text/javascript">
+var popup;
+async function runTheTest(iframe_domain, cross_origin_domain, mode) {
+ let s = `<html><script>
+ console.log("TKTK: Loaded popup");
+ function give_result() {
+ console.log("TKTK: popup: give_result()");
+ return {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ }
+ window.addEventListener('load', async function listener(event) {
+ console.log("TKTK: popup: loaded");
+ window.opener.postMessage(["popup_ready"], "*");
+ });
+ window.addEventListener('message', async function listener(event) {
+ console.log("TKTK: popup: message");
+ if (event.data[0] == 'popup_request') {
+ console.log("TKTK: popup: popup_request");
+ let result = give_result();
+ window.opener.postMessage(['popup_response', result], '*');
+ window.close();
+ }
+ });`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ let params = new URLSearchParams(document.location.search);
+ let options = "";
+ if (params.get("submode") == "noopener") {
+ options = "noopener";
+ }
+
+ let b = new Blob([s], { type: "text/html" });
+ let url = URL.createObjectURL(b);
+ popup = window.open(url, "", options);
+
+ if (params.get("submode") == "noopener") {
+ return {};
+ }
+
+ console.log("TKTK: popup created");
+ await waitForMessage("popup_ready", `*`);
+ console.log("TKTK: got ready message");
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ console.log("TKTK: got response message");
+ resolve(event.data[1]);
+ }, { once: true });
+ });
+
+ popup.postMessage(["popup_request", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ popup.close();
+ console.log("TKTK: closed popup");
+
+ return result;
+}
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframee.html
new file mode 100644
index 0000000000..235a7651a8
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframee.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script type="text/javascript">
+
+ var s = `<html><script>
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ window.parent.postMessage(result, "*");`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ let b = new Blob([s], { type: "text/html" });
+ let url = URL.createObjectURL(b);
+ location.href = url;
+
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframer.html
new file mode 100644
index 0000000000..d03c514fc7
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframer.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ // Set up the frame
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_blobcrossorigin_iframee.html`;
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ var result = await promiseForRFPTest;
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframee.html
new file mode 100644
index 0000000000..befd43091a
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframee.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script type="text/javascript">
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ var s = `<html><script>
+ window.addEventListener("load", async function listener(event) {
+ parent.postMessage(["frame_ready"], "*");
+ });
+ window.addEventListener('message', async function listener(event) {
+ if (event.data[0] == 'frame_request') {
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ parent.postMessage(['frame_response', result], '*');
+ }
+ });`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ let iframe = document.createElement("iframe");
+ iframe.src = "data:text/html;charset=utf-8," + s;
+ document.body.append(iframe);
+ } else if (event.data[0] == "frame_ready") {
+ let iframe = document.getElementsByTagName("iframe")[0];
+ iframe.contentWindow.postMessage(["frame_request"], "*");
+ } else if (event.data[0] == "frame_response") {
+ parent.postMessage(event.data[1], "*")
+ }
+});
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframer.html
new file mode 100644
index 0000000000..0abfa55062
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html
new file mode 100644
index 0000000000..188d78ee6e
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_data_popupmaker.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="shared_test_funcs.js"></script>
+<script type="text/javascript">
+var popup;
+async function runTheTest(iframe_domain, cross_origin_domain, mode) {
+ let s = `<!DOCTYPE html><html><script>
+ function give_result() {
+ return {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+ }
+ window.addEventListener('load', async function listener(event) {
+ window.opener.postMessage(["popup_ready"], "*");
+ });
+ window.addEventListener('message', async function listener(event) {
+ if (event.data[0] == 'popup_is_ready') {
+ window.opener.postMessage(["popup_ready"], "*");
+ } else if (event.data[0] == 'popup_request') {
+ let result = give_result();
+ window.opener.postMessage(['popup_response', result], '*');
+ }
+ });`;
+ // eslint-disable-next-line
+ s += `</` + `script></html>`;
+
+ let params = new URLSearchParams(document.location.search);
+ let options = "";
+ if (params.get("submode") == "noopener") {
+ options = "noopener";
+ }
+
+ let url = "data:text/html;charset=utf-8," + s;
+ popup = window.open(url, "", options);
+
+ if (params.get("submode") == "noopener") {
+ return {};
+ }
+
+ await waitForMessage("popup_ready", `*`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ resolve(event.data[1]);
+ }, { once: true });
+ });
+
+ popup.postMessage(["popup_request", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ popup.close();
+
+ return result;
+}
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html
new file mode 100644
index 0000000000..b66d97563c
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script>
+var parent_window;
+let params = new URLSearchParams(document.location.search);
+if (params.get("mode") == "popup") {
+ parent_window = window.opener;
+} else {
+ parent_window = window.parent;
+}
+
+window.onload = async () => {
+ parent_window.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ let result = give_result();
+ parent_window.postMessage(result, "*")
+ }
+});
+
+function give_result() {
+ return {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+}
+</script>
+<output id="result"></output>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html
new file mode 100644
index 0000000000..3de74bc9a3
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframer.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain, mode) {
+ var child_reference;
+ let url = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_iframee.html?mode=`
+ let params = new URLSearchParams(document.location.search);
+
+ if (params.get("mode") == 'iframe') {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = url + 'iframe';
+ child_reference = iframes[0].contentWindow;
+ } else if (params.get("mode") == "popup") {
+ let options = "";
+ if (params.get("submode") == "noopener") {
+ options = "noopener";
+ }
+ const popup = window.open(url + 'popup', '', options);
+ if (params.get("submode") == "noopener") {
+ return {};
+ }
+ child_reference = popup;
+ } else {
+ throw new Error("Unknown page mode specified");
+ }
+
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ child_reference.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ if (params.get("mode") == "popup") {
+ child_reference.close();
+ }
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_double_framee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_double_framee.html
new file mode 100644
index 0000000000..8a4373c703
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_double_framee.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<body>
+<output id="result"></output>
+<script type="text/javascript">
+ window.addEventListener("load", function listener(event) {
+ parent.postMessage(["frame_ready"], "*");
+ });
+ window.addEventListener("message", function listener(event) {
+ if (event.data[0] == "gimme") {
+ let result = {
+ hardwareConcurrency : navigator.hardwareConcurrency
+ };
+
+ parent.postMessage(["frame_response", result], "*");
+ }
+ });
+</script>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframee.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframee.html
new file mode 100644
index 0000000000..3ae4453928
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframee.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script type="text/javascript">
+window.onload = () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", function listener(event) {
+ if (event.data[0] == "gimme") {
+ let iframe = document.createElement("iframe");
+ iframe.src = "/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_double_framee.html";
+ iframe.sandbox = "allow-scripts";
+ document.body.append(iframe);
+
+ } else if (event.data[0] == "frame_ready") {
+ let iframe = document.getElementsByTagName("iframe")[0];
+ iframe.contentWindow.postMessage({0: "gimme"}, "*");
+ } else if (event.data[0] == "frame_response") {
+ parent.postMessage(event.data[1], "*")
+ }
+});
+</script>
+<body>
+<output id="result"></output>
+</body>
diff --git a/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframer.html b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframer.html
new file mode 100644
index 0000000000..60890c0260
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframer.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_hwconcurrency_sandboxediframe_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_keyBoardEvent.sjs b/browser/components/resistfingerprinting/test/browser/file_keyBoardEvent.sjs
new file mode 100644
index 0000000000..5e3c0e2e57
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_keyBoardEvent.sjs
@@ -0,0 +1,64 @@
+"use strict";
+
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+const HTML_DATA = `
+ <!DOCTYPE HTML>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1222285</title>
+ <script type="application/javascript">
+ function populateKeyEventResult(aEvent) {
+ let result = document.getElementById("result-" + aEvent.type);
+ let data = {
+ key: aEvent.key,
+ code: aEvent.code,
+ location: aEvent.location,
+ altKey: aEvent.altKey,
+ shiftKey: aEvent.shiftKey,
+ ctrlKey: aEvent.ctrlKey,
+ charCode: aEvent.charCode,
+ keyCode: aEvent.keyCode,
+ modifierState: {
+ Alt: aEvent.getModifierState("Alt"),
+ AltGraph: aEvent.getModifierState("AltGraph"),
+ Shift: aEvent.getModifierState("Shift"),
+ Control: aEvent.getModifierState("Control")
+ }
+ };
+
+ result.value = JSON.stringify(data);
+ result.dispatchEvent(new CustomEvent("resultAvailable"));
+ }
+
+ window.onload = () => {
+ for (event of ["keydown", "keypress", "keyup"]) {
+ document.getElementById("test")
+ .addEventListener(event, populateKeyEventResult);
+ }
+ }
+ </script>
+ </head>
+ <body>
+ <input id="test"/>
+ <input id="result-keydown"/>
+ <input id="result-keypress"/>
+ <input id="result-keyup"/>
+ </body>
+ </html>`;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/html", false);
+
+ let queryString = new URLSearchParams(request.queryString);
+
+ let language = queryString.get("language");
+
+ if (language) {
+ response.setHeader("Content-Language", language, false);
+ }
+
+ response.write(HTML_DATA);
+}
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigator.html b/browser/components/resistfingerprinting/test/browser/file_navigator.html
new file mode 100644
index 0000000000..73029a2168
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+<title>Test page for navigator object</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+<script>
+ // This page will collect information from the navigator object and store
+ // the result at a paragraph in the page.
+ function collect() {
+ let result = {};
+
+ result.appCodeName = navigator.appCodeName;
+ result.appName = navigator.appName;
+ result.appVersion = navigator.appVersion;
+ result.platform = navigator.platform;
+ result.userAgent = navigator.userAgent;
+ result.product = navigator.product;
+ result.productSub = navigator.productSub;
+ result.vendor = navigator.vendor;
+ result.vendorSub = navigator.vendorSub;
+ result.mimeTypesLength = navigator.mimeTypes.length;
+ result.pluginsLength = navigator.plugins.length;
+ result.oscpu = navigator.oscpu;
+ result.hardwareConcurrency = navigator.hardwareConcurrency;
+
+ // eslint-disable-next-line no-unsanitized/property
+ document.getElementById("result").innerHTML = JSON.stringify(result);
+ }
+</script>
+</head>
+<body onload="collect();">
+<p id="result"></p>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigatorWorker.js b/browser/components/resistfingerprinting/test/browser/file_navigatorWorker.js
new file mode 100644
index 0000000000..b0a8e308ff
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigatorWorker.js
@@ -0,0 +1,19 @@
+/* eslint-env worker */
+
+onconnect = function (e) {
+ let port = e.ports[0];
+
+ let navigatorObj = self.navigator;
+ let result = {};
+
+ result.appCodeName = navigatorObj.appCodeName;
+ result.appName = navigatorObj.appName;
+ result.appVersion = navigatorObj.appVersion;
+ result.platform = navigatorObj.platform;
+ result.userAgent = navigatorObj.userAgent;
+ result.product = navigatorObj.product;
+ result.hardwareConcurrency = navigatorObj.hardwareConcurrency;
+
+ port.postMessage(JSON.stringify(result));
+ port.start();
+};
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs b/browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs
new file mode 100644
index 0000000000..0afa1f1b41
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs
@@ -0,0 +1,12 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain; charset=UTF-8", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+
+ if (request.hasHeader("user-agent")) {
+ response.write(request.getHeader("user-agent"));
+ } else {
+ response.write("no user agent header");
+ }
+}
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigator_iframe_worker.sjs b/browser/components/resistfingerprinting/test/browser/file_navigator_iframe_worker.sjs
new file mode 100644
index 0000000000..cbb54d06bc
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator_iframe_worker.sjs
@@ -0,0 +1,25 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "application/javascript");
+
+ let content = `onconnect = function(e) {
+ let port = e.ports[0];
+
+ let navigatorObj = self.navigator;
+ let result = {};
+
+ result.appCodeName = navigatorObj.appCodeName;
+ result.appName = navigatorObj.appName;
+ result.appVersion = navigatorObj.appVersion;
+ result.platform = navigatorObj.platform;
+ result.userAgent = navigatorObj.userAgent;
+ result.product = navigatorObj.product;
+ result.hardwareConcurrency = navigatorObj.hardwareConcurrency;
+
+ port.postMessage(result);
+ port.start();
+ };`;
+
+ response.write(content);
+}
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigator_iframee.html b/browser/components/resistfingerprinting/test/browser/file_navigator_iframee.html
new file mode 100644
index 0000000000..8e312d1d7b
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator_iframee.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script>
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ let cross_origin_domain = event.data[1];
+ var result = {};
+
+ result.appCodeName = navigator.appCodeName;
+ result.appName = navigator.appName;
+ result.appVersion = navigator.appVersion;
+ result.platform = navigator.platform;
+ result.userAgent = navigator.userAgent;
+ result.product = navigator.product;
+ result.productSub = navigator.productSub;
+ result.vendor = navigator.vendor;
+ result.vendorSub = navigator.vendorSub;
+ result.mimeTypesLength = navigator.mimeTypes.length;
+ result.pluginsLength = navigator.plugins.length;
+ result.oscpu = navigator.oscpu;
+ result.hardwareConcurrency = navigator.hardwareConcurrency;
+ result.userAgentHTTPHeader = "unknown";
+
+ let worker = new SharedWorker("file_navigator_iframe_worker.sjs");
+ let worker_result = await new Promise(resolve => {
+ worker.port.onmessage = function(e) {
+ resolve(e.data);
+ };
+ });
+
+ result.worker_appCodeName = worker_result.appCodeName;
+ result.worker_appName = worker_result.appName;
+ result.worker_appVersion = worker_result.appVersion;
+ result.worker_platform = worker_result.platform;
+ result.worker_userAgent = worker_result.userAgent;
+ result.worker_product = worker_result.product;
+ result.worker_hardwareConcurrency = worker_result.hardwareConcurrency;
+
+ var one = fetch("file_navigator_header.sjs?")
+ .then((response) => { return response.text(); })
+ .then((content) => {
+ result.userAgentHTTPHeader = content;
+ });
+
+ var two = fetch(`https://${cross_origin_domain}/browser/browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs?`)
+ .then((response) => { return response.text(); })
+ .then((content) => {
+ result.framee_crossOrigin_userAgentHTTPHeader = content;
+ });
+
+ Promise.all([one, two]).then((values) => {
+ parent.postMessage(result, "*")
+ });
+ }
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html b/browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html
new file mode 100644
index 0000000000..3498393904
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_navigator_iframer.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_navigator_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ await fetch(`https://${cross_origin_domain}/browser/browser/components/resistfingerprinting/test/browser/file_navigator_header.sjs?`)
+ .then((response) => { return response.text(); })
+ .then((content) => {
+ result.framer_crossOrigin_userAgentHTTPHeader = content;
+ });
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframe_worker.sjs b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframe_worker.sjs
new file mode 100644
index 0000000000..7791c05c06
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframe_worker.sjs
@@ -0,0 +1,34 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "application/javascript");
+
+ let content = `onconnect = function(e) {
+ let port = e.ports[0];
+
+ let navigatorObj = self.navigator;
+ let result = [];
+
+ // Known ways to generate time stamps, in milliseconds
+ const timeStampCodes = [
+ 'performance.now()',
+ 'new Date().getTime()',
+ 'new Event("").timeStamp',
+ 'new File([], "").lastModified',
+ ];
+
+ for (let timeStampCode of timeStampCodes) {
+ let timeStamp = eval(timeStampCode);
+
+ result.push({
+ 'name': 'worker ' + timeStampCode,
+ 'value': timeStamp
+ });
+ }
+
+ port.postMessage(result);
+ port.start();
+ };`;
+
+ response.write(content);
+}
diff --git a/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframee.html b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframee.html
new file mode 100644
index 0000000000..d336fa8d85
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframee.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script>
+window.onload = async () => {
+ parent.postMessage("ready", "*");
+}
+
+window.addEventListener("message", async function listener(event) {
+ if (event.data[0] == "gimme") {
+ var result = [];
+
+ // Prepare for test of AudioContext.currentTime
+ // eslint-disable-next-line
+ let audioContext = new AudioContext();
+
+ // Known ways to generate time stamps, in milliseconds
+ const timeStampCodes = [
+ "performance.now()",
+ "new Date().getTime()",
+ "new Event(\"\").timeStamp",
+ "new File([], \"\").lastModified",
+ ];
+ // These are measured in seconds, so we need to scale them up
+ var timeStampCodesDOM = timeStampCodes.concat([
+ "audioContext.currentTime * 1000",
+ ]);
+
+ for (let timeStampCode of timeStampCodesDOM) {
+ // eslint-disable-next-line no-eval
+ let timeStamp = eval(timeStampCode);
+
+ result.push({
+ 'name': timeStampCode,
+ 'value': timeStamp
+ });
+ }
+
+ let worker = new SharedWorker("file_reduceTimePrecision_iframe_worker.sjs");
+ let worker_result = await new Promise(resolve => {
+ worker.port.onmessage = function(e) {
+ resolve(e.data);
+ };
+ });
+
+ for (let item of worker_result) {
+ result.push(item);
+ }
+
+ parent.postMessage(result, "*")
+ }
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframer.html b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframer.html
new file mode 100644
index 0000000000..4d9c81ec8d
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title></title>
+<script src="shared_test_funcs.js"></script>
+<script>
+async function runTheTest(iframe_domain, cross_origin_domain, extraData) {
+ const iframes = document.querySelectorAll("iframe");
+ iframes[0].src = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_reduceTimePrecision_iframee.html`;
+ await waitForMessage("ready", `https://${iframe_domain}`);
+
+ const promiseForRFPTest = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if(event.origin != `https://${iframe_domain}`) {
+ throw new Error(`origin should be ${iframe_domain}`);
+ }
+ resolve(event.data);
+ }, { once: true });
+ });
+ iframes[0].contentWindow.postMessage(["gimme", cross_origin_domain], "*");
+ var result = await promiseForRFPTest;
+
+ return result;
+}
+</script>
+</head>
+<body>
+<iframe width=100></iframe>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/browser/file_workerNetInfo.js b/browser/components/resistfingerprinting/test/browser/file_workerNetInfo.js
new file mode 100644
index 0000000000..2d21fa6f5e
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_workerNetInfo.js
@@ -0,0 +1,30 @@
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({ type: "finish" });
+}
+
+function runTests() {
+ ok("connection" in navigator, "navigator.connection should exist");
+ is(
+ navigator.connection.type,
+ "unknown",
+ "The connection type is spoofed correctly"
+ );
+
+ finish();
+}
+
+self.onmessage = function (e) {
+ if (e.data.type === "runTests") {
+ runTests();
+ } else {
+ ok(false, "Unknown message type");
+ }
+};
diff --git a/browser/components/resistfingerprinting/test/browser/file_workerPerformance.js b/browser/components/resistfingerprinting/test/browser/file_workerPerformance.js
new file mode 100644
index 0000000000..4533073180
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/file_workerPerformance.js
@@ -0,0 +1,122 @@
+function ok(a, msg) {
+ postMessage({ type: "status", status: !!a, msg });
+}
+
+function is(a, b, msg) {
+ ok(a === b, msg);
+}
+
+function finish() {
+ postMessage({ type: "finish" });
+}
+
+let isRounded = (x, expectedPrecision) => {
+ let rounded = Math.floor(x / expectedPrecision) * expectedPrecision;
+ // First we do the perfectly normal check that should work just fine
+ if (rounded === x || x === 0) {
+ return true;
+ }
+
+ // When we're diving by non-whole numbers, we may not get perfect
+ // multiplication/division because of floating points.
+ // When dealing with ms since epoch, a double's precision is on the order
+ // of 1/5 of a microsecond, so we use a value a little higher than that as
+ // our epsilon.
+ // To be clear, this error is introduced in our re-calculation of 'rounded'
+ // above in JavaScript.
+ if (Math.abs(rounded - x + expectedPrecision) < 0.0005) {
+ return true;
+ } else if (Math.abs(rounded - x) < 0.0005) {
+ return true;
+ }
+
+ // Then we handle the case where you're sub-millisecond and the timer is not
+ // We check that the timer is not sub-millisecond by assuming it is not if it
+ // returns an even number of milliseconds
+ if (expectedPrecision < 1 && Math.round(x) == x) {
+ if (Math.round(rounded) == x) {
+ return true;
+ }
+ }
+
+ ok(
+ false,
+ "Looming Test Failure, Additional Debugging Info: Expected Precision: " +
+ expectedPrecision +
+ " Measured Value: " +
+ x +
+ " Rounded Vaue: " +
+ rounded +
+ " Fuzzy1: " +
+ Math.abs(rounded - x + expectedPrecision) +
+ " Fuzzy 2: " +
+ Math.abs(rounded - x)
+ );
+
+ return false;
+};
+
+function runTimerTests(expectedPrecision) {
+ ok(
+ isRounded(performance.timeOrigin, expectedPrecision),
+ `In a worker, for reduceTimerPrecision, performance.timeOrigin is not correctly rounded: ` +
+ performance.timeOrigin
+ );
+
+ // Try to add some entries.
+ performance.mark("Test");
+ performance.mark("Test-End");
+ performance.measure("Test-Measure", "Test", "Test-End");
+
+ // Check the entries in performance.getEntries/getEntriesByType/getEntriesByName.
+ is(
+ performance.getEntries().length,
+ 3,
+ "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntries() for workers: " +
+ performance.getEntries().length
+ );
+ for (var i = 0; i < 3; i++) {
+ let startTime = performance.getEntries()[i].startTime;
+ let duration = performance.getEntries()[i].duration;
+ ok(
+ isRounded(startTime, expectedPrecision),
+ "In a worker, for reduceTimerPrecision(" +
+ expectedPrecision +
+ "), performance.getEntries(" +
+ i.toString() +
+ ").startTime is not rounded: " +
+ startTime.toString()
+ );
+ ok(
+ isRounded(duration, expectedPrecision),
+ "In a worker, for reduceTimerPrecision(" +
+ expectedPrecision +
+ "), performance.getEntries(" +
+ i.toString() +
+ ").duration is not rounded: " +
+ duration.toString()
+ );
+ }
+ is(
+ performance.getEntriesByType("mark").length,
+ 2,
+ "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByType() for workers: " +
+ performance.getEntriesByType("resource").length
+ );
+ is(
+ performance.getEntriesByName("Test", "mark").length,
+ 1,
+ "In a worker, for reduceTimerPrecision: Incorrect number of entries for performance.getEntriesByName() for workers: " +
+ performance.getEntriesByName("Test", "mark").length
+ );
+
+ finish();
+}
+
+self.onmessage = function (e) {
+ if (e.data.type === "runTimerTests") {
+ runTimerTests(e.data.precision);
+ } else {
+ ok(false, "Unknown message type");
+ }
+};
diff --git a/browser/components/resistfingerprinting/test/browser/head.js b/browser/components/resistfingerprinting/test/browser/head.js
new file mode 100644
index 0000000000..244bd578ef
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/head.js
@@ -0,0 +1,1063 @@
+/* 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/. */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ ContentBlockingAllowList:
+ "resource://gre/modules/ContentBlockingAllowList.sys.mjs",
+});
+
+const TEST_PATH =
+ "http://example.net/browser/browser/" +
+ "components/resistfingerprinting/test/browser/";
+
+const PERFORMANCE_TIMINGS = [
+ "navigationStart",
+ "unloadEventStart",
+ "unloadEventEnd",
+ "redirectStart",
+ "redirectEnd",
+ "fetchStart",
+ "domainLookupStart",
+ "domainLookupEnd",
+ "connectStart",
+ "connectEnd",
+ "secureConnectionStart",
+ "requestStart",
+ "responseStart",
+ "responseEnd",
+ "domLoading",
+ "domInteractive",
+ "domContentLoadedEventStart",
+ "domContentLoadedEventEnd",
+ "domComplete",
+ "loadEventStart",
+ "loadEventEnd",
+];
+
+/**
+ * Sets up tests for making sure that performance APIs have been correctly
+ * spoofed or disabled.
+ */
+let setupPerformanceAPISpoofAndDisableTest = async function (
+ resistFingerprinting,
+ reduceTimerPrecision,
+ crossOriginIsolated,
+ expectedPrecision,
+ runTests,
+ workerCall
+) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", resistFingerprinting],
+ ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ expectedPrecision * 1000,
+ ],
+ ["browser.tabs.remote.useCrossOriginOpenerPolicy", crossOriginIsolated],
+ ["browser.tabs.remote.useCrossOriginEmbedderPolicy", crossOriginIsolated],
+ ],
+ });
+
+ let url = crossOriginIsolated
+ ? `https://example.com/browser/browser/components/resistfingerprinting` +
+ `/test/browser/coop_header.sjs?crossOriginIsolated=${crossOriginIsolated}`
+ : TEST_PATH + "file_dummy.html";
+
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
+
+ // No matter what we set the precision to, if we're in ResistFingerprinting
+ // mode we use the larger of the precision pref and the RFP time-atom constant
+ if (resistFingerprinting) {
+ const RFP_TIME_ATOM_MS = 16.667;
+ expectedPrecision = Math.max(RFP_TIME_ATOM_MS, expectedPrecision);
+ }
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [
+ {
+ list: PERFORMANCE_TIMINGS,
+ resistFingerprinting,
+ precision: expectedPrecision,
+ isRoundedFunc: isTimeValueRounded.toString(),
+ workerCall,
+ },
+ ],
+ runTests
+ );
+
+ if (crossOriginIsolated) {
+ let remoteType = tab.linkedBrowser.remoteType;
+ ok(
+ remoteType.startsWith(E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX),
+ `${remoteType} expected to be coop+coep`
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+};
+
+let isTimeValueRounded = (x, expectedPrecision, console) => {
+ const nearestExpected = Math.round(x / expectedPrecision) * expectedPrecision;
+ // First we do the perfectly normal check that should work just fine
+ if (x === nearestExpected) {
+ return true;
+ }
+
+ // When we're dividing by non-whole numbers, we may not get perfect
+ // multiplication/division because of floating points.
+ // When dealing with ms since epoch, a double's precision is on the order
+ // of 1/5 of a microsecond, so we use a value a little higher than that as
+ // our epsilon.
+ // To be clear, this error is introduced in our re-calculation of 'rounded'
+ // above in JavaScript.
+ const error = Math.abs(x - nearestExpected);
+
+ if (console) {
+ console.log(
+ "Additional Debugging Info: Expected Precision: " +
+ expectedPrecision +
+ " Measured Value: " +
+ x +
+ " Nearest Expected Vaue: " +
+ nearestExpected +
+ " Error: " +
+ error
+ );
+ }
+
+ if (Math.abs(error) < 0.0005) {
+ return true;
+ }
+
+ // Then we handle the case where you're sub-millisecond and the timer is not
+ // We check that the timer is not sub-millisecond by assuming it is not if it
+ // returns an even number of milliseconds
+ if (
+ Math.round(expectedPrecision) != expectedPrecision &&
+ Math.round(x) == x
+ ) {
+ let acceptableIntRounding = false;
+ acceptableIntRounding |= Math.floor(nearestExpected) == x;
+ acceptableIntRounding |= Math.ceil(nearestExpected) == x;
+ if (acceptableIntRounding) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+let setupAndRunCrossOriginIsolatedTest = async function (
+ options,
+ expectedPrecision,
+ runTests,
+ workerCall
+) {
+ let prefsToSet = [
+ [
+ "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
+ expectedPrecision * 1000,
+ ],
+ ];
+
+ if (options.resistFingerprinting !== undefined) {
+ prefsToSet = prefsToSet.concat([
+ ["privacy.resistFingerprinting", options.resistFingerprinting],
+ ]);
+ } else {
+ options.resistFingerprinting = false;
+ }
+
+ if (options.resistFingerprintingPBMOnly !== undefined) {
+ prefsToSet = prefsToSet.concat([
+ [
+ "privacy.resistFingerprinting.pbmode",
+ options.resistFingerprintingPBMOnly,
+ ],
+ ]);
+ } else {
+ options.resistFingerprintingPBMOnly = false;
+ }
+
+ if (options.reduceTimerPrecision !== undefined) {
+ prefsToSet = prefsToSet.concat([
+ ["privacy.reduceTimerPrecision", options.reduceTimerPrecision],
+ ]);
+ } else {
+ options.reduceTimerPrecision = false;
+ }
+
+ if (options.crossOriginIsolated !== undefined) {
+ prefsToSet = prefsToSet.concat([
+ [
+ "browser.tabs.remote.useCrossOriginOpenerPolicy",
+ options.crossOriginIsolated,
+ ],
+ [
+ "browser.tabs.remote.useCrossOriginEmbedderPolicy",
+ options.crossOriginIsolated,
+ ],
+ ]);
+ } else {
+ options.crossOriginIsolated = false;
+ }
+
+ if (options.openPrivateWindow === undefined) {
+ options.openPrivateWindow = false;
+ }
+ if (options.shouldBeRounded === undefined) {
+ options.shouldBeRounded = true;
+ }
+
+ console.log(prefsToSet);
+ await SpecialPowers.pushPrefEnv({ set: prefsToSet });
+
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: options.openPrivateWindow,
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ `https://example.com/browser/browser/components/resistfingerprinting` +
+ `/test/browser/coop_header.sjs?crossOriginIsolated=${options.crossOriginIsolated}`
+ );
+
+ // No matter what we set the precision to, if we're in ResistFingerprinting
+ // mode we use the larger of the precision pref and the RFP time-atom constant
+ if (
+ options.resistFingerprinting ||
+ (options.resistFingerprintingPBMOnly && options.openPrivateWindow)
+ ) {
+ const RFP_TIME_ATOM_MS = 16.667;
+ expectedPrecision = Math.max(RFP_TIME_ATOM_MS, expectedPrecision);
+ }
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [
+ {
+ precision: expectedPrecision,
+ isRoundedFunc: isTimeValueRounded.toString(),
+ workerCall,
+ options,
+ },
+ ],
+ runTests
+ );
+
+ if (options.crossOriginIsolated) {
+ let remoteType = tab.linkedBrowser.remoteType;
+ ok(
+ remoteType.startsWith(E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX),
+ `${remoteType} expected to be coop+coep`
+ );
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+};
+
+// This function calculates the maximum available window dimensions and returns
+// them as an object.
+async function calcMaximumAvailSize(aChromeWidth, aChromeHeight) {
+ let chromeUIWidth;
+ let chromeUIHeight;
+ let testPath =
+ "http://example.net/browser/browser/" +
+ "components/resistfingerprinting/test/browser/";
+
+ // If the chrome UI dimensions is not given, we will calculate it.
+ if (!aChromeWidth || !aChromeHeight) {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ testPath + "file_dummy.html"
+ );
+
+ let contentSize = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ let result = {
+ width: content.innerWidth,
+ height: content.innerHeight,
+ };
+
+ return result;
+ }
+ );
+
+ // Calculate the maximum available window size which is depending on the
+ // available screen space.
+ chromeUIWidth = win.outerWidth - contentSize.width;
+ chromeUIHeight = win.outerHeight - contentSize.height;
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ } else {
+ chromeUIWidth = aChromeWidth;
+ chromeUIHeight = aChromeHeight;
+ }
+
+ let availWidth = window.screen.availWidth;
+ let availHeight = window.screen.availHeight;
+
+ // Ideally, we would round the window size as 1000x1000. But the available
+ // screen space might not suffice. So, we decide the size according to the
+ // available screen size.
+ let availContentWidth = Math.min(1000, availWidth - chromeUIWidth);
+ let availContentHeight;
+
+ // If it is GTK window, we would consider the system decorations when we
+ // calculating avail content height since the system decorations won't be
+ // reported when we get available screen dimensions.
+ if (AppConstants.MOZ_WIDGET_GTK) {
+ availContentHeight = Math.min(1000, -40 + availHeight - chromeUIHeight);
+ } else {
+ availContentHeight = Math.min(1000, availHeight - chromeUIHeight);
+ }
+
+ // Rounded the desire size to the nearest 200x100.
+ let maxAvailWidth = availContentWidth - (availContentWidth % 200);
+ let maxAvailHeight = availContentHeight - (availContentHeight % 100);
+
+ return { maxAvailWidth, maxAvailHeight };
+}
+
+async function calcPopUpWindowChromeUISize() {
+ let testPath =
+ "http://example.net/browser/browser/" +
+ "components/resistFingerprinting/test/browser/";
+ // open a popup window to acquire the chrome UI size of it.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ testPath + "file_dummy.html"
+ );
+
+ let result = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ let win;
+
+ await new Promise(resolve => {
+ win = content.open("about:blank", "", "width=1000,height=1000");
+ win.onload = () => resolve();
+ });
+
+ let res = {
+ chromeWidth: win.outerWidth - win.innerWidth,
+ chromeHeight: win.outerHeight - win.innerHeight,
+ };
+
+ win.close();
+
+ return res;
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ return result;
+}
+
+async function testWindowOpen(
+ aBrowser,
+ aSettingWidth,
+ aSettingHeight,
+ aTargetWidth,
+ aTargetHeight,
+ aMaxAvailWidth,
+ aMaxAvailHeight,
+ aPopupChromeUIWidth,
+ aPopupChromeUIHeight
+) {
+ // If the target size is greater than the maximum available content size,
+ // we set the target size to it.
+ if (aTargetWidth > aMaxAvailWidth) {
+ aTargetWidth = aMaxAvailWidth;
+ }
+
+ if (aTargetHeight > aMaxAvailHeight) {
+ aTargetHeight = aMaxAvailHeight;
+ }
+
+ // Create the testing window features.
+ let winFeatures = "width=" + aSettingWidth + ",height=" + aSettingHeight;
+
+ let testParams = {
+ winFeatures,
+ targetWidth: aTargetWidth,
+ targetHeight: aTargetHeight,
+ };
+
+ await SpecialPowers.spawn(aBrowser, [testParams], async function (input) {
+ // Call window.open() with window features.
+ await new Promise(resolve => {
+ let win = content.open("http://example.net/", "", input.winFeatures);
+
+ win.onload = () => {
+ is(
+ win.screen.width,
+ input.targetWidth,
+ "The screen.width has a correct rounded value"
+ );
+ is(
+ win.screen.height,
+ input.targetHeight,
+ "The screen.height has a correct rounded value"
+ );
+ is(
+ win.innerWidth,
+ input.targetWidth,
+ "The window.innerWidth has a correct rounded value"
+ );
+ is(
+ win.innerHeight,
+ input.targetHeight,
+ "The window.innerHeight has a correct rounded value"
+ );
+
+ win.close();
+ resolve();
+ };
+ });
+ });
+}
+
+async function testWindowSizeSetting(
+ aBrowser,
+ aSettingWidth,
+ aSettingHeight,
+ aTargetWidth,
+ aTargetHeight,
+ aInitWidth,
+ aInitHeight,
+ aTestOuter,
+ aMaxAvailWidth,
+ aMaxAvailHeight,
+ aPopupChromeUIWidth,
+ aPopupChromeUIHeight
+) {
+ // If the target size is greater than the maximum available content size,
+ // we set the target size to it.
+ if (aTargetWidth > aMaxAvailWidth) {
+ aTargetWidth = aMaxAvailWidth;
+ }
+
+ if (aTargetHeight > aMaxAvailHeight) {
+ aTargetHeight = aMaxAvailHeight;
+ }
+
+ let testParams = {
+ initWidth: aInitWidth,
+ initHeight: aInitHeight,
+ settingWidth: aSettingWidth + (aTestOuter ? aPopupChromeUIWidth : 0),
+ settingHeight: aSettingHeight + (aTestOuter ? aPopupChromeUIHeight : 0),
+ targetWidth: aTargetWidth,
+ targetHeight: aTargetHeight,
+ testOuter: aTestOuter,
+ };
+
+ await SpecialPowers.spawn(aBrowser, [testParams], async function (input) {
+ let win;
+ // Open a new window and wait until it loads.
+ await new Promise(resolve => {
+ // Given a initial window size which should be different from target
+ // size. We need this to trigger 'onresize' event.
+ let initWinFeatures =
+ "width=" + input.initWidth + ",height=" + input.initHeight;
+ win = content.open("http://example.net/", "", initWinFeatures);
+ win.onload = () => resolve();
+ });
+
+ // Test inner/outerWidth.
+ await new Promise(resolve => {
+ win.addEventListener(
+ "resize",
+ () => {
+ is(
+ win.screen.width,
+ input.targetWidth,
+ "The screen.width has a correct rounded value"
+ );
+ is(
+ win.innerWidth,
+ input.targetWidth,
+ "The window.innerWidth has a correct rounded value"
+ );
+
+ resolve();
+ },
+ { once: true }
+ );
+
+ if (input.testOuter) {
+ win.outerWidth = input.settingWidth;
+ } else {
+ win.innerWidth = input.settingWidth;
+ }
+ });
+
+ win.close();
+ // Open a new window and wait until it loads.
+ await new Promise(resolve => {
+ // Given a initial window size which should be different from target
+ // size. We need this to trigger 'onresize' event.
+ let initWinFeatures =
+ "width=" + input.initWidth + ",height=" + input.initHeight;
+ win = content.open("http://example.net/", "", initWinFeatures);
+ win.onload = () => resolve();
+ });
+
+ // Test inner/outerHeight.
+ await new Promise(resolve => {
+ win.addEventListener(
+ "resize",
+ () => {
+ is(
+ win.screen.height,
+ input.targetHeight,
+ "The screen.height has a correct rounded value"
+ );
+ is(
+ win.innerHeight,
+ input.targetHeight,
+ "The window.innerHeight has a correct rounded value"
+ );
+
+ resolve();
+ },
+ { once: true }
+ );
+
+ if (input.testOuter) {
+ win.outerHeight = input.settingHeight;
+ } else {
+ win.innerHeight = input.settingHeight;
+ }
+ });
+
+ win.close();
+ });
+}
+
+class RoundedWindowTest {
+ // testOuter is optional. run() can be invoked with only 1 parameter.
+ static run(testCases, testOuter) {
+ // "this" is the calling class itself.
+ // e.g. when invoked by RoundedWindowTest.run(), "this" is "class RoundedWindowTest".
+ let test = new this(testCases);
+ add_task(async () => test.setup());
+ add_task(async () => {
+ if (testOuter == undefined) {
+ // If testOuter is not given, do tests for both inner and outer.
+ await test.doTests(false);
+ await test.doTests(true);
+ } else {
+ await test.doTests(testOuter);
+ }
+ });
+ }
+
+ constructor(testCases) {
+ this.testCases = testCases;
+ }
+
+ async setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]],
+ });
+
+ // Calculate the popup window's chrome UI size for tests of outerWidth/Height.
+ let popUpChromeUISize = await calcPopUpWindowChromeUISize();
+
+ this.popupChromeUIWidth = popUpChromeUISize.chromeWidth;
+ this.popupChromeUIHeight = popUpChromeUISize.chromeHeight;
+
+ // Calculate the maximum available size.
+ let maxAvailSize = await calcMaximumAvailSize(
+ this.popupChromeUIWidth,
+ this.popupChromeUIHeight
+ );
+
+ this.maxAvailWidth = maxAvailSize.maxAvailWidth;
+ this.maxAvailHeight = maxAvailSize.maxAvailHeight;
+ }
+
+ async doTests(testOuter) {
+ // Open a tab to test.
+ this.tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "file_dummy.html"
+ );
+
+ for (let test of this.testCases) {
+ await this.doTest(test, testOuter);
+ }
+
+ BrowserTestUtils.removeTab(this.tab);
+ }
+
+ async doTest() {
+ throw new Error("RoundedWindowTest.doTest must be overridden.");
+ }
+}
+
+class WindowSettingTest extends RoundedWindowTest {
+ async doTest(test, testOuter) {
+ await testWindowSizeSetting(
+ this.tab.linkedBrowser,
+ test.settingWidth,
+ test.settingHeight,
+ test.targetWidth,
+ test.targetHeight,
+ test.initWidth,
+ test.initHeight,
+ testOuter,
+ this.maxAvailWidth,
+ this.maxAvailHeight,
+ this.popupChromeUIWidth,
+ this.popupChromeUIHeight
+ );
+ }
+}
+
+class OpenTest extends RoundedWindowTest {
+ async doTest(test) {
+ await testWindowOpen(
+ this.tab.linkedBrowser,
+ test.settingWidth,
+ test.settingHeight,
+ test.targetWidth,
+ test.targetHeight,
+ this.maxAvailWidth,
+ this.maxAvailHeight,
+ this.popupChromeUIWidth,
+ this.popupChromeUIHeight
+ );
+ }
+}
+
+// ============================================================
+const FRAMER_DOMAIN = "example.com";
+const IFRAME_DOMAIN = "example.org";
+const CROSS_ORIGIN_DOMAIN = "example.net";
+
+async function runActualTest(uri, testFunction, expectedResults, extraData) {
+ let browserWin = gBrowser;
+ let openedWin = null;
+
+ if ("private_window" in extraData) {
+ openedWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ browserWin = openedWin.gBrowser;
+ }
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(browserWin, uri);
+
+ if ("etp_reload" in extraData) {
+ ContentBlockingAllowList.add(tab.linkedBrowser);
+ await BrowserTestUtils.reloadTab(tab);
+ }
+
+ /*
+ * We expect that `runTheTest` is going to be able to communicate with the iframe
+ * or tab that it opens, but if it cannot (because we are using noopener), we kind
+ * of hack around and get the data directly.
+ */
+ if ("noopener" in extraData) {
+ var popupTabPromise = BrowserTestUtils.waitForNewTab(
+ browserWin,
+ extraData.await_uri
+ );
+ }
+
+ // In SpecialPowers.spawn, extraData goes through a structuredClone, which cannot clone
+ // functions. await_uri is sometimes a function. This filters out keys that are used by
+ // this function (runActualTest) and not by runTheTest or testFunction. It avoids the
+ // cloning issue, and avoids polluting the object in those called functions.
+ let filterExtraData = function (x) {
+ let banned_keys = ["private_window", "etp_reload", "noopener", "await_uri"];
+ return Object.fromEntries(
+ Object.entries(x).filter(([k, v]) => !banned_keys.includes(k))
+ );
+ };
+
+ let result = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [IFRAME_DOMAIN, CROSS_ORIGIN_DOMAIN, filterExtraData(extraData)],
+ async function (iframe_domain_, cross_origin_domain_, extraData_) {
+ return content.wrappedJSObject.runTheTest(
+ iframe_domain_,
+ cross_origin_domain_,
+ extraData_
+ );
+ }
+ );
+
+ if ("noopener" in extraData) {
+ await popupTabPromise;
+ if (Services.appinfo.OS === "WINNT") {
+ await new Promise(r => setTimeout(r, 1000));
+ }
+
+ let popup_tab = browserWin.tabs[browserWin.tabs.length - 1];
+ result = await SpecialPowers.spawn(
+ popup_tab.linkedBrowser,
+ [],
+ async function () {
+ let r = content.wrappedJSObject.give_result();
+ return r;
+ }
+ );
+ BrowserTestUtils.removeTab(popup_tab);
+ }
+
+ testFunction(result, expectedResults, extraData);
+
+ if ("etp_reload" in extraData) {
+ ContentBlockingAllowList.remove(tab.linkedBrowser);
+ }
+ BrowserTestUtils.removeTab(tab);
+ if ("private_window" in extraData) {
+ await BrowserTestUtils.closeWindow(openedWin);
+ }
+}
+
+async function defaultsTest(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "default";
+ expectedResults.shouldRFPApply = false;
+ if (extraPrefs != undefined) {
+ await SpecialPowers.pushPrefEnv({
+ set: extraPrefs,
+ });
+ }
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+ if (extraPrefs != undefined) {
+ await SpecialPowers.popPrefEnv();
+ }
+}
+
+async function simpleRFPTest(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "simple RFP enabled";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting", true]].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+async function simplePBMRFPTest(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.private_window = true;
+ extraData.testDesc = extraData.testDesc || "simple RFP in PBM enabled";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.resistFingerprinting.pbmode", true]].concat(
+ extraPrefs || []
+ ),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+async function simpleFPPTest(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "simple FPP enabled";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.fingerprintingProtection", true],
+ ["privacy.fingerprintingProtection.overrides", "+NavigatorHWConcurrency"],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+async function simplePBMFPPTest(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.private_window = true;
+ extraData.testDesc = extraData.testDesc || "simple FPP in PBM enabled";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.fingerprintingProtection.pbmode", true],
+ ["privacy.fingerprintingProtection.overrides", "+HardwareConcurrency"],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (A) RFP is exempted on the framer and framee and (if needed) on another cross-origin domain
+async function testA(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (A)";
+ expectedResults.shouldRFPApply = false;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ `${FRAMER_DOMAIN}, ${IFRAME_DOMAIN}, ${CROSS_ORIGIN_DOMAIN}`,
+ ],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (B) RFP is exempted on the framer and framee but is not on another (if needed) cross-origin domain
+async function testB(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (B)";
+ expectedResults.shouldRFPApply = false;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ `${FRAMER_DOMAIN}, ${IFRAME_DOMAIN}`,
+ ],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (C) RFP is exempted on the framer and (if needed) on another cross-origin domain, but not the framee
+async function testC(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (C)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ `${FRAMER_DOMAIN}, ${CROSS_ORIGIN_DOMAIN}`,
+ ],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (D) RFP is exempted on the framer but not the framee nor another (if needed) cross-origin domain
+async function testD(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (D)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.exemptedDomains", `${FRAMER_DOMAIN}`],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (E) RFP is not exempted on the framer nor the framee but (if needed) is exempted on another cross-origin domain
+async function testE(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (E)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ `${CROSS_ORIGIN_DOMAIN}`,
+ ],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (F) RFP is not exempted on the framer nor the framee nor another (if needed) cross-origin domain
+async function testF(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (F)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.exemptedDomains", ""],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (G) RFP is not exempted on the framer but is on the framee and (if needed) on another cross-origin domain
+async function testG(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (G)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ [
+ "privacy.resistFingerprinting.exemptedDomains",
+ `${IFRAME_DOMAIN}, ${CROSS_ORIGIN_DOMAIN}`,
+ ],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
+
+// (H) RFP is not exempted on the framer nor another (if needed) cross-origin domain but is on the framee
+async function testH(
+ uri,
+ testFunction,
+ expectedResults,
+ extraData,
+ extraPrefs
+) {
+ if (extraData == undefined) {
+ extraData = {};
+ }
+ extraData.testDesc = extraData.testDesc || "test (H)";
+ expectedResults.shouldRFPApply = true;
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["privacy.resistFingerprinting.exemptedDomains", `${IFRAME_DOMAIN}`],
+ ].concat(extraPrefs || []),
+ });
+
+ await runActualTest(uri, testFunction, expectedResults, extraData);
+
+ await SpecialPowers.popPrefEnv();
+}
diff --git a/browser/components/resistfingerprinting/test/browser/shared_test_funcs.js b/browser/components/resistfingerprinting/test/browser/shared_test_funcs.js
new file mode 100644
index 0000000000..0949ef5848
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/browser/shared_test_funcs.js
@@ -0,0 +1,10 @@
+function waitForMessage(aMsg, aOrigin) {
+ return new Promise(resolve => {
+ window.addEventListener("message", function listener(event) {
+ if (event.data == aMsg && (aOrigin == "*" || event.origin == aOrigin)) {
+ window.removeEventListener("message", listener);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/browser/components/resistfingerprinting/test/mochitest/.eslintrc.js b/browser/components/resistfingerprinting/test/mochitest/.eslintrc.js
new file mode 100644
index 0000000000..16ee78885f
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ rules: {
+ "no-eval": "off",
+ },
+};
diff --git a/browser/components/resistfingerprinting/test/mochitest/decode_error.mp4 b/browser/components/resistfingerprinting/test/mochitest/decode_error.mp4
new file mode 100644
index 0000000000..ee72e2dd75
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/decode_error.mp4
Binary files differ
diff --git a/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html b/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
new file mode 100644
index 0000000000..376fddf343
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/file_animation_api.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382545</title>
+<script>
+ function waitForCondition(aCond, aCallback, aErrorMsg) {
+ var tries = 0;
+ var interval = setInterval(() => {
+ if (tries >= 30) {
+ opener.ok(false, aErrorMsg);
+ moveOn();
+ return;
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = aCond();
+ } catch (e) {
+ opener.ok(false, `${e}\n${e.stack}`);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = () => { clearInterval(interval); aCallback(); };
+ }
+
+ function runTest() {
+ let expectedPrecision = opener.expectedPrecision / 1000;
+ let isRounded = (x) => {
+ let rounded = (Math.floor(x / expectedPrecision) * expectedPrecision);
+ // First we do the perfectly normal check that should work just fine
+ if (rounded === x || x === 0)
+ return true;
+
+ // When we're diving by non-whole numbers, we may not get perfect
+ // multiplication/division because of floating points.
+ // When dealing with ms since epoch, a double's precision is on the order
+ // of 1/5 of a microsecond, so we use a value a little higher than that as
+ // our epsilon.
+ // To be clear, this error is introduced in our re-calculation of 'rounded'
+ // above in JavaScript.
+ if (Math.abs(rounded - x + expectedPrecision) < .0005) {
+ return true;
+ } else if (Math.abs(rounded - x) < .0005) {
+ return true;
+ }
+
+ // Then we handle the case where you're sub-millisecond and the timer is not
+ // We check that the timer is not sub-millisecond by assuming it is not if it
+ // returns an even number of milliseconds
+ if (expectedPrecision < 1 && Math.round(x) == x) {
+ if (Math.round(rounded) == x) {
+ return true;
+ }
+ }
+
+ // We are temporarily disabling this extra debugging failure because we expect to return false in some instances
+ // When we correct things we will re-enable it for debugging assistance
+ // opener.ok(false, "Looming Test Failure, Additional Debugging Info: Expected Precision: " + expectedPrecision + " Measured Value: " + x +
+ // " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
+ // " Fuzzy 2: " + Math.abs(rounded - x));
+
+ return false;
+ };
+ const testDiv = document.getElementById("testDiv");
+ const animation = testDiv.animate({ opacity: [0, 1] }, 100000);
+ animation.play();
+
+ waitForCondition(
+ () => animation.currentTime > 100,
+ () => {
+ // We have disabled Time Precision Reduction for CSS Animations, so we expect those tests to fail.
+ // If we are testing that preference, we accept either rounded or not rounded values as A-OK.
+ var maybeAcceptEverything = function(value) {
+ if (opener.prefName.includes("privacy.reduceTimerPrecision") &&
+ !opener.prefName.includes("privacy.resistFingerprinting"))
+ return true;
+ return value;
+ };
+
+ opener.ok(maybeAcceptEverything(isRounded(animation.startTime)),
+ "pref: " + opener.prefName + " - animation.startTime with precision " + expectedPrecision + " is not rounded: " + animation.startTime);
+ opener.ok(maybeAcceptEverything(isRounded(animation.currentTime)),
+ "pref: " + opener.prefName + " - animation.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.currentTime);
+ opener.ok(maybeAcceptEverything(isRounded(animation.timeline.currentTime)),
+ "pref: " + opener.prefName + " - animation.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + animation.timeline.currentTime);
+ if (document.timeline) {
+ opener.ok(maybeAcceptEverything(isRounded(document.timeline.currentTime)),
+ "pref: " + opener.prefName + " - document.timeline.currentTime with precision " + expectedPrecision + " is not rounded: " + document.timeline.currentTime);
+ }
+ opener.done();
+ window.close();
+ },
+ "animation failed to start");
+ }
+</script>
+</head>
+<body onload="runTest();">
+<div id="testDiv">test</div>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/mochitest.ini b/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..2429f5e4dc
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/mochitest.ini
@@ -0,0 +1,29 @@
+[DEFAULT]
+skip-if = toolkit == 'android' # bug 1730213
+tags = resistfingerprinting
+
+support-files =
+ file_animation_api.html
+ worker_child.js
+ worker_grandchild.js
+ !/dom/tests/mochitest/geolocation/network_geolocation.sjs
+
+[test_animation_api.html]
+[test_bug1354633_media_error.html]
+support-files = decode_error.mp4
+[test_bug1382499_touch_api.html]
+[test_bug863246_resource_uri.html]
+[test_device_sensor_event.html]
+[test_geolocation.html]
+scheme = https
+fail-if = xorigin
+[test_hide_gamepad_info.html]
+scheme = https
+support-files = test_hide_gamepad_info_iframe.html
+[test_iframe.html]
+[test_keyboard_event.html]
+[test_pointer_event.html]
+ support-files =
+ ../../../../../dom/events/test/pointerevents/mochitest_support_external.js
+[test_speech_synthesis.html]
+skip-if = verify
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html b/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
new file mode 100644
index 0000000000..e754eeac7d
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1382545
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1382545</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1382545 */
+ SimpleTest.waitForExplicitFinish();
+
+ // Used by file_animation_api.html
+ var prefName = "";
+ var expectedPrecision = 0;
+ var resistFingerprinting = false;
+ var reduceTimerPrecision = false;
+
+ function runTest() {
+ // No matter what we set the precision to, if we're in ResistFingerprinting mode
+ // we use the larger of the precision pref and the constant RFP time-atom
+ if (resistFingerprinting) {
+ const RFP_TIME_ATOM_MS = 16.667;
+ expectedPrecision = Math.max(1000*RFP_TIME_ATOM_MS, expectedPrecision);
+ }
+ window.open("file_animation_api.html");
+ }
+
+ function setupTest(rfp, rtp, ep) {
+ // Set globals
+ expectedPrecision = ep;
+ resistFingerprinting = rfp;
+ reduceTimerPrecision = rtp;
+ prefName = "";
+ prefName += resistFingerprinting ? "privacy.resistFingerprinting " : "";
+ prefName += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["dom.animations-api.timelines.enabled", true],
+ ["privacy.resistFingerprinting", resistFingerprinting],
+ ["privacy.reduceTimerPrecision", reduceTimerPrecision],
+ ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision],
+ ],
+ }, runTest);
+ }
+
+ var testIndx = 0;
+ var testSequence = [
+ [true, false, 100000],
+ [false, true, 100000],
+ [true, false, 50000],
+ [false, true, 50000],
+ [true, false, 100],
+ [false, true, 100],
+ [true, true, 13],
+ [false, true, 13],
+ ];
+
+ window.onload = () => {
+ setupTest(testSequence[testIndx][0], testSequence[testIndx][1], testSequence[testIndx][2]);
+ };
+
+ function done() {
+ testIndx++;
+ if (testIndx == testSequence.length) {
+ SimpleTest.finish();
+ } else {
+ setupTest(testSequence[testIndx][0], testSequence[testIndx][1], testSequence[testIndx][2]);
+ }
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_bug1354633_media_error.html b/browser/components/resistfingerprinting/test/mochitest/test_bug1354633_media_error.html
new file mode 100644
index 0000000000..1155ab1778
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_bug1354633_media_error.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+/* global SimpleTest SpecialPowers */
+
+let errorMessageMap = {};
+
+let testPromise = (resistFingerprinting, src, allowlist) => new Promise(resolve => {
+ let video = document.createElement("video");
+ video.src = src;
+ video.controls = "true";
+ video.onerror = () => {
+ let message = video.error.message;
+ if (!resistFingerprinting) {
+ SimpleTest.isnot(message, "", "Message should not be blank");
+ SimpleTest.info(src + ": " + message);
+ errorMessageMap[src] = message;
+ } else if (allowlist) {
+ SimpleTest.is(message, allowlist, "Error message in allowlist: " + allowlist);
+ } else {
+ SimpleTest.is(message, "", "Blank error message: " + errorMessageMap[src]);
+ }
+ resolve();
+ };
+ document.body.appendChild(video);
+});
+
+async function testBody(resistFingerprinting) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", resistFingerprinting],
+ ],
+ });
+ await testPromise(
+ resistFingerprinting,
+ "load_error.mp4",
+ "404: Not Found" // allowlist
+ );
+ await testPromise(
+ resistFingerprinting,
+ "decode_error.mp4",
+ false // allowlist
+ );
+}
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener("DOMContentLoaded", async () => {
+ await testBody(false);
+ await testBody(true);
+ SimpleTest.finish();
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_bug1382499_touch_api.html b/browser/components/resistfingerprinting/test/mochitest/test_bug1382499_touch_api.html
new file mode 100644
index 0000000000..c440d37cf2
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_bug1382499_touch_api.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+/* global SimpleTest SpecialPowers synthesizeTouch */
+
+SimpleTest.waitForExplicitFinish();
+
+function promiseEvent(target, eventName) {
+ return new Promise(resolve => {
+ target.addEventListener(eventName, resolve, {once: true});
+ });
+}
+
+function promiseTouchEvent(target, type, offsetX, offsetY, params) {
+ let touchEventPromise = promiseEvent(target, type);
+ params.type = type;
+ synthesizeTouch(target, offsetX, offsetY, params);
+ return touchEventPromise;
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ const target0 = document.getElementById("target0");
+ const touchParams = {force: 1.0, angle: 1.0, rx: 2, ry: 3};
+ await SpecialPowers.pushPrefEnv({set: [["dom.w3c_touch_events.enabled", 1]]});
+
+ for (let seq of [0, 1, 2, 3]) {
+ let resist = false;
+ if (seq == 0) {
+ resist = true;
+ await SpecialPowers.pushPrefEnv({set: [["privacy.resistFingerprinting", true]]});
+ } else if (seq == 1) {
+ await SpecialPowers.pushPrefEnv({set: [["privacy.resistFingerprinting", false]]});
+ } else if (seq == 2) {
+ resist = true;
+ await SpecialPowers.pushPrefEnv({set: [
+ ["privacy.fingerprintingProtection", true],
+ ["privacy.fingerprintingProtection.overrides", "+TouchEvents"]
+ ]});
+ } else {
+ await SpecialPowers.pushPrefEnv({set: [
+ ["privacy.fingerprintingProtection", true],
+ ["privacy.fingerprintingProtection.overrides", "-TouchEvents"]
+ ]});
+ }
+
+ info("starting test with fingerprinting resistance " + (resist ? "on" : "off") + " sequence number " + seq);
+ let touchEvent = await promiseTouchEvent(target0, "touchstart", 5, 5, touchParams);
+ info("touch event received");
+ let touch = touchEvent.touches[0];
+
+ if (resist) {
+ is(touch.screenX, touch.clientX, "touch.screenX should be the same as touch.clientX");
+ is(touch.screenY, touch.clientY, "touch.screenY should be the same as touch.clientY");
+ // radiusX/radiusY may differ from the original rx/ry because of AppUnitsPerCSSPixel and AppUnitsPerDevPixel.
+ // So only check if the values are spoofed.
+ is(touch.radiusX, 0, "touch.radiusX");
+ is(touch.radiusY, 0, "touch.radiusY");
+ }
+ is(touch.force, resist ? 0.0 : touchParams.force, "touch.force");
+ is(touch.rotationAngle, resist ? 0 : touchParams.angle, "touch.rotationAngle");
+ await SpecialPowers.popPrefEnv();
+ }
+
+ SimpleTest.finish();
+});
+</script>
+<div id="target0">target 0</div>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html b/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html
new file mode 100644
index 0000000000..fda1c04200
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_bug863246_resource_uri.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+/* global SimpleTest SpecialPowers add_task */
+
+function testResourceUri(aTest, aUri, aContentAccessible) {
+ return new Promise((aResolve) => {
+ let link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.onload = () => {
+ SimpleTest.ok(aContentAccessible, aTest);
+ aResolve();
+ };
+ link.onerror = () => {
+ SimpleTest.ok(!aContentAccessible, aTest);
+ aResolve();
+ };
+ link.href = aUri;
+ document.head.appendChild(link);
+ });
+}
+
+add_task(async function() {
+ await testResourceUri(
+ "resource://content-accessible is content-accessible",
+ "resource://content-accessible/viewsource.css",
+ true);
+ await testResourceUri(
+ "resource://gre-resources is not content-accessible",
+ "resource://gre-resources/html.css",
+ false);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["security.all_resource_uri_content_accessible", true],
+ ],
+ });
+ await testResourceUri(
+ "security.all_resource_uri_content_accessible = true, resource://gre-resources is now content-accessible",
+ "resource://gre-resources/html.css",
+ true);
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_device_sensor_event.html b/browser/components/resistfingerprinting/test/mochitest/test_device_sensor_event.html
new file mode 100644
index 0000000000..7ec4dce94f
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_device_sensor_event.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1369319
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1369319</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1369319 */
+ SimpleTest.waitForExplicitFinish();
+ window.onload = () => {
+ SimpleTest.waitForFocus(() => {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["device.sensors.test.events", true],
+ ["privacy.resistFingerprinting", true],
+ ],
+ }, doTest);
+ }, window);
+ };
+
+ function doTest() {
+ window.addEventListener("devicemotion", () => {
+ ok(false, "The device motion event should not be fired.");
+ }, {once: true});
+
+ window.addEventListener("TestEvent", () => {
+ // If we receive this event without receiving a 'devicemotion' event, this means
+ // the device sensor event has been blocked correctly.
+ ok(true, "Got the 'TestEvent' event.");
+ SimpleTest.finish();
+ }, {once: true});
+
+ window.dispatchEvent(new CustomEvent("TestEvent"));
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_geolocation.html b/browser/components/resistfingerprinting/test/mochitest/test_geolocation.html
new file mode 100644
index 0000000000..95394ddb56
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_geolocation.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1372069
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1372069</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ const BASE_GEO_URL = "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs";
+
+ /** Test for Bug 1372069 */
+ /** Modified for Bug 1441295 */
+ SimpleTest.waitForExplicitFinish();
+ window.onload = () => {
+ SimpleTest.waitForFocus(() => {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["privacy.resistFingerprinting", true],
+ ["geo.prompt.testing", true],
+ ["geo.prompt.testing.allow", true],
+ ["geo.provider.network.url", BASE_GEO_URL],
+ ],
+ }, doTest_getCurrentPosition);
+ }, window);
+ };
+
+ function doTest_getCurrentPosition() {
+ navigator.geolocation.getCurrentPosition(
+ (position) => {
+ ok(true, "Success callback is expected to be called");
+ doTest_watchPosition();
+ },
+ (error) => {
+ ok(false, "Should be able to call success callback, Got error. code = " + error.code);
+ doTest_watchPosition();
+ }
+ );
+ }
+
+ function doTest_watchPosition() {
+ let wid = navigator.geolocation.watchPosition(
+ (position) => {
+ ok(true, "Success callback is expected to be called");
+ navigator.geolocation.clearWatch(wid);
+ SimpleTest.finish();
+ },
+ (error) => {
+ ok(false, "Should be able to call success callback, Got error. code = " + error.code);
+ navigator.geolocation.clearWatch(wid);
+ SimpleTest.finish();
+ }
+ );
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info.html b/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info.html
new file mode 100644
index 0000000000..08fdb0c852
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+/* global SimpleTest SpecialPowers */
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener("DOMContentLoaded", function() {
+ SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.gamepad.test.enabled", true],
+ ["privacy.resistFingerprinting", true],
+ ],
+ }, function() {
+ // This test loads in an iframe, to ensure that the navigator instance is
+ // loaded with the correct value of the preference.
+ var iframe = document.createElement("iframe");
+ iframe.allow = "gamepad";
+ iframe.src = "test_hide_gamepad_info_iframe.html";
+ document.body.appendChild(iframe);
+ });
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info_iframe.html b/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info_iframe.html
new file mode 100644
index 0000000000..5946e1ce6e
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_hide_gamepad_info_iframe.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf8">
+<!--<script src="/tests/SimpleTest/SimpleTest.js"></script>-->
+<script>
+var SimpleTest = window.parent.SimpleTest;
+
+function forceFail() {
+ SimpleTest.ok(
+ false,
+ "privacy.resistFingerprinting is true, should not receive any gamepad events"
+ );
+}
+
+window.addEventListener("gamepadconnected", forceFail);
+window.addEventListener("gamepaddisconnected", forceFail);
+window.addEventListener("gamepadbuttondown", forceFail);
+
+window.addEventListener("load", async () => {
+ const service = navigator.requestGamepadServiceTest();
+ const buttonIndex = await service.addGamepad(
+ "test gamepad", // id
+ service.standardMapping,
+ service.noHand,
+ 4, // buttons
+ 2,
+ 0,
+ 0,
+ 0
+ );
+
+ // Press a button to make the gamepad visible to the page.
+ await service.newButtonEvent(buttonIndex, 0, true, true);
+
+ const { length } = navigator.getGamepads();
+ SimpleTest.is(
+ length,
+ 0,
+ "privacy.resistFingerprinting is true, navigator.getGamepads() should always return an empty array"
+ );
+
+ // Attempt to force gamepad events to be fired, by simulating gamepad disconnect
+ await service.removeGamepad(buttonIndex);
+ SimpleTest.finish();
+});
+</script>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_iframe.html b/browser/components/resistfingerprinting/test/mochitest/test_iframe.html
new file mode 100644
index 0000000000..f01809b28c
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_iframe.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<body>
+<script>
+ add_task(async function() {
+ await SpecialPowers.pushPrefEnv({
+ "set": [["privacy.resistFingerprinting", true]],
+ });
+ is(screen.width, window.innerWidth, "Width should be spoofed");
+ is(screen.height, window.innerHeight, "Height should be spoofed");
+ let iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ is(iframe.contentWindow.screen.width, iframe.contentWindow.innerWidth, "Width should be spoofed in iframe");
+ is(iframe.contentWindow.screen.height, iframe.contentWindow.innerHeight, "Height should be spoofed in iframe");
+ });
+</script>
+</body>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_keyboard_event.html b/browser/components/resistfingerprinting/test/mochitest/test_keyboard_event.html
new file mode 100644
index 0000000000..72f2d29b90
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_keyboard_event.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222285
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1222285</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1222285 */
+ SimpleTest.waitForExplicitFinish();
+
+ window.onload = () => {
+ SpecialPowers.pushPrefEnv({"set":
+ [
+ ["privacy.resistFingerprinting", true],
+ ],
+ }, doTestForSystemEventGroup);
+ };
+
+ // This test makes sure that system event group will still get real keyboard event.
+ function doTestForSystemEventGroup() {
+ SpecialPowers.addSystemEventListener(document, "keydown",
+ function eventHandler(aEvent) {
+ SpecialPowers.removeSystemEventListener(document,
+ "keydown", eventHandler, true);
+
+ is(aEvent.code, "Minus", "The system group event should get real code.");
+ is(aEvent.keyCode, 63, "The system group event should get real keyCode.");
+
+ doTestModifiersForSystemEventGroup();
+ }, true);
+
+ // Send key event to the system group.
+ synthesizeKey("\u00DF", {code: "Minus", keyCode: 63});
+ }
+
+ // Test that will system group event still get suppressed modifier keys
+ function doTestModifiersForSystemEventGroup() {
+ SpecialPowers.addSystemEventListener(document, "keydown",
+ function eventHandler(aEvent) {
+ SpecialPowers.removeSystemEventListener(document,
+ "keydown", eventHandler, true);
+ is(aEvent.key, "Alt", "The system group event get the suppressed keyboard event.");
+
+ SimpleTest.finish();
+ }, true);
+
+ // Send key event to the system group.
+ synthesizeKey("KEY_Alt", {altKey: true});
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_pointer_event.html b/browser/components/resistfingerprinting/test/mochitest/test_pointer_event.html
new file mode 100644
index 0000000000..5b8271274e
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_pointer_event.html
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1363508
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Pointer Events spoofing</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="target0" style="width: 50px; height: 50px; background: green"></div>
+<div id="target1" style="width: 50px; height: 50px; background: black"></div>
+<script type="application/javascript">
+
+ /** Test for Bug 1363508 */
+ SimpleTest.waitForExplicitFinish();
+
+ var target0 = window.document.getElementById("target0");
+ var target1 = window.document.getElementById("target1");
+ var utils = SpecialPowers.Ci.nsIDOMWindowUtils;
+
+ // A helper function to check that whether the pointer is spoofed correctly.
+ function checkPointerEvent(aEvent) {
+ is(aEvent.pointerId, utils.DEFAULT_MOUSE_POINTER_ID,
+ "The spoofed pointer event should always have the mouse pointer id.");
+ is(aEvent.width, 1, "The spoofed pointer event should always have width as 1.");
+ is(aEvent.height, 1, "The spoofed pointer event should always have width as 1.");
+ if (aEvent.buttons === 0) {
+ is(aEvent.pressure, 0.0,
+ "The spoofed pointer event should have pressure as 0.0 if it is not in a active buttons state.");
+ } else {
+ is(aEvent.pressure, 0.5,
+ "The spoofed pointer event should have pressure as 0.5 if it is in a active buttons state.");
+ }
+ is(aEvent.tangentialPressure, 0, "The spoofed pointer event should always have tangentialPressure as 0.");
+ is(aEvent.tiltX, 0, "The spoofed pointer event should always have tiltX as 0.");
+ is(aEvent.tiltY, 0, "The spoofed pointer event should always have tiltY as 0.");
+ is(aEvent.twist, 0, "The spoofed pointer event should always have twist as 0.");
+ is(aEvent.pointerType, "mouse", "The spoofed pointer event should always has mouse pointerType.");
+ is(aEvent.isPrimary, true, "The spoofed pointer event should only receive primary pointer events.");
+ }
+
+ // A helper function to create a promise for waiting the event.
+ function promiseForEvent(aEventType, aCheckFunc) {
+ return new Promise(resolve => {
+ target0.addEventListener(aEventType, (event) => {
+ is(event.type, aEventType, "receive " + event.type + " on target0");
+ aCheckFunc(event);
+ resolve();
+ }, { once: true });
+ });
+ }
+
+ // A test for pointer events from touch interface.
+ async function doTestForTouchPointerEvent() {
+ let eventPromises = [
+ promiseForEvent("pointerover", checkPointerEvent),
+ promiseForEvent("pointerenter", checkPointerEvent),
+ promiseForEvent("pointerdown", checkPointerEvent),
+ promiseForEvent("pointermove", checkPointerEvent),
+ promiseForEvent("pointerup", checkPointerEvent),
+ promiseForEvent("pointerout", checkPointerEvent),
+ promiseForEvent("pointerleave", checkPointerEvent),
+ ];
+
+ synthesizeMouse(target0, 5, 5, { type: "mousedown", inputSource: MouseEvent.MOZ_SOURCE_TOUCH, pressure: 0.75 });
+ synthesizeMouse(target0, 5, 5, { type: "mousemove", inputSource: MouseEvent.MOZ_SOURCE_TOUCH, pressure: 0.75 });
+ synthesizeMouse(target0, 5, 5, { type: "mouseup", inputSource: MouseEvent.MOZ_SOURCE_TOUCH, pressure: 0.75 });
+
+ await Promise.all(eventPromises);
+ }
+
+ // A test for pointercancel event.
+ async function doTestForTouchPointerCancelEvent() {
+ let eventPromises = [
+ promiseForEvent("pointerover", checkPointerEvent),
+ promiseForEvent("pointerenter", checkPointerEvent),
+ promiseForEvent("pointerdown", checkPointerEvent),
+ promiseForEvent("pointermove", checkPointerEvent),
+ promiseForEvent("pointercancel", checkPointerEvent),
+ promiseForEvent("pointerout", checkPointerEvent),
+ promiseForEvent("pointerleave", checkPointerEvent),
+ ];
+
+ synthesizeTouch(target0, 5, 5, { type: "touchstart" });
+ synthesizeTouch(target0, 6, 6, { type: "touchmove" });
+ synthesizeTouch(target0, 6, 6, { type: "touchcancel" });
+
+ await Promise.all(eventPromises);
+ }
+
+ // A test for pointer events from pen interface.
+ async function doTestForPenPointerEvent() {
+ let eventPromises = [
+ promiseForEvent("pointerover", checkPointerEvent),
+ promiseForEvent("pointerenter", checkPointerEvent),
+ promiseForEvent("pointerdown", checkPointerEvent),
+ promiseForEvent("pointermove", checkPointerEvent),
+ promiseForEvent("pointerup", checkPointerEvent),
+ promiseForEvent("pointerout", checkPointerEvent),
+ promiseForEvent("pointerleave", checkPointerEvent),
+ ];
+
+ synthesizeMouse(target0, 5, 5, { type: "mousedown", inputSource: MouseEvent.MOZ_SOURCE_PEN });
+ synthesizeMouse(target0, 5, 5, { type: "mousemove", inputSource: MouseEvent.MOZ_SOURCE_PEN });
+ synthesizeMouse(target0, 5, 5, { type: "mouseup", inputSource: MouseEvent.MOZ_SOURCE_PEN });
+ synthesizeMouse(target1, 5, 5, { type: "mousemove", inputSource: MouseEvent.MOZ_SOURCE_PEN });
+
+ await Promise.all(eventPromises);
+ }
+
+ // A test for gotpointercapture and lostpointercapture events.
+ // We would also test releasePointerCapture for only accepting spoofed pointer
+ // Id here.
+ async function doTestForPointerCapture() {
+ // We test for both mouse and touch to see whether the capture events are
+ // filed properly. We don't check pen here since it won't file capture
+ // events.
+ let inputSources = [ MouseEvent.MOZ_SOURCE_MOUSE,
+ MouseEvent.MOZ_SOURCE_TOUCH ];
+
+ for (let inputSource of inputSources) {
+ function eventHandler(event) {
+ checkPointerEvent(event);
+ if (event.type === "pointerdown") {
+ target0.setPointerCapture(event.pointerId);
+ } else if (event.type === "pointermove") {
+ if (inputSource === MouseEvent.MOZ_SOURCE_TOUCH) {
+ try {
+ target0.releasePointerCapture(utils.DEFAULT_TOUCH_POINTER_ID);
+ ok(false, "The releasePointerCapture should fail here, but it is not.");
+ } catch (e) {
+ ok(true, "The releasePointerCapture fails properly.");
+ }
+ }
+ target0.releasePointerCapture(event.pointerId);
+ }
+ }
+
+ let eventPromises = [
+ promiseForEvent("pointerover", eventHandler),
+ promiseForEvent("pointerenter", eventHandler),
+ promiseForEvent("pointerdown", eventHandler),
+ promiseForEvent("gotpointercapture", eventHandler),
+ promiseForEvent("pointermove", eventHandler),
+ promiseForEvent("lostpointercapture", eventHandler),
+ promiseForEvent("pointerup", eventHandler),
+ promiseForEvent("pointerout", eventHandler),
+ promiseForEvent("pointerleave", eventHandler),
+ ];
+
+ synthesizeMouse(target0, 5, 5, { type: "mousedown", inputSource });
+ synthesizeMouse(target0, 5, 5, { type: "mousemove", inputSource });
+ synthesizeMouse(target0, 5, 5, { type: "mouseup", inputSource });
+ synthesizeMouse(target1, 5, 5, { type: "mousemove", inputSource });
+
+ await Promise.all(eventPromises);
+ }
+ }
+
+ // A test for setPointerCapture() for only accepting spoofed pointer id.
+ async function doTestForSetPointerCapture() {
+ function eventHandler(event) {
+ checkPointerEvent(event);
+ if (event.type === "pointerdown") {
+ try {
+ target0.setPointerCapture(utils.DEFAULT_TOUCH_POINTER_ID);
+ ok(false, "The setPointerCapture should fail here, but it is not.");
+ } catch (e) {
+ ok(true, "The setPointerCapture fails properly.");
+ }
+ }
+ }
+
+ let eventPromises = [
+ promiseForEvent("pointerover", eventHandler),
+ promiseForEvent("pointerenter", eventHandler),
+ promiseForEvent("pointerdown", eventHandler),
+ promiseForEvent("pointermove", eventHandler),
+ promiseForEvent("pointerup", eventHandler),
+ promiseForEvent("pointerout", eventHandler),
+ promiseForEvent("pointerleave", eventHandler),
+ ];
+
+ synthesizeMouse(target0, 5, 5, { type: "mousedown", inputSource: MouseEvent.MOZ_SOURCE_TOUCH });
+ synthesizeMouse(target0, 5, 5, { type: "mousemove", inputSource: MouseEvent.MOZ_SOURCE_TOUCH });
+ synthesizeMouse(target0, 5, 5, { type: "mouseup", inputSource: MouseEvent.MOZ_SOURCE_TOUCH });
+
+ await Promise.all(eventPromises);
+ }
+
+ // A test for assuring that script generated events won't be spoofed.
+ function doTestNoSpoofingForScriptGeneratedEvent() {
+ return new Promise(resolve => {
+ // Generate a custom pointer event by script.
+ let pointerEventCustom = new PointerEvent("pointerover", {
+ pointerId: utils.DEFAULT_TOUCH_POINTER_ID,
+ pointerType: "touch",
+ width: 5,
+ height: 5,
+ pressure: 0.75,
+ tangentialPressure: 0.5,
+ isPrimary: false,
+ });
+
+ target0.addEventListener("pointerover", (event) => {
+ // Check that script generated event is not spoofed.
+ is(event.pointerType, "touch", "The pointerEvent.pointerType is not spoofed.");
+ is(event.width, 5, "The pointerEvent.width is not spoofed.");
+ is(event.height, 5, "The pointerEvent.height is not spoofed.");
+ is(event.pressure, 0.75, "The pointerEvent.pressure is not spoofed.");
+ is(event.tangentialPressure, 0.5, "The pointerEvent.tangentialPressure is not spoofed.");
+ is(event.isPrimary, false, "The pointerEvent.isPrimary is not spoofed.");
+ resolve();
+ }, { once: true });
+
+ target0.dispatchEvent(pointerEventCustom);
+ });
+ }
+
+ async function doTests() {
+ await doTestForTouchPointerEvent();
+ await doTestForTouchPointerCancelEvent();
+ await doTestForPenPointerEvent();
+ await doTestForPointerCapture();
+ await doTestForSetPointerCapture();
+ await doTestNoSpoofingForScriptGeneratedEvent();
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForFocus(() => {
+ SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", true]]},
+ doTests);
+ });
+
+</script>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/test_speech_synthesis.html b/browser/components/resistfingerprinting/test/mochitest/test_speech_synthesis.html
new file mode 100644
index 0000000000..a1778a0a0d
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/test_speech_synthesis.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1333641
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1333641</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1333641 */
+ SimpleTest.waitForExplicitFinish();
+ window.onload = setupSpeechSynthesis;
+
+ // This function setup the speechSynthesis and flip 'privacy.resistFingerprinting'
+ // after it has been setup correctly.
+ function setupSpeechSynthesis() {
+ window.speechSynthesis.addEventListener("voiceschanged", () => {
+ isnot(window.speechSynthesis.getVoices().length, 0, "Voices added");
+ SimpleTest.waitForFocus(() => {
+ SpecialPowers.pushPrefEnv({"set":
+ [["privacy.resistFingerprinting", true]],
+ }, doGetVoicesTest);
+ }, window);
+ }, {once: true});
+
+ is(window.speechSynthesis.getVoices().length, 0, "No voices added initially");
+ }
+
+ function doGetVoicesTest() {
+ is(window.speechSynthesis.getVoices().length, 0,
+ "There should be no voices after fingerprinting resistance is enabled.");
+ doVoiceschangedEventTest();
+ }
+
+ function doVoiceschangedEventTest() {
+ window.speechSynthesis.addEventListener("voiceschanged", () => {
+ ok(false, "The voiceschanged event should not be fired.");
+ doSpeakTestAsync();
+ }, {once: true});
+
+ window.addEventListener("TestEvent", () => {
+ // If we receive this event without receiving a 'voiceschanged' event, this means
+ // the voiceschanged event has been blocked correctly.
+ ok(true, "Got the 'TestEvent' event.");
+ doSpeakTestAsync();
+ }, {once: true});
+
+ // Notify 'synth-voices-changed' for triggering the voiceschanged event.
+ SpecialPowers.Services.obs.notifyObservers(null, "synth-voices-changed");
+ window.dispatchEvent(new CustomEvent("TestEvent"));
+ }
+
+ // This tests Speak() and its asynchronousness.
+ function doSpeakTestAsync() {
+ // For non-e10s, this test will always fail since the event will be triggered immediately
+ // after speak() is called. So, new added events after speak() won't be called. We skip
+ // this test if it is non-e10s.
+ if (SpecialPowers.Services.appinfo.browserTabsRemoteAutostart) {
+ let utterance = new window.SpeechSynthesisUtterance("Hello, world!");
+ window.speechSynthesis.speak(utterance);
+
+ utterance.addEventListener("start", () => {
+ ok(false, "speechSynthesis should not start speaking if fingerprinting resistance is enabled.");
+ doSpeakTestSync();
+ }, {once: true});
+
+ utterance.addEventListener("error", () => {
+ ok(true, "speechSynthesis.speak should fail if fingerprinting resistance is enabled.");
+ doSpeakTestSync();
+ }, {once: true});
+ } else {
+ doSpeakTestSync();
+ }
+ }
+
+ // This tests Speak() and its synchronousness.
+ function doSpeakTestSync() {
+ let utterance = new window.SpeechSynthesisUtterance("Hello, world!");
+ utterance.addEventListener("start", () => {
+ ok(false, "speechSynthesis should not start speaking if fingerprinting resistance is enabled.");
+ SimpleTest.finish();
+ }, {once: true});
+
+ utterance.addEventListener("error", () => {
+ ok(true, "speechSynthesis.speak should fail if fingerprinting resistance is enabled.");
+ SimpleTest.finish();
+ }, {once: true});
+
+ window.speechSynthesis.speak(utterance);
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/browser/components/resistfingerprinting/test/mochitest/worker_child.js b/browser/components/resistfingerprinting/test/mochitest/worker_child.js
new file mode 100644
index 0000000000..fa340fc652
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/worker_child.js
@@ -0,0 +1,28 @@
+let timeStampCodes;
+let worker = new Worker("worker_grandchild.js");
+
+function listenToParent(event) {
+ self.removeEventListener("message", listenToParent);
+ timeStampCodes = event.data;
+
+ let timeStamps = [];
+ for (let timeStampCode of timeStampCodes) {
+ timeStamps.push(eval(timeStampCode));
+ }
+ // Send the timeStamps to the parent.
+ postMessage(timeStamps);
+
+ // Tell the grandchild to start.
+ worker.postMessage(timeStampCodes);
+}
+
+// The worker grandchild will send results back.
+function listenToChild(event) {
+ worker.removeEventListener("message", listenToChild);
+ // Pass the results to the parent.
+ postMessage(event.data);
+ worker.terminate();
+}
+
+worker.addEventListener("message", listenToChild);
+self.addEventListener("message", listenToParent);
diff --git a/browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js b/browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js
new file mode 100644
index 0000000000..ef9940d7ab
--- /dev/null
+++ b/browser/components/resistfingerprinting/test/mochitest/worker_grandchild.js
@@ -0,0 +1,10 @@
+self.addEventListener("message", function (event) {
+ let timeStampCodes = event.data;
+
+ let timeStamps = [];
+ for (let timeStampCode of timeStampCodes) {
+ timeStamps.push(eval(timeStampCode));
+ }
+ // Send the timeStamps to the parent.
+ postMessage(timeStamps);
+});