"use strict"; /** * This test verifies that the extension API's access to cookies is consistent * with the cookies as seen by web pages under the following modes: * - Every top-level document shares the same cookie jar, every subdocument of * the top-level document has a distinct cookie jar tied to the site of the * top-level document (dFPI). * - All documents have a cookie jar keyed by the domain of the top-level * document (FPI). * - All cookies are in one cookie jar (classic behavior = no FPI nor dFPI) * * FPI and dFPI are implemented using OriginAttributes, and historically the * consequence of not recognizing an origin attribute is that cookies cannot be * deleted. Hence, the functionality of the cookies API is verified as follows, * by the testCookiesAPI/runTestCase methods. * * 1. Load page that creates cookies for the top and a framed document: * - "delete_me" * - "edit_me" * 2. cookies.getAll: get all cookies with extension API. * 3. cookies.remove: Remove "delete_me" cookies with the extension API. * 4. cookies.set: Edit "edit_me" cookie with the extension API. * 5. Verify that the web page can see "edit_me" cookie (via document.cookie). * 6. cookies.get: "edit_me" is still present. * 7. cookies.remove: "edit_me" can be removed. * 8. cookies.getAll: no cookies left. */ const FIRST_DOMAIN = "first.example.com"; const FIRST_DOMAIN_ETLD_PLUS_1 = "example.com"; const FIRST_DOMAIN_ETLD_PLUS_MANY = "nested.under.first.example.com"; const THIRD_PARTY_DOMAIN = "third.example.net"; const server = createHttpServer({ hosts: [FIRST_DOMAIN, FIRST_DOMAIN_ETLD_PLUS_MANY, THIRD_PARTY_DOMAIN], }); const LOCAL_IP_AND_PORT = `127.0.0.1:${server.identity.primaryPort}`; server.registerPathHandler("/top", (request, response) => { response.setHeader("Set-Cookie", `delete_me=top; SameSite=none`); response.setHeader("Set-Cookie", `edit_me=top; SameSite=none`, true); response.setHeader("Content-Type", "text/html; charset=utf-8", false); response.write( `` ); }); server.registerPathHandler("/framed", (request, response) => { response.setHeader("Set-Cookie", `delete_me=frame; SameSite=none`); response.setHeader("Set-Cookie", `edit_me=frame; SameSite=none`, true); }); // Background script of the extension that drives the test. // It first waits for the content scripts in /top and /framed to connect, // in order to verify that cookie operations by the extension API are reflected // to the web page (verified through document.cookie from the content script). function backgroundScript() { let portsByDomain = new Map(); async function getDocumentCookies(port) { return new Promise(resolve => { port.onMessage.addListener(function listener(cookieString) { port.onMessage.removeListener(listener); resolve(cookieString); }); port.postMessage("get_cookies"); }); } // Stringify cookie identifier for comparisons in assertions. function stringifyCookie(cookie) { if (!cookie) { return "COOKIE MISSING"; } let domain = cookie.domain; if (!domain) { // The return value of `cookies.remove` has a URL instead of a domain. domain = new URL(cookie.url).hostname; } return `${cookie.name} domain=${domain} firstPartyDomain=${ cookie.firstPartyDomain } partitionKey=${JSON.stringify(cookie.partitionKey)}`; } function stringifyCookies(cookies) { return cookies.map(stringifyCookie).sort().join(" , "); } // detailsIn may have partitionKey and firstPartyDomain attributes. // expectedOut has partitionKey and firstPartyDomain attributes. async function runTestCase({ domain, detailsIn, expectedOut }) { const port = portsByDomain.get(domain); browser.test.assertTrue(port, `Got port to document for ${domain}`); let allCookies = await browser.cookies.getAll({ domain, firstPartyDomain: null, partitionKey: {}, }); let allCookiesWithFPD = await browser.cookies.getAll({ domain, ...detailsIn, }); browser.test.assertEq( stringifyCookies(allCookies), stringifyCookies(allCookiesWithFPD), "cookies.getAll returns consistent results" ); for (let [key, expectedValue] of Object.entries(expectedOut)) { expectedValue = JSON.stringify(expectedValue); browser.test.assertTrue( allCookies.every(c => JSON.stringify(c[key]) === expectedValue), `All ${allCookies.length} cookies have ${key}=${expectedValue}` ); } // delete_me: get, remove, get. const cookieToDelete = { url: `http://${domain}/`, name: "delete_me", ...detailsIn, }; const deletedCookie = { ...cookieToDelete, ...expectedOut, }; browser.test.assertEq( stringifyCookie(deletedCookie), stringifyCookie(await browser.cookies.get(cookieToDelete)), "delete_me cookie exists before removal" ); browser.test.assertEq( stringifyCookie(deletedCookie), stringifyCookie(await browser.cookies.remove(cookieToDelete)), "delete_me cookie has been removed by cookies.remove" ); browser.test.assertEq( null, await browser.cookies.get(cookieToDelete), "delete_me cookie does not exist any more" ); // edit_me: set, retrieve via document.cookie const cookieToEdit = { url: `http://${domain}/`, name: "edit_me", ...detailsIn, }; const editedCookie = await browser.cookies.set({ ...cookieToEdit, value: `new_value_${domain}`, }); browser.test.assertEq( stringifyCookie({ ...cookieToEdit, ...expectedOut }), stringifyCookie(editedCookie), "edit_me cookie updated" ); browser.test.assertEq( await getDocumentCookies(port), `edit_me=new_value_${domain}`, "Expected cookies after removing and editing a cookie" ); // edit_me: get, remove, getAll. browser.test.assertEq( stringifyCookie(editedCookie), stringifyCookie(await browser.cookies.get(cookieToEdit)), "edit_me cookie still exists" ); await browser.cookies.remove(cookieToEdit); let allCookiesAtEnd = await browser.cookies.getAll({ domain, firstPartyDomain: null, partitionKey: {}, }); browser.test.assertEq( "[]", JSON.stringify(allCookiesAtEnd), "No cookies left" ); } let resolveTestReady; let testReadyPromise = new Promise(resolve => { resolveTestReady = resolve; }); browser.test.onMessage.addListener(async (msg, testCase) => { await testReadyPromise; browser.test.assertEq("runTest", msg, `Starting: ${testCase.description}`); try { await runTestCase(testCase); } catch (e) { browser.test.fail(`Unexpected error: ${e} :: ${e.stack}`); } browser.test.sendMessage("runTest_done"); }); // cookie-checker-contentscript.js will connect. browser.runtime.onConnect.addListener(port => { portsByDomain.set(port.name, port); browser.test.log(`Got port #${portsByDomain.size} ${port.name}`); if (portsByDomain.size === 2) { // The top document and the embedded frame has loaded and the // content script that we use to read cookies is connected. // The test can now start. resolveTestReady(); } }); } // The primary purpose of this test is to verify that the cookies API can read // and write cookies that are actually in use by the web page. async function testCookiesAPI({ testCases, topDomain = FIRST_DOMAIN }) { let extension = ExtensionTestUtils.loadExtension({ background: backgroundScript, manifest: { permissions: [ "cookies", // Remove port to work around bug 1350523. `*://${topDomain.replace(/:\d+$/, "")}/*`, `*://${THIRD_PARTY_DOMAIN}/*`, ], content_scripts: [ { js: ["cookie-checker-contentscript.js"], matches: [ // Remove port to work around bug 1362809. `*://${topDomain.replace(/:\d+$/, "")}/top`, `*://${THIRD_PARTY_DOMAIN}/framed`, ], all_frames: true, run_at: "document_end", }, ], }, files: { "cookie-checker-contentscript.js": () => { const port = browser.runtime.connect({ name: location.hostname }); port.onMessage.addListener(msg => { browser.test.assertEq(msg, "get_cookies", "Expected port message"); port.postMessage(document.cookie); }); }, }, }); await extension.startup(); let contentPage = await ExtensionTestUtils.loadContentPage( `http://${topDomain}/top` ); for (let testCase of testCases) { info(`Running test case: ${testCase.description}`); extension.sendMessage("runTest", testCase); await extension.awaitMessage("runTest_done"); } await contentPage.close(); await extension.unload(); } add_task(async function setup() { // SameSite=none is needed to set cookies in third-party contexts. // SameSite=none usually requires Secure, but the test server doesn't support // https, so disable the Secure requirement for SameSite=none. Services.prefs.setBoolPref( "network.cookie.sameSite.noneRequiresSecure", false ); }); add_task(async function test_no_partitioning() { const testCases = [ { description: "first-party cookies without any partitioning", domain: FIRST_DOMAIN, detailsIn: { firstPartyDomain: "", partitionKey: null, }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies without any partitioning", domain: THIRD_PARTY_DOMAIN, detailsIn: { // Without (d)FPI, firstPartyDomain and partitionKey are optional. }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, ]; await runWithPrefs( // dFPI is enabled by default on Nightly, disable it. [["network.cookie.cookieBehavior", 4]], () => testCookiesAPI({ testCases }) ); }); add_task(async function test_firstPartyIsolate() { const testCases = [ { description: "first-party cookies with FPI", domain: FIRST_DOMAIN, detailsIn: { firstPartyDomain: FIRST_DOMAIN_ETLD_PLUS_1, }, expectedOut: { firstPartyDomain: FIRST_DOMAIN_ETLD_PLUS_1, partitionKey: null, }, }, { description: "third-party cookies with FPI", domain: THIRD_PARTY_DOMAIN, detailsIn: { firstPartyDomain: FIRST_DOMAIN_ETLD_PLUS_1, }, expectedOut: { firstPartyDomain: FIRST_DOMAIN_ETLD_PLUS_1, partitionKey: null, }, }, ]; await runWithPrefs( [ // FPI is mutually exclusive with dFPI. Disable dFPI. ["network.cookie.cookieBehavior", 4], ["privacy.firstparty.isolate", true], ], () => testCookiesAPI({ testCases }) ); }); add_task(async function test_dfpi() { const testCases = [ { description: "first-party cookies with dFPI", domain: FIRST_DOMAIN, detailsIn: { // partitionKey is optional and expected to default to unpartitioned. }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies with dFPI", domain: THIRD_PARTY_DOMAIN, detailsIn: { partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` }, }, expectedOut: { firstPartyDomain: "", partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` }, }, }, ]; await runWithPrefs( // Enable dFPI; 5 = BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN. [["network.cookie.cookieBehavior", 5]], () => testCookiesAPI({ testCases }) ); }); add_task(async function test_dfpi_with_ip_and_port() { const testCases = [ { description: "first-party cookies for IP with port", domain: "127.0.0.1", detailsIn: { partitionKey: null, }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies for IP with port", domain: THIRD_PARTY_DOMAIN, detailsIn: { partitionKey: { topLevelSite: `http://${LOCAL_IP_AND_PORT}` }, }, expectedOut: { firstPartyDomain: "", partitionKey: { topLevelSite: `http://${LOCAL_IP_AND_PORT}` }, }, }, ]; await runWithPrefs( // Enable dFPI; 5 = BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN. [["network.cookie.cookieBehavior", 5]], () => testCookiesAPI({ testCases, topDomain: LOCAL_IP_AND_PORT }) ); }); add_task(async function test_dfpi_with_nested_subdomains() { const testCases = [ { description: "first-party cookies with DFPI at eTLD+many", domain: FIRST_DOMAIN_ETLD_PLUS_MANY, detailsIn: { partitionKey: null, }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies for first party with eTLD+many", domain: THIRD_PARTY_DOMAIN, detailsIn: { // Partitioned cookies are keyed by eTLD+1, so even if eTLD+many is // passed, then eTLD+1 is stored (and returned). partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_MANY}` }, }, expectedOut: { firstPartyDomain: "", partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` }, }, }, ]; await runWithPrefs( // Enable dFPI; 5 = BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN. [["network.cookie.cookieBehavior", 5]], () => testCookiesAPI({ testCases, topDomain: FIRST_DOMAIN_ETLD_PLUS_MANY }) ); }); add_task(async function test_dfpi_with_non_default_use_site() { // privacy.dynamic_firstparty.use_site is a pref that can be used to toggle // the internal representation of partitionKey. True (default) means keyed // by site (scheme, host, port); false means keyed by host only. const testCases = [ { description: "first-party cookies with dFPI and use_site=false", domain: FIRST_DOMAIN, detailsIn: { partitionKey: null, }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies with dFPI and use_site=false", domain: THIRD_PARTY_DOMAIN, detailsIn: { partitionKey: { topLevelSite: `http://${FIRST_DOMAIN_ETLD_PLUS_1}` }, }, expectedOut: { firstPartyDomain: "", // When use_site=false, the scheme is not stored, and the // implementation just prepends "https" as a dummy scheme. partitionKey: { topLevelSite: `https://${FIRST_DOMAIN_ETLD_PLUS_1}` }, }, }, ]; await runWithPrefs( [ // Enable dFPI; 5 = BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN. ["network.cookie.cookieBehavior", 5], ["privacy.dynamic_firstparty.use_site", false], ], () => testCookiesAPI({ testCases }) ); }); add_task(async function test_dfpi_with_ip_and_port_and_non_default_use_site() { // privacy.dynamic_firstparty.use_site is a pref that can be used to toggle // the internal representation of partitionKey. True (default) means keyed // by site (scheme, host, port); false means keyed by host only. const testCases = [ { description: "first-party cookies for IP:port with dFPI+use_site=false", domain: "127.0.0.1", detailsIn: { partitionKey: null, }, expectedOut: { firstPartyDomain: "", partitionKey: null, }, }, { description: "third-party cookies for IP:port with dFPI+use_site=false", domain: THIRD_PARTY_DOMAIN, detailsIn: { // When use_site=false, the scheme is not stored in the internal // representation of the partitionKey. So even though the web page // creates the cookie at HTTP, the cookies are still detected when // "https" is used. partitionKey: { topLevelSite: `https://${LOCAL_IP_AND_PORT}` }, }, expectedOut: { firstPartyDomain: "", // When use_site=false, the scheme and port are not stored. // "https" is used as a dummy scheme, and the port is not used. partitionKey: { topLevelSite: "https://127.0.0.1" }, }, }, ]; await runWithPrefs( [ // Enable dFPI; 5 = BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN. ["network.cookie.cookieBehavior", 5], ["privacy.dynamic_firstparty.use_site", false], ], () => testCookiesAPI({ testCases, topDomain: LOCAL_IP_AND_PORT }) ); }); add_task(async function dfpi_invalid_partitionKey() { AddonTestUtils.init(globalThis); AddonTestUtils.createAppInfo( "xpcshell@tests.mozilla.org", "XPCShell", "1", "42" ); // The test below uses the browser.privacy API, which relies on // ExtensionSettingsStore, which in turn depends on AddonManager. await AddonTestUtils.promiseStartupManager(); let extension = ExtensionTestUtils.loadExtension({ useAddonManager: "temporary", manifest: { permissions: ["cookies", "*://example.com/*", "privacy"], }, async background() { const url = "http://example.com/"; const name = "dfpi_invalid_partitionKey_dummy_name"; const value = "1"; // Shorthands to minimize boilerplate. const set = d => browser.cookies.set({ url, name, value, ...d }); const remove = d => browser.cookies.remove({ url, name, ...d }); const get = d => browser.cookies.get({ url, name, ...d }); const getAll = d => browser.cookies.getAll(d); await browser.test.assertRejects( set({ partitionKey: { topLevelSite: "example.net" } }), /Invalid value for 'partitionKey' attribute/, "partitionKey must be a URL, not a domain" ); await browser.test.assertRejects( set({ partitionKey: { topLevelSite: "chrome://foo" } }), /Invalid value for 'partitionKey' attribute/, "partitionKey cannot be the chrome:-scheme (canonicalization fails)" ); await browser.test.assertRejects( set({ partitionKey: { topLevelSite: "chrome://foo/foo/foo" } }), /Invalid value for 'partitionKey' attribute/, "partitionKey cannot be the chrome:-scheme (canonicalization passes)" ); await browser.test.assertRejects( set({ partitionKey: { topLevelSite: "http://[]:" } }), /Invalid value for 'partitionKey' attribute/, "partitionKey must be a valid URL" ); browser.test.assertThrows( () => get({ partitionKey: "" }), /Error processing partitionKey: Expected object instead of ""/, "cookies.get should reject invalid partitionKey (string)" ); browser.test.assertThrows( () => get({ partitionKey: { topLevelSite: "http://x", badkey: 0 } }), /Error processing partitionKey: Unexpected property "badkey"/, "cookies.get should reject unsupported keys in partitionKey" ); await browser.test.assertRejects( remove({ partitionKey: { topLevelSite: "invalid" } }), /Invalid value for 'partitionKey' attribute/, "cookies.remove should reject invalid partitionKey.topLevelSite" ); await browser.test.assertRejects( get({ partitionKey: { topLevelSite: "invalid" } }), /Invalid value for 'partitionKey' attribute/, "cookies.get should reject invalid partitionKey.topLevelSite" ); await browser.test.assertRejects( getAll({ partitionKey: { topLevelSite: "invalid" } }), /Invalid value for 'partitionKey' attribute/, "cookies.getAll should reject invalid partitionKey.topLevelSite" ); // firstPartyDomain and partitionKey are mutually exclusive, because // FPI and dFPI are mutually exclusive. await browser.test.assertRejects( set({ firstPartyDomain: "example.net", partitionKey: {} }), /Partitioned cookies cannot have a 'firstPartyDomain' attribute./, "partitionKey and firstPartyDomain cannot both be non-empty" ); // On Nightly, dFPI is enabled by default. We have to disable it first, // before we can enable FPI. Otherwise we would get error: // Can't enable firstPartyIsolate when cookieBehavior is 'reject_trackers_and_partition_foreign' await browser.privacy.websites.cookieConfig.set({ value: { behavior: "reject_trackers" }, }); await browser.privacy.websites.firstPartyIsolate.set({ value: true, }); // FPI and dFPI are mutually exclusive. FPI is documented to require the // firstPartyDomain attribute, let's verify that, despite it being // technically possible to support both attributes. for (let cookiesMethod of [get, getAll, remove, set]) { await browser.test.assertRejects( cookiesMethod({ partitionKey: { topLevelSite: url } }), /First-Party Isolation is enabled, but the required 'firstPartyDomain' attribute was not set./, `cookies.${cookiesMethod.name} requires firstPartyDomain when FPI is enabled` ); } // The pref changes above (to dFPI/FPI) via the browser.privacy API will // be undone when the extension unloads. browser.test.sendMessage("test_done"); }, }); await extension.startup(); await extension.awaitMessage("test_done"); await extension.unload(); await AddonTestUtils.promiseShutdownManager(); }); add_task(async function dfpi_moz_extension() { let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["cookies", "*://example.com/*"], }, async background() { let cookie = await browser.cookies.set({ url: "http://example.com/", name: "moz_ext_party", value: "1", // moz-extension: URL is passed here, in an attempt to mark the cookie // as part of the "moz-extension:"-partition. Below we will expect "" // because the dFPI implementation treats "moz-extension" as // unpartitioned, see // https://searchfox.org/mozilla-central/rev/ac7da6c7306d86e2f86a302ce1e170ad54b3c1fe/caps/OriginAttributes.cpp#79-82 partitionKey: { topLevelSite: browser.runtime.getURL("/") }, }); browser.test.assertEq( null, cookie.partitionKey, "Cookies in moz-extension:-URL are unpartitioned" ); let deletedCookie = await browser.cookies.remove({ url: "http://example.com/", name: "moz_ext_party", partitionKey: { topLevelSite: "moz-extension://ignoreme" }, }); browser.test.assertEq( null, deletedCookie.partitionKey, "moz-extension:-partition key is treated as unpartitioned" ); browser.test.sendMessage("test_done"); }, }); await extension.startup(); await extension.awaitMessage("test_done"); await extension.unload(); }); add_task(async function dfpi_about_scheme_as_partitionKey() { let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["cookies", "*://example.com/*"], }, async background() { let cookie = await browser.cookies.set({ url: "http://example.com/", name: "moz_ext_party", value: "1", partitionKey: { topLevelSite: "about:blank" }, }); // It doesn't really make sense to partition in `about:blank` (since it // cannot really be a first party), but for completeness of test coverage // we also check that the use of an about:-scheme results in predictable // behavior. The weird "about://"-URL below is the serialization of the // internal value of the partitionKey attribute: // https://searchfox.org/mozilla-central/rev/ac7da6c7306d86e2f86a302ce1e170ad54b3c1fe/caps/OriginAttributes.cpp#73-77 browser.test.assertEq( "about://about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla", cookie.partitionKey.topLevelSite, "An URL-like representation of the internal about:-format is returned" ); let deletedCookie = await browser.cookies.remove({ url: "http://example.com/", name: "moz_ext_party", partitionKey: { topLevelSite: "about://about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla", }, }); browser.test.assertEq( "about://about.ef2a7dd5-93bc-417f-a698-142c3116864f.mozilla", deletedCookie.partitionKey.topLevelSite, "Cookie can be deleted via the dummy about:-scheme" ); browser.test.sendMessage("test_done"); }, }); await extension.startup(); await extension.awaitMessage("test_done"); await extension.unload(); }); // Same-site frames are expected to be unpartitioned. // The cookies API can receive partitionKey and url that are same-site. While // such cookies won't be sent to websites in practice, we do want to verify that // the behavior is predictable. add_task(async function test_url_is_same_site_as_partitionKey() { // This loads a page with a frame at third.example.net (= THIRD_PARTY_DOMAIN). let contentPage = await ExtensionTestUtils.loadContentPage( `http://${THIRD_PARTY_DOMAIN}/top` ); await contentPage.close(); let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["cookies", "*://third.example.net/"], }, async background() { // Retrieve all cookies, partitioned and unpartitioned. We expect only // unpartitioned cookies at first because the top frame and the child // frame have the same origin. let initialCookies = await browser.cookies.getAll({ partitionKey: {} }); browser.test.assertEq( "delete_me=frame,edit_me=frame", initialCookies.map(c => `${c.name}=${c.value}`).join(), "Same-site frames are in unpartitioned storage; /frame overwrites /top" ); browser.test.assertTrue( await browser.cookies.remove({ url: "https://third.example.net/", name: "delete_me", }), "Removed unpartitioned cookie" ); browser.test.assertEq( "[null,null]", JSON.stringify(initialCookies.map(c => c.partitionKey)), "Cookies in same-site/same-origin frames are not partitioned" ); // We only have one unpartitioned cookie (edit_cookie) left. // Add new cookie whose partitionKey is same-site relative to url. let newCookie = await browser.cookies.set({ url: "http://third.example.net/", name: "edit_me", value: "url_is_partitionKey_eTLD+2", partitionKey: { topLevelSite: "http://third.example.net" }, }); browser.test.assertEq( "http://example.net", newCookie.partitionKey.topLevelSite, "Created cookie with partitionKey=url; eTLD+2 is normalized as eTLD+1" ); browser.test.assertTrue( await browser.cookies.remove({ url: "http://third.example.net/", name: "edit_me", partitionKey: {}, }), "Removed unpartitioned cookie when partitionKey: {} is used" ); browser.test.assertEq( null, await browser.cookies.remove({ url: "http://third.example.net/", name: "edit_me", partitionKey: {}, }), "No more unpartitioned cookies to remove" ); browser.test.assertTrue( await browser.cookies.remove({ url: "http://third.example.net/", name: "edit_me", partitionKey: { topLevelSite: "http://example.net" }, }), "Removed partitioned cookie when partitionKey is passed" ); browser.test.sendMessage("test_done"); }, }); await extension.startup(); await extension.awaitMessage("test_done"); await extension.unload(); }); add_task(async function test_getAll_partitionKey() { let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["cookies", "*://third.example.net/"], }, async background() { const url = "http://third.example.net"; const name = "test_url_is_identical_to_partitionKey"; const partitionKey = { topLevelSite: "http://example.com" }; const firstPartyDomain = "example.net"; // Create non-partitioned cookie, create partitioned cookie. await browser.cookies.set({ url, name, value: "no_partition" }); await browser.cookies.set({ url, name, value: "fpd", firstPartyDomain }); await browser.cookies.set({ url, name, partitionKey, value: "party" }); // partitionKey + firstPartyDomain was tested in dfpi_invalid_partitionKey async function getAllValues(details) { let cookies = await browser.cookies.getAll(details); let values = cookies.map(c => c.value); return values.sort().join(); // Serialize for use with assertEq. } browser.test.assertEq( "no_partition", await getAllValues({}), "getAll() returns unpartitioned by default" ); browser.test.assertEq( "no_partition,party", await getAllValues({ partitionKey: {} }), "getAll() with partitionKey: {} returns all cookies" ); browser.test.assertEq( "party", await getAllValues({ partitionKey }), "getAll() with specific partitionKey returns partitionKey cookies only" ); browser.test.assertEq( "", await getAllValues({ partitionKey: { topLevelSite: url } }), "getAll() with partitionKey set to cookie URL does not match anything" ); browser.test.assertEq( "", await getAllValues({ partitionKey, firstPartyDomain }), "getAll() with non-empty partitionKey and firstPartyDomain does not match anything" ); browser.test.assertEq( "fpd", await getAllValues({ partitionKey: {}, firstPartyDomain }), "getAll() with empty partitionKey and firstPartyDomain matches fpd" ); browser.test.assertEq( "fpd,no_partition,party", await getAllValues({ partitionKey: {}, firstPartyDomain: null }), "getAll() with empty partitionKey and firstPartyDomain:null matches everything" ); await browser.cookies.remove({ url, name }); await browser.cookies.remove({ url, name, firstPartyDomain }); await browser.cookies.remove({ url, name, partitionKey }); browser.test.sendMessage("test_done"); }, }); await extension.startup(); await extension.awaitMessage("test_done"); await extension.unload(); }); add_task(async function no_unexpected_cookies_at_end_of_test() { let results = []; for (const cookie of Services.cookies.cookies) { results.push({ name: cookie.name, value: cookie.value, host: cookie.host, originAttributes: cookie.originAttributes, }); } Assert.deepEqual(results, [], "Test should not leave any cookies"); });