commit d4cf73ac9411060a035affe325c5761057bba570
parent 992c24e4cf666613a8853213d2a36dcf1feaf35d
Author: Tom Ritter <tom@mozilla.com>
Date: Fri, 3 Oct 2025 16:03:18 +0000
Bug 1980264: Add a test r=timhuang
Differential Revision: https://phabricator.services.mozilla.com/D267090
Diffstat:
4 files changed, 367 insertions(+), 0 deletions(-)
diff --git a/browser/components/resistfingerprinting/test/browser/browser.toml b/browser/components/resistfingerprinting/test/browser/browser.toml
@@ -11,6 +11,8 @@ support-files = [
"file_workerNetInfo.js",
"file_workerPerformance.js",
"head.js",
+ "file_efficientcanvascompare_iframer.html",
+ "file_efficientcanvascompare_iframee.html",
"file_canvascompare_aboutblank_iframee.html",
"file_canvascompare_aboutblank_iframer.html",
"file_canvascompare_aboutblank_popupmaker.html",
@@ -97,6 +99,8 @@ skip-if = [
"os == 'mac' && os_version == '15.30' && arch == 'aarch64' && opt", # Bug 1775698
]
+["browser_efficientcanvascompare_iframes.js"]
+
["browser_exslt_time_precision.js"]
["browser_exslt_timezone_load.js"]
diff --git a/browser/components/resistfingerprinting/test/browser/browser_efficientcanvascompare_iframes.js b/browser/components/resistfingerprinting/test/browser/browser_efficientcanvascompare_iframes.js
@@ -0,0 +1,247 @@
+/**
+ * This test compares canvas randomization on a parent and an iframe, and ensures that the canvas randomization key
+ * is inherited correctly. (e.g. that the canvases have the same random value)
+ *
+ * It runs all the tests twice - once for when the iframe is cross-domain, and once when it is same-domain
+ *
+ * Covers the following cases:
+ * - RFP/FPP is disabled entirely
+ * - RFP is enabled entirely, and only in PBM
+ * - FPP is enabled entirely, and only in PBM
+ * - A normal window when FPP is enabled globally and RFP is enabled in PBM, Protections Enabled and Disabled
+ *
+ */
+
+"use strict";
+
+// =============================================================================================
+
+async function testCanvasRandomization(result, expectedResults, extraData) {
+ let testDesc = extraData.testDesc;
+
+ let parent = result.mine;
+ let child = result.theirs;
+ let unmodified = UNMODIFIED_CANVAS_DATA;
+
+ if (expectedResults.shouldBeRandom) {
+ if (expectedResults.shouldRFPApply) {
+ Assert.notEqual(
+ parent,
+ child,
+ `Checking ${testDesc} for RFP canvas randomization parent != child` +
+ ` is ${parent != child}`
+ );
+ Assert.notEqual(
+ parent,
+ unmodified,
+ `Checking ${testDesc} for RFP canvas randomization, parent != unmodified` +
+ ` is ${parent != unmodified}`
+ );
+ } else {
+ Assert.equal(
+ parent,
+ child,
+ `Checking ${testDesc} for canvas randomization parent == child` +
+ ` is ${parent == child}`
+ );
+ Assert.notEqual(
+ parent,
+ unmodified,
+ `Checking ${testDesc} for canvas randomization, parent != unmodified` +
+ ` is ${parent != unmodified}`
+ );
+ }
+ } else {
+ Assert.equal(
+ parent,
+ child,
+ `Checking ${testDesc} for no canvas randomization, parent == child` +
+ ` is ${parent == child}`
+ );
+ Assert.equal(
+ parent,
+ unmodified,
+ `Checking ${testDesc} for no canvas randomization, parent == unmodified` +
+ ` is ${parent == unmodified}`
+ );
+ }
+}
+
+requestLongerTimeout(2);
+
+var UNMODIFIED_CANVAS_DATA = undefined;
+
+add_setup(async function () {
+ // Disable the fingerprinting randomization.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "privacy.fingerprintingProtection.overrides",
+ "+EfficientCanvasRandomization,-CanvasRandomization",
+ ],
+ ],
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.fingerprintingProtection", false],
+ ["privacy.fingerprintingProtection.pbmode", false],
+ ["privacy.resistFingerprinting", false],
+ ],
+ });
+
+ function runExtractCanvasData(tab) {
+ return SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ const canvas = content.document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+
+ const context = canvas.getContext("2d");
+
+ context.fillStyle = "#EE2222";
+ context.fillRect(0, 0, 100, 100);
+ context.fillStyle = "#2222EE";
+ context.fillRect(20, 20, 100, 100);
+
+ // Add the canvas element to the document
+ content.document.body.appendChild(canvas);
+
+ let url = canvas.toDataURL();
+ return url;
+ });
+ }
+
+ const emptyPage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "empty.html";
+
+ // Open a tab for extracting the canvas data.
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, emptyPage);
+
+ let data = await runExtractCanvasData(tab);
+ UNMODIFIED_CANVAS_DATA = data;
+
+ BrowserTestUtils.removeTab(tab);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Keep the test simpler, we do pixel tests in other tests.
+
+// Note that we are inheriting the randomization key ACROSS top-level domains that are cross-domain, because the iframe is a 3rd party domain
+let uri = `https://${FRAMER_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframer.html?mode=iframe`;
+
+let shouldBeRandom = {
+ shouldBeRandom: true,
+};
+let noRandom = {
+ shouldBeRandom: false,
+};
+let expectedResults = undefined;
+
+expectedResults = structuredClone(noRandom);
+add_task(
+ defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ defaultsPBMTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test a private window with RFP enabled in PBMode
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test a Private Window with FPP Enabled in PBM
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
+expectedResults = structuredClone(noRandom);
+add_task(
+ RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
+ null,
+ uri,
+ testCanvasRandomization,
+ expectedResults
+ )
+);
+
+// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ RFPPBMFPP_NormalMode_ProtectionsTest.bind(
+ null,
+ uri,
+ testCanvasRandomization,
+ expectedResults
+ )
+);
+
+// And here the we are inheriting the randomization key into an iframe that is same-domain to the parent
+uri = `https://${IFRAME_DOMAIN}/browser/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframer.html?mode=iframe`;
+
+expectedResults = structuredClone(noRandom);
+add_task(
+ defaultsTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simpleRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test a private window with RFP enabled in PBMode
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simplePBMRFPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simpleFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test a Private Window with FPP Enabled in PBM
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ simplePBMFPPTest.bind(null, uri, testCanvasRandomization, expectedResults)
+);
+
+// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, No Protections
+expectedResults = structuredClone(noRandom);
+add_task(
+ RFPPBMFPP_NormalMode_NoProtectionsTest.bind(
+ null,
+ uri,
+ testCanvasRandomization,
+ expectedResults
+ )
+);
+
+// Test RFP Enabled in PBM and FPP enabled in Normal Browsing Mode, Protections Enabled
+expectedResults = structuredClone(shouldBeRandom);
+add_task(
+ RFPPBMFPP_NormalMode_ProtectionsTest.bind(
+ null,
+ uri,
+ testCanvasRandomization,
+ expectedResults
+ )
+);
diff --git a/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframee.html b/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframee.html
@@ -0,0 +1,41 @@
+<!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() {
+ const canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+
+ const context = canvas.getContext("2d");
+
+ context.fillStyle = "#EE2222";
+ context.fillRect(0, 0, 100, 100);
+ context.fillStyle = "#2222EE";
+ context.fillRect(20, 20, 100, 100);
+
+ // Add the canvas element to the document
+ document.body.appendChild(canvas);
+
+ return canvas.toDataURL();
+}
+</script>
+<output id="result"></output>
diff --git a/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframer.html b/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_iframer.html
@@ -0,0 +1,75 @@
+<!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) {
+ var child_reference;
+ let url = `https://${iframe_domain}/browser/browser/components/resistfingerprinting/test/browser/file_efficientcanvascompare_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");
+ }
+
+ function give_result() {
+ const canvas = document.createElement("canvas");
+ canvas.width = 100;
+ canvas.height = 100;
+
+ const context = canvas.getContext("2d");
+
+ context.fillStyle = "#EE2222";
+ context.fillRect(0, 0, 100, 100);
+ context.fillStyle = "#2222EE";
+ context.fillRect(20, 20, 100, 100);
+
+ // Add the canvas element to the document
+ document.body.appendChild(canvas);
+
+ return canvas.toDataURL();
+ }
+ let myResult = give_result();
+
+ 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({mine: myResult, theirs: 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>