/* 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"; const { RemoteSettings } = ChromeUtils.importESModule( "resource://services-settings/remote-settings.sys.mjs" ); const COLLECTION_NAME = "fingerprinting-protection-overrides"; const TOP_DOMAIN = "https://example.com"; const THIRD_PARTY_DOMAIN = "https://example.org"; const SPOOFED_HW_CONCURRENCY = 2; const DEFAULT_HW_CONCURRENCY = navigator.hardwareConcurrency; const TEST_TOP_PAGE = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", TOP_DOMAIN ) + "empty.html"; const TEST_THIRD_PARTY_PAGE = getRootDirectory(gTestPath).replace( "chrome://mochitests/content", THIRD_PARTY_DOMAIN ) + "empty.html"; const TEST_CASES = [ // Test the wildcard entry. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "*", }, ], expects: { windowOuter: { top: true, firstParty: true, thirdParty: true, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test the entry that only applies to the first-party context. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.com", }, ], expects: { windowOuter: { top: true, firstParty: true, thirdParty: false, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test the entry that applies to every context under the first-party domain. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.com", thirdPartyDomain: "*", }, ], expects: { windowOuter: { top: true, firstParty: true, thirdParty: true, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test the entry that applies to the specific third-party domain under the first-party domain. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.com", thirdPartyDomain: "example.org", }, ], expects: { windowOuter: { top: false, firstParty: false, thirdParty: true, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test the entry that applies to the every third-party domain under any first-party domains. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "*", thirdPartyDomain: "example.org", }, ], expects: { windowOuter: { top: false, firstParty: false, thirdParty: true, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test a entry that doesn't apply to the current contexts. The first-party // domain is different. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.net", }, ], expects: { windowOuter: { top: false, firstParty: false, thirdParty: false, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test a entry that doesn't apply to the current contexts. The first-party // domain is different. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.net", thirdPartyDomain: "*", }, ], expects: { windowOuter: { top: false, firstParty: false, thirdParty: false, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test a entry that doesn't apply to a the current contexts. Both first-party // and third-party domains are different. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.net", thirdPartyDomain: "example.com", }, ], expects: { windowOuter: { top: false, firstParty: false, thirdParty: false, }, hwConcurrency: { top: false, firstParty: false, thirdParty: false, }, }, }, // Test multiple entries that enable HW concurrency in the first-party context // and WindowOuter in the third-party context. { entires: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.com", }, { id: "2", last_modified: 1000000000000001, overrides: "+NavigatorHWConcurrency", firstPartyDomain: "example.com", thirdPartyDomain: "example.org", }, ], expects: { windowOuter: { top: true, firstParty: true, thirdParty: false, }, hwConcurrency: { top: false, firstParty: false, thirdParty: true, }, }, }, ]; async function openAndSetupTestPage() { let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, TEST_TOP_PAGE ); // Create a first-party and a third-party iframe in the tab. let { firstPartyBC, thirdPartyBC } = await SpecialPowers.spawn( tab.linkedBrowser, [TEST_TOP_PAGE, TEST_THIRD_PARTY_PAGE], async (firstPartySrc, thirdPartySrc) => { let firstPartyFrame = content.document.createElement("iframe"); let loading = new content.Promise(resolve => { firstPartyFrame.onload = resolve; }); content.document.body.appendChild(firstPartyFrame); firstPartyFrame.src = firstPartySrc; await loading; let thirdPartyFrame = content.document.createElement("iframe"); loading = new content.Promise(resolve => { thirdPartyFrame.onload = resolve; }); content.document.body.appendChild(thirdPartyFrame); thirdPartyFrame.src = thirdPartySrc; await loading; return { firstPartyBC: firstPartyFrame.browsingContext, thirdPartyBC: thirdPartyFrame.browsingContext, }; } ); return { tab, firstPartyBC, thirdPartyBC }; } async function openAndSetupTestPageForPopup() { let tab = await BrowserTestUtils.openNewForegroundTab( gBrowser, TEST_TOP_PAGE ); // Create a third-party iframe and a pop-up from this iframe. let { thirdPartyFrameBC, popupBC } = await SpecialPowers.spawn( tab.linkedBrowser, [TEST_THIRD_PARTY_PAGE], async thirdPartySrc => { let thirdPartyFrame = content.document.createElement("iframe"); let loading = new content.Promise(resolve => { thirdPartyFrame.onload = resolve; }); content.document.body.appendChild(thirdPartyFrame); thirdPartyFrame.src = thirdPartySrc; await loading; let popupBC = await SpecialPowers.spawn( thirdPartyFrame, [thirdPartySrc], async src => { let win; let loading = new content.Promise(resolve => { win = content.open(src); win.onload = resolve; }); await loading; return win.browsingContext; } ); return { thirdPartyFrameBC: thirdPartyFrame.browsingContext, popupBC, }; } ); return { tab, thirdPartyFrameBC, popupBC }; } async function verifyResultInTab(tab, firstPartyBC, thirdPartyBC, expected) { let testWindowOuter = enabled => { if (enabled) { ok( content.wrappedJSObject.outerHeight == content.wrappedJSObject.innerHeight && content.wrappedJSObject.outerWidth == content.wrappedJSObject.innerWidth, "Fingerprinting target WindowOuterSize is enabled for WindowOuterSize." ); } else { ok( content.wrappedJSObject.outerHeight != content.wrappedJSObject.innerHeight || content.wrappedJSObject.outerWidth != content.wrappedJSObject.innerWidth, "Fingerprinting target WindowOuterSize is not enabled for WindowOuterSize." ); } }; let testHWConcurrency = expected => { is( content.wrappedJSObject.navigator.hardwareConcurrency, expected, "The hardware concurrency is expected." ); }; info( `Verify top-level context with fingerprinting protection is ${ expected.top ? "enabled" : "not enabled" }.` ); await SpecialPowers.spawn( tab.linkedBrowser, [expected.windowOuter.top], testWindowOuter ); let expectHWConcurrencyTop = expected.hwConcurrency.top ? SPOOFED_HW_CONCURRENCY : DEFAULT_HW_CONCURRENCY; await SpecialPowers.spawn( tab.linkedBrowser, [expectHWConcurrencyTop], testHWConcurrency ); let workerTopResult = await runFunctionInWorker( tab.linkedBrowser, async _ => { return navigator.hardwareConcurrency; } ); is( workerTopResult, expectHWConcurrencyTop, "The top worker reports the expected HW concurrency." ); info( `Verify first-party context with fingerprinting protection is ${ expected.firstParty ? "enabled" : "not enabled" }.` ); await SpecialPowers.spawn( firstPartyBC, [expected.windowOuter.firstParty], testWindowOuter ); let expectHWConcurrencyFirstParty = expected.hwConcurrency.firstParty ? SPOOFED_HW_CONCURRENCY : DEFAULT_HW_CONCURRENCY; await SpecialPowers.spawn( firstPartyBC, [expectHWConcurrencyFirstParty], testHWConcurrency ); let workerFirstPartyResult = await runFunctionInWorker( firstPartyBC, async _ => { return navigator.hardwareConcurrency; } ); is( workerFirstPartyResult, expectHWConcurrencyFirstParty, "The first-party worker reports the expected HW concurrency." ); info( `Verify third-party context with fingerprinting protection is ${ expected.thirdParty ? "enabled" : "not enabled" }.` ); await SpecialPowers.spawn( thirdPartyBC, [expected.windowOuter.thirdParty], testWindowOuter ); let expectHWConcurrencyThirdParty = expected.hwConcurrency.thirdParty ? SPOOFED_HW_CONCURRENCY : DEFAULT_HW_CONCURRENCY; await SpecialPowers.spawn( thirdPartyBC, [expectHWConcurrencyThirdParty], testHWConcurrency ); let workerThirdPartyResult = await runFunctionInWorker( thirdPartyBC, async _ => { return navigator.hardwareConcurrency; } ); is( workerThirdPartyResult, expectHWConcurrencyThirdParty, "The third-party worker reports the expected HW concurrency." ); } add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [ ["privacy.fingerprintingProtection.remoteOverrides.testing", true], ["privacy.fingerprintingProtection", true], ["privacy.fingerprintingProtection.pbmode", true], ], }); registerCleanupFunction(() => { Services.rfp.cleanAllOverrides(); }); }); add_task(async function () { // Add initial empty record. let db = RemoteSettings(COLLECTION_NAME).db; await db.importChanges({}, Date.now(), []); for (let test of TEST_CASES) { // Create a promise for waiting the overrides get updated. let promise = promiseObserver("fpp-test:set-overrides-finishes"); // Trigger the fingerprinting overrides update by a remote settings sync. await RemoteSettings(COLLECTION_NAME).emit("sync", { data: { current: test.entires, }, }); await promise; ok(true, "Got overrides update"); let { tab, firstPartyBC, thirdPartyBC } = await openAndSetupTestPage(); await verifyResultInTab(tab, firstPartyBC, thirdPartyBC, test.expects); BrowserTestUtils.removeTab(tab); } db.clear(); }); // A test to verify that a pop-up inherits overrides from the third-party iframe // that opens it. add_task(async function test_popup_inheritance() { // Add initial empty record. let db = RemoteSettings(COLLECTION_NAME).db; await db.importChanges({}, Date.now(), []); // Create a promise for waiting the overrides get updated. let promise = promiseObserver("fpp-test:set-overrides-finishes"); // Trigger the fingerprinting overrides update by a remote settings sync. await RemoteSettings(COLLECTION_NAME).emit("sync", { data: { current: [ { id: "1", last_modified: 1000000000000001, overrides: "+WindowOuterSize", firstPartyDomain: "example.com", thirdPartyDomain: "example.org", }, ], }, }); await promise; let { tab, thirdPartyFrameBC, popupBC } = await openAndSetupTestPageForPopup(); // Ensure the third-party iframe has the correct overrides. await SpecialPowers.spawn(thirdPartyFrameBC, [], _ => { ok( content.wrappedJSObject.outerHeight == content.wrappedJSObject.innerHeight && content.wrappedJSObject.outerWidth == content.wrappedJSObject.innerWidth, "Fingerprinting target WindowOuterSize is enabled for third-party iframe." ); }); // Verify the popup inherits overrides from the opener. await SpecialPowers.spawn(popupBC, [], _ => { ok( content.wrappedJSObject.outerHeight == content.wrappedJSObject.innerHeight && content.wrappedJSObject.outerWidth == content.wrappedJSObject.innerWidth, "Fingerprinting target WindowOuterSize is enabled for the pop-up." ); content.close(); }); BrowserTestUtils.removeTab(tab); db.clear(); });