463 lines
14 KiB
JavaScript
463 lines
14 KiB
JavaScript
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* 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/. */
|
|
|
|
/* import-globals-from head.js */
|
|
|
|
"use strict";
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/toolkit/components/antitracking/test/browser/dynamicfpi_head.js",
|
|
this
|
|
);
|
|
|
|
this.PartitionedStorageHelper = {
|
|
runTestInNormalAndPrivateMode(
|
|
name,
|
|
callback,
|
|
cleanupFunction,
|
|
extraPrefs,
|
|
runInSecure = true
|
|
) {
|
|
// Normal mode
|
|
this.runTest(name, callback, cleanupFunction, extraPrefs, {
|
|
runInSecureContext: runInSecure,
|
|
runInPrivateWindow: false,
|
|
});
|
|
|
|
// Private mode
|
|
this.runTest(name, callback, cleanupFunction, extraPrefs, {
|
|
runInSecureContext: runInSecure,
|
|
runInPrivateWindow: true,
|
|
});
|
|
},
|
|
|
|
runTest(
|
|
name,
|
|
callback,
|
|
cleanupFunction,
|
|
extraPrefs,
|
|
{ runInPrivateWindow = false, runInSecureContext = true } = {}
|
|
) {
|
|
DynamicFPIHelper.runTest(
|
|
name,
|
|
callback,
|
|
cleanupFunction,
|
|
extraPrefs,
|
|
runInPrivateWindow,
|
|
{ runInSecureContext }
|
|
);
|
|
},
|
|
|
|
runPartitioningTestInNormalAndPrivateMode(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
expectUnpartition = false
|
|
) {
|
|
// Normal mode
|
|
this.runPartitioningTest(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
expectUnpartition,
|
|
false
|
|
);
|
|
|
|
// Private mode
|
|
this.runPartitioningTest(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
expectUnpartition,
|
|
true
|
|
);
|
|
},
|
|
|
|
runPartitioningTest(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
expectUnpartition,
|
|
runInPrivateWindow = false
|
|
) {
|
|
for (let variant of ["normal", "initial-aboutblank"]) {
|
|
for (let limitForeignContexts of [false, true]) {
|
|
this.runPartitioningTestInner(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
variant,
|
|
runInPrivateWindow,
|
|
limitForeignContexts,
|
|
expectUnpartition
|
|
);
|
|
}
|
|
}
|
|
},
|
|
|
|
runPartitioningTestInner(
|
|
name,
|
|
testCategory,
|
|
getDataCallback,
|
|
addDataCallback,
|
|
cleanupFunction,
|
|
variant,
|
|
runInPrivateWindow,
|
|
limitForeignContexts,
|
|
expectUnpartition
|
|
) {
|
|
add_task(async _ => {
|
|
info(
|
|
"Starting test `" +
|
|
name +
|
|
"' testCategory `" +
|
|
testCategory +
|
|
"' variant `" +
|
|
variant +
|
|
"' in a " +
|
|
(runInPrivateWindow ? "private" : "normal") +
|
|
" window " +
|
|
(limitForeignContexts ? "with" : "without") +
|
|
" limitForeignContexts to check that 2 tabs are correctly partititioned"
|
|
);
|
|
|
|
await SpecialPowers.flushPrefEnv();
|
|
await setCookieBehaviorPref(
|
|
BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
|
|
runInPrivateWindow
|
|
);
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [
|
|
["dom.storage_access.enabled", true],
|
|
["network.cookie.cookieBehavior.optInPartitioning", true],
|
|
["network.cookie.CHIPS.enabled", true],
|
|
["privacy.dynamic_firstparty.limitForeign", limitForeignContexts],
|
|
["privacy.trackingprotection.enabled", false],
|
|
["privacy.trackingprotection.pbmode.enabled", false],
|
|
["privacy.trackingprotection.annotate_channels", true],
|
|
["dom.security.https_first_pbm", false],
|
|
[
|
|
"privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
|
|
"not-tracking.example.com",
|
|
],
|
|
],
|
|
});
|
|
|
|
let win = window;
|
|
if (runInPrivateWindow) {
|
|
win = OpenBrowserWindow({ private: true });
|
|
await TestUtils.topicObserved("browser-delayed-startup-finished");
|
|
}
|
|
|
|
info("Creating the first tab");
|
|
let tab1 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_HTTPS);
|
|
win.gBrowser.selectedTab = tab1;
|
|
|
|
let browser1 = win.gBrowser.getBrowserForTab(tab1);
|
|
await BrowserTestUtils.browserLoaded(browser1);
|
|
|
|
info("Creating the second tab");
|
|
let tab2 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_9);
|
|
win.gBrowser.selectedTab = tab2;
|
|
|
|
let browser2 = win.gBrowser.getBrowserForTab(tab2);
|
|
await BrowserTestUtils.browserLoaded(browser2);
|
|
|
|
info("Creating the third tab");
|
|
let tab3 = BrowserTestUtils.addTab(
|
|
win.gBrowser,
|
|
TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS
|
|
);
|
|
win.gBrowser.selectedTab = tab3;
|
|
|
|
let browser3 = win.gBrowser.getBrowserForTab(tab3);
|
|
await BrowserTestUtils.browserLoaded(browser3);
|
|
|
|
// Use the same URL as first tab to check partitioned data
|
|
info("Creating the forth tab");
|
|
let tab4 = BrowserTestUtils.addTab(win.gBrowser, TEST_TOP_PAGE_HTTPS);
|
|
win.gBrowser.selectedTab = tab4;
|
|
|
|
let browser4 = win.gBrowser.getBrowserForTab(tab4);
|
|
await BrowserTestUtils.browserLoaded(browser4);
|
|
|
|
async function getDataFromThirdParty(browser, result) {
|
|
// Overwrite the special case here since third party cookies are not
|
|
// avilable when `limitForeignContexts` is enabled.
|
|
if (testCategory === "cookies" && limitForeignContexts) {
|
|
info("overwrite result to empty");
|
|
result = "";
|
|
}
|
|
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[
|
|
{
|
|
page:
|
|
TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS + "?variant=" + variant,
|
|
getDataCallback: getDataCallback.toString(),
|
|
result,
|
|
},
|
|
],
|
|
async obj => {
|
|
await new content.Promise(resolve => {
|
|
let ifr = content.document.createElement("iframe");
|
|
ifr.onload = __ => {
|
|
info("Sending code to the 3rd party content");
|
|
ifr.contentWindow.postMessage({ cb: obj.getDataCallback }, "*");
|
|
};
|
|
|
|
content.addEventListener(
|
|
"message",
|
|
function msg(event) {
|
|
is(
|
|
event.data,
|
|
obj.result,
|
|
"Partitioned cookie jar has value: " + obj.result
|
|
);
|
|
resolve();
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
content.document.body.appendChild(ifr);
|
|
ifr.src = obj.page;
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
async function getDataFromFirstParty(browser, result) {
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[
|
|
{
|
|
getDataCallback: getDataCallback.toString(),
|
|
result,
|
|
variant,
|
|
},
|
|
],
|
|
async obj => {
|
|
let runnableStr = `(() => {return (${obj.getDataCallback});})();`;
|
|
let runnable = eval(runnableStr); // eslint-disable-line no-eval
|
|
let win = content;
|
|
if (obj.variant == "initial-aboutblank") {
|
|
let i = win.document.createElement("iframe");
|
|
i.src = "about:blank";
|
|
win.document.body.appendChild(i);
|
|
// override win to make it point to the initial about:blank window
|
|
win = i.contentWindow;
|
|
}
|
|
|
|
let result = await runnable.call(content, win);
|
|
is(
|
|
result,
|
|
obj.result,
|
|
"Partitioned cookie jar is empty: " + obj.result
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
info("Checking 3rd party has an empty cookie jar in first tab");
|
|
await getDataFromThirdParty(browser1, "");
|
|
|
|
info("Checking 3rd party has an empty cookie jar in second tab");
|
|
await getDataFromThirdParty(browser2, "");
|
|
|
|
info("Checking first party has an empty cookie jar in third tab");
|
|
await getDataFromFirstParty(browser3, "");
|
|
|
|
info("Checking 3rd party has an empty cookie jar in forth tab");
|
|
await getDataFromThirdParty(browser4, "");
|
|
|
|
async function createDataInThirdParty(browser, value) {
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[
|
|
{
|
|
page:
|
|
TEST_4TH_PARTY_PARTITIONED_PAGE_HTTPS + "?variant=" + variant,
|
|
addDataCallback: addDataCallback.toString(),
|
|
value,
|
|
},
|
|
],
|
|
async obj => {
|
|
await new content.Promise(resolve => {
|
|
let ifr = content.document.getElementsByTagName("iframe")[0];
|
|
content.addEventListener(
|
|
"message",
|
|
function msg(event) {
|
|
ok(event.data, "Data created");
|
|
resolve();
|
|
},
|
|
{ once: true }
|
|
);
|
|
|
|
ifr.contentWindow.postMessage(
|
|
{
|
|
cb: obj.addDataCallback,
|
|
value: obj.value,
|
|
},
|
|
"*"
|
|
);
|
|
});
|
|
}
|
|
);
|
|
}
|
|
|
|
async function createDataInFirstParty(browser, value) {
|
|
await SpecialPowers.spawn(
|
|
browser,
|
|
[
|
|
{
|
|
addDataCallback: addDataCallback.toString(),
|
|
value,
|
|
variant,
|
|
},
|
|
],
|
|
async obj => {
|
|
let runnableStr = `(() => {return (${obj.addDataCallback});})();`;
|
|
let runnable = eval(runnableStr); // eslint-disable-line no-eval
|
|
let win = content;
|
|
if (obj.variant == "initial-aboutblank") {
|
|
let i = win.document.createElement("iframe");
|
|
i.src = "about:blank";
|
|
win.document.body.appendChild(i);
|
|
// override win to make it point to the initial about:blank window
|
|
win = i.contentWindow;
|
|
}
|
|
|
|
let result = await runnable.call(content, win, obj.value);
|
|
ok(result, "Data created");
|
|
}
|
|
);
|
|
}
|
|
|
|
info("Creating data in the first tab");
|
|
await createDataInThirdParty(browser1, "A");
|
|
|
|
info("Creating data in the second tab");
|
|
await createDataInThirdParty(browser2, "B");
|
|
|
|
// Before writing browser4, check data written by browser1
|
|
info("First tab should still have just 'A'");
|
|
await getDataFromThirdParty(browser1, "A");
|
|
info("Forth tab should still have just 'A'");
|
|
await getDataFromThirdParty(browser4, "A");
|
|
|
|
// Ensure to create data in the forth tab before the third tab,
|
|
// otherwise cookie will be written successfully due to prior cookie
|
|
// of the base domain exists.
|
|
info("Creating data in the forth tab");
|
|
await createDataInThirdParty(browser4, "D");
|
|
|
|
info("Creating data in the third tab");
|
|
await createDataInFirstParty(browser3, "C");
|
|
|
|
// read all tabs
|
|
info("First tab should be changed to 'D'");
|
|
await getDataFromThirdParty(browser1, "D");
|
|
|
|
info("Second tab should still have just 'B'");
|
|
await getDataFromThirdParty(browser2, "B");
|
|
|
|
info("Third tab should still have just 'C'");
|
|
await getDataFromFirstParty(browser3, "C");
|
|
|
|
info("Forth tab should still have just 'D'");
|
|
await getDataFromThirdParty(browser4, "D");
|
|
|
|
async function setStorageAccessForThirdParty(browser) {
|
|
info(`Setting permission for ${browser.currentURI.spec}`);
|
|
let type = "3rdPartyStorage^https://not-tracking.example.com";
|
|
let permission = Services.perms.ALLOW_ACTION;
|
|
let expireType = Services.perms.EXPIRE_SESSION;
|
|
Services.perms.addFromPrincipal(
|
|
browser.contentPrincipal,
|
|
type,
|
|
permission,
|
|
expireType,
|
|
0
|
|
);
|
|
// Wait for permission to be set successfully
|
|
let originAttributes = runInPrivateWindow
|
|
? { privateBrowsingId: 1 }
|
|
: {};
|
|
await new Promise(resolve => {
|
|
let id = setInterval(async _ => {
|
|
if (
|
|
await SpecialPowers.testPermission(type, permission, {
|
|
url: browser.currentURI.spec,
|
|
originAttributes,
|
|
})
|
|
) {
|
|
clearInterval(id);
|
|
resolve();
|
|
}
|
|
}, 0);
|
|
});
|
|
}
|
|
|
|
if (!expectUnpartition) {
|
|
info("Setting Storage access for third parties");
|
|
|
|
await setStorageAccessForThirdParty(browser1);
|
|
await setStorageAccessForThirdParty(browser2);
|
|
await setStorageAccessForThirdParty(browser3);
|
|
await setStorageAccessForThirdParty(browser4);
|
|
|
|
info("Done setting Storage access for third parties");
|
|
|
|
// read all tabs
|
|
info("First tab should still have just 'D'");
|
|
await getDataFromThirdParty(browser1, "D");
|
|
|
|
info("Second tab should still have just 'B'");
|
|
await getDataFromThirdParty(browser2, "B");
|
|
|
|
info("Third tab should still have just 'C'");
|
|
await getDataFromFirstParty(browser3, "C");
|
|
|
|
info("Forth tab should still have just 'D'");
|
|
await getDataFromThirdParty(browser4, "D");
|
|
}
|
|
|
|
info("Done checking departitioned state");
|
|
|
|
info("Removing the tabs");
|
|
BrowserTestUtils.removeTab(tab1);
|
|
BrowserTestUtils.removeTab(tab2);
|
|
BrowserTestUtils.removeTab(tab3);
|
|
BrowserTestUtils.removeTab(tab4);
|
|
|
|
if (runInPrivateWindow) {
|
|
win.close();
|
|
}
|
|
});
|
|
|
|
add_task(async _ => {
|
|
info("Cleaning up.");
|
|
if (cleanupFunction) {
|
|
await cleanupFunction();
|
|
}
|
|
|
|
// While running these tests we typically do not have enough idle time to do
|
|
// GC reliably, so force it here.
|
|
/* import-globals-from antitracking_head.js */
|
|
forceGC();
|
|
});
|
|
},
|
|
};
|