Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* 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/. */
"use strict";
const { RemoteSettings } = ChromeUtils.importESModule(
);
XPCOMUtils.defineLazyServiceGetter(
this,
"peuService",
"@mozilla.org/partitioning/exception-list-service;1",
"nsIPartitioningExceptionListService"
);
const TEST_REDIRECT_TOP_PAGE =
TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_TOP_PAGE;
const TEST_REDIRECT_3RD_PARTY_PAGE =
TEST_DOMAIN + TEST_PATH + "redirect.sjs?" + TEST_3RD_PARTY_PARTITIONED_PAGE;
const TEST_REDIRECT_ANOTHER_3RD_PARTY_PAGE =
TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS +
TEST_PATH +
"redirect.sjs?" +
TEST_TOP_PAGE_HTTPS;
const COLLECTION_NAME = "partitioning-exempt-urls";
const EXCEPTION_LIST_PREF_NAME = "privacy.restrict3rdpartystorage.skip_list";
async function cleanup() {
Services.prefs.clearUserPref(EXCEPTION_LIST_PREF_NAME);
await new Promise(resolve => {
Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
resolve()
);
});
}
add_setup(async function () {
await SpecialPowers.flushPrefEnv();
await SpecialPowers.pushPrefEnv({
set: [
[
"network.cookie.cookieBehavior",
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
],
[
"network.cookie.cookieBehavior.pbmode",
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
],
["privacy.restrict3rdpartystorage.heuristic.redirect", false],
["privacy.trackingprotection.enabled", false],
["privacy.trackingprotection.pbmode.enabled", false],
["privacy.trackingprotection.annotate_channels", true],
// Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
["network.cookie.sameSite.laxByDefault", false],
],
});
registerCleanupFunction(cleanup);
await cleanup();
});
function executeContentScript(browser, callback, options = {}) {
return SpecialPowers.spawn(
browser,
[
{
callback: callback.toString(),
...options,
},
],
obj => {
return new content.Promise(async resolve => {
if (obj.page) {
// third-party
let ifr = content.document.createElement("iframe");
ifr.onload = async () => {
info("Sending code to the 3rd party content");
ifr.contentWindow.postMessage(
{ cb: obj.callback, value: obj.value },
"*"
);
};
content.addEventListener("message", event => resolve(event.data), {
once: true,
});
content.document.body.appendChild(ifr);
ifr.src = obj.page;
} else {
// first-party
let runnableStr = `(() => {return (${obj.callback});})();`;
let runnable = eval(runnableStr); // eslint-disable-line no-eval
resolve(await runnable.call(content, content, obj.value));
}
});
}
);
}
function readNetworkCookie(win) {
return win
.fetch("cookies.sjs")
.then(r => r.text())
.then(text => {
return text.substring("cookie:foopy=".length);
});
}
async function writeNetworkCookie(win, value) {
await win.fetch("cookies.sjs?" + value).then(r => r.text());
return true;
}
function createDataInFirstParty(browser, value) {
return executeContentScript(browser, writeNetworkCookie, { value });
}
function getDataFromFirstParty(browser) {
return executeContentScript(browser, readNetworkCookie, {});
}
function createDataInThirdParty(browser, value) {
return executeContentScript(browser, writeNetworkCookie, {
page: TEST_3RD_PARTY_PARTITIONED_PAGE,
value: value + ";SameSite=None;Secure;Partitioned;",
});
}
function getDataFromThirdParty(browser) {
return executeContentScript(browser, readNetworkCookie, {
page: TEST_3RD_PARTY_PARTITIONED_PAGE,
});
}
async function redirectWithUserInteraction(browser, url, wait = null) {
await executeContentScript(
browser,
(content, value) => {
content.document.userInteractionForTesting();
let link = content.document.createElement("a");
link.appendChild(content.document.createTextNode("click me!"));
link.href = value;
content.document.body.appendChild(link);
link.click();
},
{
value: url,
}
);
await BrowserTestUtils.browserLoaded(browser, false, wait || url);
}
async function checkData(browser, options) {
let data;
// We check if the cookie string includes the expected cookie because the
// cookie string might contain both partitioned and unpartitioned cookies at
// the same time.
if ("firstParty" in options) {
data = await getDataFromFirstParty(browser);
ok(data.includes(options.firstParty), "correct first-party data");
}
if ("thirdParty" in options) {
data = await getDataFromThirdParty(browser);
ok(data.includes(options.thirdParty), "correct third-party data");
}
}
async function runTestRedirectHeuristic(disableHeuristics) {
info("Starting Dynamic FPI Redirect Heuristic test...");
await SpecialPowers.pushPrefEnv({
set: [
["privacy.restrict3rdpartystorage.heuristic.recently_visited", true],
["privacy.antitracking.enableWebcompat", !disableHeuristics],
],
});
// mark third-party as tracker
await UrlClassifierTestUtils.addTestTrackers();
info("Creating a new tab");
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
info("initializing...");
await checkData(browser, { firstParty: "", thirdParty: "" });
await Promise.all([
createDataInFirstParty(browser, "firstParty"),
createDataInThirdParty(browser, "thirdParty"),
]);
await checkData(browser, {
firstParty: "firstParty",
thirdParty: "",
});
info("load third-party content as first-party");
await redirectWithUserInteraction(
browser,
TEST_REDIRECT_3RD_PARTY_PAGE,
TEST_3RD_PARTY_PARTITIONED_PAGE
);
await checkData(browser, { firstParty: "" });
await createDataInFirstParty(browser, "heuristicFirstParty");
await checkData(browser, { firstParty: "heuristicFirstParty" });
info("redirect back to first-party page");
await redirectWithUserInteraction(
browser,
TEST_REDIRECT_TOP_PAGE,
TEST_TOP_PAGE
);
info("third-party tracker should NOT able to access first-party data");
await checkData(browser, {
firstParty: "firstParty",
thirdParty: "",
});
// remove third-party from tracker
await UrlClassifierTestUtils.cleanupTestTrackers();
info("load third-party content as first-party");
await redirectWithUserInteraction(
browser,
TEST_REDIRECT_3RD_PARTY_PAGE,
TEST_3RD_PARTY_PARTITIONED_PAGE
);
await checkData(browser, {
firstParty: "heuristicFirstParty",
});
info("redirect back to first-party page");
await redirectWithUserInteraction(
browser,
TEST_REDIRECT_TOP_PAGE,
TEST_TOP_PAGE
);
info(
`third-party page should ${
disableHeuristics ? "not " : ""
}be able to access first-party data`
);
await checkData(browser, {
firstParty: "firstParty",
thirdParty: disableHeuristics ? "" : "heuristicFirstParty",
});
info("Removing the tab");
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
await cleanup();
}
async function runTestRedirectHeuristicWithSameSite() {
info("Starting Dynamic FPI Redirect Between Same Site Heuristic test...");
info("Creating a new tab");
let tab = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE_HTTPS);
gBrowser.selectedTab = tab;
let browser = gBrowser.getBrowserForTab(tab);
await BrowserTestUtils.browserLoaded(browser);
info(
`Redirecting from ${TEST_DOMAIN_HTTPS} to ${TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS}`
);
await redirectWithUserInteraction(
browser,
TEST_REDIRECT_ANOTHER_3RD_PARTY_PAGE,
TEST_TOP_PAGE_HTTPS
);
info("Checking if a permission was set between the redirect");
const principal = browser.contentPrincipal;
is(
Services.perms.testPermissionFromPrincipal(
principal,
`3rdPartyStorage^${TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS.slice(0, -1)}`
),
Services.perms.UNKNOWN_ACTION,
"No permission was set for same-site redirect"
);
info("Removing the tab");
BrowserTestUtils.removeTab(tab);
await cleanup();
}
add_task(async function testRedirectHeuristic() {
await runTestRedirectHeuristic(false);
});
add_task(runTestRedirectHeuristicWithSameSite);
add_task(async function testRedirectHeuristicDisabled() {
await runTestRedirectHeuristic(true);
});
class UpdateEvent extends EventTarget {}
function waitForEvent(element, eventName) {
return new Promise(function (resolve) {
element.addEventListener(eventName, e => resolve(e.detail), { once: true });
});
}
// The test URLs have a trailing / which means they're not valid origins.
const TEST_ORIGIN = TEST_DOMAIN.substring(0, TEST_DOMAIN.length - 1);
const TEST_3RD_PARTY_ORIGIN = TEST_3RD_PARTY_DOMAIN.substring(
0,
TEST_3RD_PARTY_DOMAIN.length - 1
);
async function runTestExceptionListPref(disableHeuristics) {
info("Starting Dynamic FPI exception list test pref");
await SpecialPowers.pushPrefEnv({
set: [
["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
["privacy.antitracking.enableWebcompat", !disableHeuristics],
],
});
info("Creating new tabs");
let tabThirdParty = BrowserTestUtils.addTab(
gBrowser,
TEST_3RD_PARTY_PARTITIONED_PAGE
);
gBrowser.selectedTab = tabThirdParty;
let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
await BrowserTestUtils.browserLoaded(browserThirdParty);
let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tabFirstParty;
let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
await BrowserTestUtils.browserLoaded(browserFirstParty);
info("initializing...");
await Promise.all([
checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
checkData(browserThirdParty, { firstParty: "" }),
]);
info("fill default data");
await Promise.all([
createDataInFirstParty(browserFirstParty, "firstParty"),
createDataInThirdParty(browserFirstParty, "thirdParty"),
createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
]);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "thirdParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set exception list pref");
Services.prefs.setStringPref(
EXCEPTION_LIST_PREF_NAME,
`${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}`
);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set incomplete exception list pref");
Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN}`);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "thirdParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set exception list pref, with extra semicolons");
Services.prefs.setStringPref(
EXCEPTION_LIST_PREF_NAME,
`;${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN};;`
);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set exception list pref, with subdomain wildcard");
Services.prefs.setStringPref(
EXCEPTION_LIST_PREF_NAME,
`${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN.replace("tracking", "*")}`
);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: disableHeuristics ? "thirdParty" : "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("Removing the tab");
BrowserTestUtils.removeTab(tabFirstParty);
BrowserTestUtils.removeTab(tabThirdParty);
await SpecialPowers.popPrefEnv();
await cleanup();
}
add_task(async function testExceptionListPref() {
await runTestExceptionListPref(false);
});
add_task(async function testExceptionListPrefDisabled() {
await runTestExceptionListPref(true);
});
add_task(async function testExceptionListRemoteSettings() {
info("Starting Dynamic FPI exception list test (remote settings)");
await SpecialPowers.pushPrefEnv({
set: [
["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
],
});
// Make sure we have a pref initially, since the exception list service
// requires it.
Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, "");
// Add some initial data
let db = RemoteSettings(COLLECTION_NAME).db;
await db.importChanges({}, Date.now(), []);
// make peuSerivce start working by calling
// registerAndRunExceptionListObserver
let updateEvent = new UpdateEvent();
let obs = data => {
let event = new CustomEvent("update", { detail: data });
updateEvent.dispatchEvent(event);
};
let promise = waitForEvent(updateEvent, "update");
peuService.registerAndRunExceptionListObserver(obs);
await promise;
info("Creating new tabs");
let tabThirdParty = BrowserTestUtils.addTab(
gBrowser,
TEST_3RD_PARTY_PARTITIONED_PAGE
);
gBrowser.selectedTab = tabThirdParty;
let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
await BrowserTestUtils.browserLoaded(browserThirdParty);
let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tabFirstParty;
let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
await BrowserTestUtils.browserLoaded(browserFirstParty);
info("initializing...");
await Promise.all([
checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
checkData(browserThirdParty, { firstParty: "" }),
]);
info("fill default data");
await Promise.all([
createDataInFirstParty(browserFirstParty, "firstParty"),
createDataInThirdParty(browserFirstParty, "thirdParty"),
createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
]);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "thirdParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set exception list remote settings");
// set records
promise = waitForEvent(updateEvent, "update");
await RemoteSettings(COLLECTION_NAME).emit("sync", {
data: {
current: [
{
id: "1",
last_modified: 1000000000000001,
firstPartyOrigin: TEST_ORIGIN,
thirdPartyOrigin: TEST_3RD_PARTY_ORIGIN,
},
],
},
});
let list = await promise;
is(
list,
`${TEST_ORIGIN},${TEST_3RD_PARTY_ORIGIN}`,
"exception list is correctly set"
);
info("check data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("Removing the tab");
BrowserTestUtils.removeTab(tabFirstParty);
BrowserTestUtils.removeTab(tabThirdParty);
promise = waitForEvent(updateEvent, "update");
await RemoteSettings(COLLECTION_NAME).emit("sync", {
data: {
current: [],
},
});
is(await promise, "", "Exception list is cleared");
peuService.unregisterExceptionListObserver(obs);
await cleanup();
});
add_task(async function testWildcardExceptionListPref() {
info("Starting Dynamic FPI wirdcard exception list test pref");
await SpecialPowers.pushPrefEnv({
set: [
["privacy.restrict3rdpartystorage.heuristic.recently_visited", false],
],
});
info("Creating new tabs");
let tabThirdParty = BrowserTestUtils.addTab(
gBrowser,
TEST_3RD_PARTY_PARTITIONED_PAGE
);
gBrowser.selectedTab = tabThirdParty;
let browserThirdParty = gBrowser.getBrowserForTab(tabThirdParty);
await BrowserTestUtils.browserLoaded(browserThirdParty);
let tabFirstParty = BrowserTestUtils.addTab(gBrowser, TEST_TOP_PAGE);
gBrowser.selectedTab = tabFirstParty;
let browserFirstParty = gBrowser.getBrowserForTab(tabFirstParty);
await BrowserTestUtils.browserLoaded(browserFirstParty);
info("initializing...");
await Promise.all([
checkData(browserFirstParty, { firstParty: "", thirdParty: "" }),
checkData(browserThirdParty, { firstParty: "" }),
]);
info("fill default data");
await Promise.all([
createDataInFirstParty(browserFirstParty, "firstParty"),
createDataInThirdParty(browserFirstParty, "thirdParty"),
createDataInFirstParty(browserThirdParty, "ExceptionListFirstParty"),
]);
info("check initial data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "thirdParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set wildcard (1st-party) pref");
Services.prefs.setStringPref(
EXCEPTION_LIST_PREF_NAME,
`*,${TEST_3RD_PARTY_ORIGIN}`
);
info("check wildcard (1st-party) data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set invalid exception list pref");
Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, "*,*");
info("check initial data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "thirdParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("set wildcard (3rd-party) pref");
Services.prefs.setStringPref(EXCEPTION_LIST_PREF_NAME, `${TEST_ORIGIN},*`);
info("check wildcard (3rd-party) data");
await Promise.all([
checkData(browserFirstParty, {
firstParty: "firstParty",
thirdParty: "ExceptionListFirstParty",
}),
checkData(browserThirdParty, { firstParty: "ExceptionListFirstParty" }),
]);
info("Removing the tab");
BrowserTestUtils.removeTab(tabFirstParty);
BrowserTestUtils.removeTab(tabThirdParty);
await cleanup();
});