Bug 1406675 - Allow storages in WebExtensions on customized cookieBehavior and lifetimePolicy prefs. r=aswan,asuth draft
authorLuca Greco <lgreco@mozilla.com>
Fri, 10 Nov 2017 00:49:33 +0100
changeset 708350 148c1a132ae32db86a4f57bf1e7d715f379b11cb
parent 707754 1e21d5a5ad26b361cd0afe4c6d1b4c0eb40197d2
child 743177 fe5ae692694382188e649ce7ccb9200e45241a50
push id92369
push userluca.greco@alcacoop.it
push dateWed, 06 Dec 2017 17:37:33 +0000
reviewersaswan, asuth
bugs1406675
milestone59.0a1
Bug 1406675 - Allow storages in WebExtensions on customized cookieBehavior and lifetimePolicy prefs. r=aswan,asuth This commit ensures that WebExtension principals always get a nsICookieService::BEHAVIOR_ACCEPT cookieBehavior and a nsICookieService::ACCEPT_NORMALLY aLifetimePolicy: - the webextension pages are still able to use indexedDB and localStorage on a globally configured: "network.cookie.cookieBehavior = 2" ("Accept cookies from websites" unchecked in the about:preferences "use custom settings for history" section) - the webextension pages' localStorage does not switch in session-only mode on a globally configured: "network.cookie.lifetimePolicy = 2" ("Keep until I close Firefox" in the about:preferences "use custom settings for history" section) MozReview-Commit-ID: 5LOCvCgcokM
dom/base/nsContentUtils.cpp
toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9133,17 +9133,27 @@ nsContentUtils::InternalStorageAllowedFo
     // Check if we are in private browsing, and record that fact
     if (IsInPrivateBrowsing(document)) {
       access = StorageAccess::ePrivateBrowsing;
     }
   }
 
   uint32_t lifetimePolicy;
   uint32_t behavior;
