summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js590
1 files changed, 590 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js b/toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js
new file mode 100644
index 0000000000..e7dd1e99c6
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js
@@ -0,0 +1,590 @@
+"use strict";
+
+const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
+);
+
+const {
+ // cookieBehavior constants.
+ BEHAVIOR_REJECT,
+ BEHAVIOR_REJECT_TRACKER,
+} = Ci.nsICookieService;
+
+function createPage({ script, body = "" } = {}) {
+ if (script) {
+ body += `<script src="${script}"></script>`;
+ }
+
+ return `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ ${body}
+ </body>
+ </html>`;
+}
+
+const server = createHttpServer({ hosts: ["example.com", "itisatracker.org"] });
+server.registerDirectory("/data/", do_get_file("data"));
+server.registerPathHandler("/test-cookies", (request, response) => {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "text/json", false);
+ response.setHeader("Set-Cookie", "myKey=myCookie", true);
+ response.write('{"success": true}');
+});
+server.registerPathHandler("/subframe.html", (request, response) => {
+ response.write(createPage());
+});
+server.registerPathHandler("/page-with-tracker.html", (request, response) => {
+ response.write(
+ createPage({
+ body: `<iframe src="http://itisatracker.org/test-cookies"></iframe>`,
+ })
+ );
+});
+server.registerPathHandler("/sw.js", (request, response) => {
+ response.setHeader("Content-Type", "text/javascript", false);
+ response.write("");
+});
+
+function assertCookiesForHost(url, cookiesCount, message) {
+ const { host } = new URL(url);
+ const cookies = Services.cookies.cookies.filter(
+ cookie => cookie.host === host
+ );
+ equal(cookies.length, cookiesCount, message);
+ return cookies;
+}
+
+// Test that the indexedDB and localStorage are allowed in an extension page
+// and that the indexedDB is allowed in a extension worker.
+add_task(async function test_ext_page_allowed_storage() {
+ function testWebStorages() {
+ const url = window.location.href;
+
+ try {
+ // In a webpage accessing indexedDB throws on cookiesBehavior reject,
+ // here we verify that doesn't happen for an extension page.
+ browser.test.assertTrue(
+ indexedDB,
+ "IndexedDB global should be accessible"
+ );
+
+ // In a webpage localStorage is undefined on cookiesBehavior reject,
+ // here we verify that doesn't happen for an extension page.
+ browser.test.assertTrue(
+ localStorage,
+ "localStorage global should be defined"
+ );
+
+ const worker = new Worker("worker.js");
+ worker.onmessage = event => {
+ browser.test.assertTrue(
+ event.data.pass,
+ "extension page worker have access to indexedDB"
+ );
+
+ browser.test.sendMessage("test-storage:done", url);
+ };
+
+ worker.postMessage({});
+ } catch (err) {
+ browser.test.fail(`Unexpected error: ${err}`);
+ browser.test.sendMessage("test-storage:done", url);
+ }
+ }
+
+ function testWorker() {
+ this.onmessage = () => {
+ try {
+ void indexedDB;
+ postMessage({ pass: true });
+ } catch (err) {
+ postMessage({ pass: false });
+ throw err;
+ }
+ };
+ }
+
+ async function createExtension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ files: {
+ "test_web_storages.js": testWebStorages,
+ "worker.js": testWorker,
+ "page_subframe.html": createPage({ script: "test_web_storages.js" }),
+ "page_with_subframe.html": createPage({
+ body: '<iframe src="page_subframe.html"></iframe>',
+ }),
+ "page.html": createPage({
+ script: "test_web_storages.js",
+ }),
+ },
+ });
+
+ await extension.startup();
+
+ const EXT_BASE_URL = `moz-extension://${extension.uuid}/`;
+
+ return { extension, EXT_BASE_URL };
+ }
+
+ const cookieBehaviors = [
+ "BEHAVIOR_LIMIT_FOREIGN",
+ "BEHAVIOR_REJECT_FOREIGN",
+ "BEHAVIOR_REJECT",
+ "BEHAVIOR_REJECT_TRACKER",
+ "BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN",
+ ];
+ equal(
+ cookieBehaviors.length,
+ Ci.nsICookieService.BEHAVIOR_LAST,
+ "all behaviors should be covered"
+ );
+
+ for (const behavior of cookieBehaviors) {
+ info(
+ `Test extension page access to indexedDB & localStorage with ${behavior}`
+ );
+ ok(
+ behavior in Ci.nsICookieService,
+ `${behavior} is a valid CookieBehavior`
+ );
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService[behavior]
+ );
+
+ // Create a new extension to ensure that the cookieBehavior just set is going to be
+ // used for the requests triggered by the extension page.
+ const { extension, EXT_BASE_URL } = await createExtension();
+ const extPage = await ExtensionTestUtils.loadContentPage("about:blank", {
+ extension,
+ remote: extension.extension.remote,
+ });
+
+ info("Test from a top level extension page");
+ await extPage.loadURL(`${EXT_BASE_URL}page.html`);
+
+ let testedFromURL = await extension.awaitMessage("test-storage:done");
+ equal(
+ testedFromURL,
+ `${EXT_BASE_URL}page.html`,
+ "Got the results from the expected url"
+ );
+
+ info("Test from a sub frame extension page");
+ await extPage.loadURL(`${EXT_BASE_URL}page_with_subframe.html`);
+
+ testedFromURL = await extension.awaitMessage("test-storage:done");
+ equal(
+ testedFromURL,
+ `${EXT_BASE_URL}page_subframe.html`,
+ "Got the results from the expected url"
+ );
+
+ await extPage.close();
+ await extension.unload();
+ }
+});
+
+add_task(async function test_ext_page_3rdparty_cookies() {
+ if (AppConstants.platform === "android") {
+ // TODO bug 1844702: Fix test_ext_page_3rdparty_cookies on Android.
+ info("Skipped test_ext_page_3rdparty_cookies");
+ return;
+ }
+ // moz-extension:-document embeds http://example.com/page-with-tracker.html
+ allow_unsafe_parent_loads_when_extensions_not_remote();
+
+ // Disable tracking protection to test cookies on BEHAVIOR_REJECT_TRACKER
+ // (otherwise tracking protection would block the tracker iframe and
+ // we would not be actually checking the cookie behavior).
+ Services.prefs.setBoolPref("privacy.trackingprotection.enabled", false);
+ await UrlClassifierTestUtils.addTestTrackers();
+ registerCleanupFunction(function () {
+ UrlClassifierTestUtils.cleanupTestTrackers();
+ Services.prefs.clearUserPref("privacy.trackingprotection.enabled");
+ Services.cookies.removeAll();
+ });
+
+ function testRequestScript() {
+ browser.test.onMessage.addListener((msg, url) => {
+ const done = () => {
+ browser.test.sendMessage(`${msg}:done`);
+ };
+
+ switch (msg) {
+ case "xhr": {
+ let req = new XMLHttpRequest();
+ req.onload = done;
+ req.open("GET", url);
+ req.send();
+ break;
+ }
+ case "fetch": {
+ window.fetch(url).then(done);
+ break;
+ }
+ case "worker fetch": {
+ const worker = new Worker("test_worker.js");
+ worker.onmessage = evt => {
+ if (evt.data.requestDone) {
+ done();
+ }
+ };
+ worker.postMessage({ url });
+ break;
+ }
+ default: {
+ browser.test.fail(`Received an unexpected message: ${msg}`);
+ done();
+ }
+ }
+ });
+
+ browser.test.sendMessage("testRequestScript:ready", window.location.href);
+ }
+
+ function testWorker() {
+ this.onmessage = evt => {
+ fetch(evt.data.url).then(() => {
+ postMessage({ requestDone: true });
+ });
+ };
+ }
+
+ async function createExtension() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["http://example.com/*", "http://itisatracker.org/*"],
+ },
+ files: {
+ "test_worker.js": testWorker,
+ "test_request.js": testRequestScript,
+ "page_subframe.html": createPage({ script: "test_request.js" }),
+ "page_with_subframe.html": createPage({
+ body: '<iframe src="page_subframe.html"></iframe>',
+ }),
+ "page.html": createPage({ script: "test_request.js" }),
+ },
+ });
+
+ await extension.startup();
+
+ const EXT_BASE_URL = `moz-extension://${extension.uuid}`;
+
+ return { extension, EXT_BASE_URL };
+ }
+
+ const testUrl = "http://example.com/test-cookies";
+ const testRequests = ["xhr", "fetch", "worker fetch"];
+ const tests = [
+ { behavior: "BEHAVIOR_ACCEPT", cookiesCount: 1 },
+ { behavior: "BEHAVIOR_REJECT_FOREIGN", cookiesCount: 1 },
+ { behavior: "BEHAVIOR_REJECT", cookiesCount: 0 },
+ { behavior: "BEHAVIOR_LIMIT_FOREIGN", cookiesCount: 1 },
+ { behavior: "BEHAVIOR_REJECT_TRACKER", cookiesCount: 1 },
+ ];
+
+ function clearAllCookies() {
+ Services.cookies.removeAll();
+ let cookies = Services.cookies.cookies;
+ equal(cookies.length, 0, "There shouldn't be any cookies after clearing");
+ }
+
+ async function runTestRequests(extension, cookiesCount, msg) {
+ for (const testRequest of testRequests) {
+ clearAllCookies();
+ extension.sendMessage(testRequest, testUrl);
+ await extension.awaitMessage(`${testRequest}:done`);
+ assertCookiesForHost(
+ testUrl,
+ cookiesCount,
+ `${msg}: cookies count on ${testRequest} "${testUrl}"`
+ );
+ }
+ }
+
+ for (const { behavior, cookiesCount } of tests) {
+ info(`Test cookies on http requests with ${behavior}`);
+ ok(
+ behavior in Ci.nsICookieService,
+ `${behavior} is a valid CookieBehavior`
+ );
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ Ci.nsICookieService[behavior]
+ );
+
+ // Create a new extension to ensure that the cookieBehavior just set is going to be
+ // used for the requests triggered by the extension page.
+ const { extension, EXT_BASE_URL } = await createExtension();
+
+ // Run all the test requests on a top level extension page.
+ let extPage = await ExtensionTestUtils.loadContentPage(
+ `${EXT_BASE_URL}/page.html`,
+ {
+ extension,
+ remote: extension.extension.remote,
+ }
+ );
+ await extension.awaitMessage("testRequestScript:ready");
+ await runTestRequests(
+ extension,
+ cookiesCount,
+ `Test top level extension page on ${behavior}`
+ );
+ await extPage.close();
+
+ // Rerun all the test requests on a sub frame extension page.
+ extPage = await ExtensionTestUtils.loadContentPage(
+ `${EXT_BASE_URL}/page_with_subframe.html`,
+ {
+ extension,
+ remote: extension.extension.remote,
+ }
+ );
+ await extension.awaitMessage("testRequestScript:ready");
+ await runTestRequests(
+ extension,
+ cookiesCount,
+ `Test sub frame extension page on ${behavior}`
+ );
+ await extPage.close();
+
+ await extension.unload();
+ }
+
+ // Test tracking url blocking from a webpage subframe.
+ info(
+ "Testing blocked tracker cookies in webpage subframe on BEHAVIOR_REJECT_TRACKERS"
+ );
+ Services.prefs.setIntPref(
+ "network.cookie.cookieBehavior",
+ BEHAVIOR_REJECT_TRACKER
+ );
+
+ const trackerURL = "http://itisatracker.org/test-cookies";
+ const { extension, EXT_BASE_URL } = await createExtension();
+ const extPage = await ExtensionTestUtils.loadContentPage(
+ `${EXT_BASE_URL}/_generated_background_page.html`,
+ {
+ extension,
+ remote: extension.extension.remote,
+ }
+ );
+ clearAllCookies();
+
+ await extPage.spawn(
+ ["http://example.com/page-with-tracker.html"],
+ async iframeURL => {
+ const iframe = this.content.document.createElement("iframe");
+ iframe.setAttribute("src", iframeURL);
+ return new Promise(resolve => {
+ iframe.onload = () => resolve();
+ this.content.document.body.appendChild(iframe);
+ });
+ }
+ );
+
+ assertCookiesForHost(
+ trackerURL,
+ 0,
+ "Test cookies on web subframe inside top level extension page on BEHAVIOR_REJECT_TRACKER"
+ );
+ clearAllCookies();
+
+ await extPage.close();
+ await extension.unload();
+
+ revert_allow_unsafe_parent_loads_when_extensions_not_remote();
+});
+
+// Test that a webpage embedded as a subframe of an extension page is not allowed to use
+// IndexedDB and register a ServiceWorker when it shouldn't be based on the cookieBehavior.
+add_task(
+ async function test_webpage_subframe_storage_respect_cookiesBehavior() {
+ if (Services.appinfo.fissionAutostart) {
+ // TODO bug 1762638: Fix this test. It fails because it tries to read
+ // properties through .contentWindow cross-origin. That doesn't work with
+ // Fission enabled; Should spawn tasks in individual frames instead.
+ info("Skipped test_webpage_subframe_storage_respect_cookiesBehavior");
+ return;
+ }
+ // moz-extension://[uuid]/toplevel.html loads example.com/subframe.html
+ allow_unsafe_parent_loads_when_extensions_not_remote();
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["http://example.com/*"],
+ web_accessible_resources: ["subframe.html"],
+ },
+ files: {
+ "toplevel.html": createPage({
+ body: `
+ <iframe id="ext" src="subframe.html"></iframe>
+ <iframe id="web" src="http://example.com/subframe.html"></iframe>
+ `,
+ }),
+ "subframe.html": createPage(),
+ },
+ });
+
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT);
+
+ await extension.startup();
+
+ let extensionPage = await ExtensionTestUtils.loadContentPage(
+ `moz-extension://${extension.uuid}/toplevel.html`,
+ {
+ extension,
+ remote: extension.extension.remote,
+ }
+ );
+
+ let results = await extensionPage.spawn([], async () => {
+ let extFrame = this.content.document.querySelector("iframe#ext");
+ let webFrame = this.content.document.querySelector("iframe#web");
+
+ function testIDB(win) {
+ try {
+ void win.indexedDB;
+ return { success: true };
+ } catch (err) {
+ return { error: `${err}` };
+ }
+ }
+
+ async function testServiceWorker(win) {
+ try {
+ await win.navigator.serviceWorker.register("sw.js");
+ return { success: true };
+ } catch (err) {
+ return { error: `${err}` };
+ }
+ }
+
+ return {
+ extTopLevel: testIDB(this.content),
+ // TODO bug 1762638: Execute the following in their own tasks.
+ extSubFrame: testIDB(extFrame.contentWindow),
+ webSubFrame: testIDB(webFrame.contentWindow),
+ webServiceWorker: await testServiceWorker(webFrame.contentWindow),
+ };
+ });
+
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://example.com/subframe.html"
+ );
+
+ results.extSubFrameContent = await contentPage.spawn(
+ [extension.uuid],
+ uuid => {
+ return new Promise(resolve => {
+ let frame = this.content.document.createElement("iframe");
+ frame.setAttribute("src", `moz-extension://${uuid}/subframe.html`);
+ frame.onload = () => {
+ try {
+ void frame.contentWindow.indexedDB;
+ resolve({ success: true });
+ } catch (err) {
+ resolve({ error: `${err}` });
+ }
+ };
+ this.content.document.body.appendChild(frame);
+ });
+ }
+ );
+
+ Assert.deepEqual(
+ results.extTopLevel,
+ { success: true },
+ "IndexedDB allowed in a top level extension page"
+ );
+
+ Assert.deepEqual(
+ results.extSubFrame,
+ { success: true },
+ "IndexedDB allowed in a subframe extension page with a top level extension page"
+ );
+
+ Assert.deepEqual(
+ results.webSubFrame,
+ { error: "SecurityError: The operation is insecure." },
+ "IndexedDB not allowed in a subframe webpage with a top level extension page"
+ );
+ Assert.deepEqual(
+ results.webServiceWorker,
+ { error: "SecurityError: The operation is insecure." },
+ "IndexedDB and Cache not allowed in a service worker registered in the subframe webpage extension page"
+ );
+
+ Assert.deepEqual(
+ results.extSubFrameContent,
+ { success: true },
+ "IndexedDB allowed in a subframe extension page with a top level webpage"
+ );
+
+ await extensionPage.close();
+ await contentPage.close();
+
+ await extension.unload();
+
+ revert_allow_unsafe_parent_loads_when_extensions_not_remote();
+ }
+);
+
+// Test that the webpage's indexedDB and localStorage are still not allowed from a content script
+// when the cookie behavior doesn't allow it, even when they are allowed in the extension pages.
+add_task(async function test_content_script_on_cookieBehaviorReject() {
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT);
+
+ function contentScript() {
+ // Ensure that when the current cookieBehavior doesn't allow a webpage to use indexedDB
+ // or localStorage, then a WebExtension content script is not allowed to use it as well.
+ browser.test.assertThrows(
+ () => indexedDB,
+ /The operation is insecure/,
+ "a content script can't use indexedDB from a page where it is disallowed"
+ );
+
+ browser.test.assertThrows(
+ () => localStorage,
+ /The operation is insecure/,
+ "a content script can't use localStorage from a page where it is disallowed"
+ );
+
+ browser.test.notifyPass("cs_disallowed_storage");
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ content_scripts: [
+ {
+ matches: ["http://*/*/file_sample.html"],
+ js: ["content_script.js"],
+ },
+ ],
+ },
+ files: {
+ "content_script.js": contentScript,
+ },
+ });
+
+ await extension.startup();
+
+ let contentPage = await ExtensionTestUtils.loadContentPage(
+ "http://example.com/data/file_sample.html"
+ );
+
+ await extension.awaitFinish("cs_disallowed_storage");
+
+ await contentPage.close();
+ await extension.unload();
+});
+
+add_task(function clear_cookieBehavior_pref() {
+ Services.prefs.clearUserPref("network.cookie.cookieBehavior");
+});