-  GetCookieBehaviorForPrincipal(aPrincipal, &lifetimePolicy, &behavior);
+
+  // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
+  // and ACCEPT_NORMALLY as lifetimePolicy (See Bug 1406675 for rationale).
+  auto policy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
+
+  if (policy) {
+    behavior = nsICookieService::BEHAVIOR_ACCEPT;
+    lifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
+  } else {
+    GetCookieBehaviorForPrincipal(aPrincipal, &lifetimePolicy, &behavior);
+  }
 
   // Check if we should only allow storage for the session, and record that fact
   if (lifetimePolicy == nsICookieService::ACCEPT_SESSION) {
     // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
     // so perform a std::min comparison to make sure we preserve ePrivateBrowsing
     // if it has been set.
     access = std::min(StorageAccess::eSessionScoped, access);
   }
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_cookieBehaviors.js
@@ -0,0 +1,216 @@
+"use strict";
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ExtensionParent.jsm");
+
+const {
+  // cookieBehavior constants.
+  BEHAVIOR_REJECT_FOREIGN,
+  BEHAVIOR_REJECT,
+  BEHAVIOR_LIMIT_FOREIGN,
+
+  // lifetimePolicy constants.
+  ACCEPT_SESSION,
+} = Ci.nsICookieService;
+
+const server = createHttpServer();
+server.registerDirectory("/data/", do_get_file("data"));
+
+const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
+
+// Test that the indexedDB and localStorage are allowed in an extension background page
+// and that the indexedDB is allowed in a extension worker.
+async function test_bg_page_allowed_storage() {
+  function background() {
+    try {
+      void indexedDB;
+      void localStorage;
+
+      const worker = new Worker("worker.js");
+      worker.onmessage = (event) => {
+        if (event.data.pass) {
+          browser.test.notifyPass("bg_allowed_storage");
+        } else {
+          browser.test.notifyFail("bg_allowed_storage");
+        }
+      };
+
+      worker.postMessage({});
+    } catch (err) {
+      browser.test.notifyFail("bg_allowed_storage");
+      throw err;
+    }
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    files: {
+      "worker.js": function worker() {
+        this.onmessage = () => {
+          try {
+            void indexedDB;
+            postMessage({pass: true});
+          } catch (err) {
+            postMessage({pass: false});
+            throw err;
+          }
+        };
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("bg_allowed_storage");
+  await extension.unload();
+}
+
+add_task(async function test_ext_page_allowed_storage_on_cookieBehaviors() {
+  do_print("Test background page indexedDB with BEHAVIOR_LIMIT_FOREIGN");
+  Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_LIMIT_FOREIGN);
+  await test_bg_page_allowed_storage();
+
+  do_print("Test background page indexedDB with BEHAVIOR_REJECT_FOREIGN");
+  Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT_FOREIGN);
+  await test_bg_page_allowed_storage();
+
+  do_print("Test background page indexedDB with BEHAVIOR_REJECT");
+  Services.prefs.setIntPref("network.cookie.cookieBehavior", BEHAVIOR_REJECT);
+  await test_bg_page_allowed_storage();
+});
+
+// Test that the webpage's indexedDB and localStorage are still not allowed from a content script
+// when the cookie behavior, 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(`${BASE_URL}/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");
+});
+
+// Test that localStorage is not in session-only mode for the extension pages,
+// even when the session-only mode has been globally enabled.
+add_task(async function test_localStorage_on_session_lifetimePolicy() {
+  // localStorage in session-only mode.
+  Services.prefs.setIntPref("network.cookie.lifetimePolicy", ACCEPT_SESSION);
+
+  function background() {
+    localStorage.setItem("test-key", "test-value");
+
+    browser.test.sendMessage("bg_localStorage_set", {uuid: window.location.hostname});
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+  });
+
+  await extension.startup();
+  const {uuid} = await extension.awaitMessage("bg_localStorage_set");
+
+  const fakeAddonActor = {addonId: extension.id};
+  const addonBrowser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor);
+  const {isRemoteBrowser} = addonBrowser;
+
+  const {
+    isSessionOnly,
+    domStorageLength,
+    domStorageStoredValue,
+  } = await ContentTask.spawn(addonBrowser, {uuid, isRemoteBrowser}, (params) => {
+    const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+    let windowEnumerator = Services.ww.getWindowEnumerator();
+
+    let bgPageWindow;
+
+    // Search the background page window in the process where the extension is running.
+    while (windowEnumerator.hasMoreElements()) {
+      let win = windowEnumerator.getNext();
+
+      // When running in remote-webextension mode the window enumerator
+      // will only include top level windows related to the extension process
+      // (the background page and the "about:blank" related to the addonBrowser
+      // used to connect to the right process).
+
+      if (!params.isRemoteBrowser) {
+        if (win.location.href !== "chrome://extensions/content/dummy.xul") {
+          // When running in single process mode, all the top level windows
+          // will be enumerated, we ignore any window that is not an
+          // extension windowlessBrowser.
+          continue;
+        } else {
+          // from an extension windowlessBrowser, retrieve the background page window
+          // (which has been loaded inside a XUL browser element).
+          win = win.document.querySelector("browser").contentWindow;
+        }
+      }
+
+      if (win.location.hostname === params.uuid &&
+          win.location.pathname === "/_generated_background_page.html") {
+        // Once we have found the background page window related to the target extension
+        // we can exit the while loop.
+        bgPageWindow = win;
+        break;
+      }
+    }
+
+    if (!bgPageWindow) {
+      throw new Error("Unable to find the extension background page");
+    }
+
+    return {
+      isSessionOnly: bgPageWindow.localStorage.isSessionOnly,
+      domStorageLength: bgPageWindow.localStorage.length,
+      domStorageStoredValue: bgPageWindow.localStorage.getItem("test-key"),
+    };
+  });
+
+  await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(fakeAddonActor);
+
+  equal(isSessionOnly, false, "the extension localStorage is not set in session-only mode");
+  equal(domStorageLength, 1, "the extension storage contains the expected number of keys");
+  equal(domStorageStoredValue, "test-value", "the extension storage contains the expected data");
+
+  await extension.unload();
+});
+
+add_task(function clear_lifetimePolicy_pref() {
+  Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -11,16 +11,17 @@ skip-if = os == "android" # Android does
 [test_ext_background_runtime_connect_params.js]
 [test_ext_background_sub_windows.js]
 [test_ext_background_telemetry.js]
 [test_ext_background_window_properties.js]
 skip-if = os == "android"
 [test_ext_browserSettings.js]
 [test_ext_browserSettings_homepage.js]
 skip-if = os == "android"
+[test_ext_cookieBehaviors.js]
 [test_ext_contextual_identities.js]
 skip-if = os == "android" # Containers are not exposed to android.
 [test_ext_debugging_utils.js]
 [test_ext_downloads.js]
 [test_ext_downloads_download.js]
 skip-if = os == "android"
 [test_ext_downloads_misc.js]
 skip-if = os == "android" || (os=='linux' && bits==32) # linux32: bug 1324870