Bug 1302697 - Containers and WebExtensions - part 2 - Cookie API, r=kmag
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 28 Oct 2016 10:16:06 +0200
changeset 319973 691162eba71737f765cb0dda5470a3a516e0d47d
parent 319972 ca0b48d8543465cd633e6893882956fe7515d3ec
child 319974 4c05c0ff09ba00d59b0a5de16a395ef0ef9d87a6
push id20749
push userryanvm@gmail.com
push dateSat, 29 Oct 2016 13:21:21 +0000
treeherderfx-team@1b170b39ed6b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1302697
milestone52.0a1
Bug 1302697 - Containers and WebExtensions - part 2 - Cookie API, r=kmag
toolkit/components/extensions/ext-cookies.js
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -189,41 +189,59 @@ function* query(detailsIn, props, contex
       details[property] = detailsIn[property];
     }
   });
 
   if ("domain" in details) {
     details.domain = details.domain.toLowerCase().replace(/^\./, "");
   }
 
+  let userContextId = 0;
   let isPrivate = context.incognito;
-  if (details.storeId == DEFAULT_STORE) {
-    isPrivate = false;
-  } else if (details.storeId == PRIVATE_STORE) {
-    isPrivate = true;
+  if (details.storeId) {
+    if (!global.isValidCookieStoreId(details.storeId)) {
+      return;
+    }
+
+    if (global.isDefaultCookieStoreId(details.storeId)) {
+      isPrivate = false;
+    } else if (global.isPrivateCookieStoreId(details.storeId)) {
+      isPrivate = true;
+    } else if (global.isContainerCookieStoreId(details.storeId)) {
+      isPrivate = false;
+      userContextId = global.getContainerForCookieStoreId(details.storeId);
+      if (!userContextId) {
+        return;
+      }
+    }
+  }
+
+  let storeId = DEFAULT_STORE;
+  if (isPrivate) {
+    storeId = PRIVATE_STORE;
   } else if ("storeId" in details) {
-    return;
+    storeId = details.storeId;
   }
 
   // We can use getCookiesFromHost for faster searching.
   let enumerator;
   let uri;
   if ("url" in details) {
     try {
       uri = NetUtil.newURI(details.url).QueryInterface(Ci.nsIURL);
       Services.cookies.usePrivateMode(isPrivate, () => {
-        enumerator = Services.cookies.getCookiesFromHost(uri.host, {});
+        enumerator = Services.cookies.getCookiesFromHost(uri.host, {userContextId});
       });
     } catch (ex) {
       // This often happens for about: URLs
       return;
     }
   } else if ("domain" in details) {
     Services.cookies.usePrivateMode(isPrivate, () => {
-      enumerator = Services.cookies.getCookiesFromHost(details.domain, {});
+      enumerator = Services.cookies.getCookiesFromHost(details.domain, {userContextId});
     });
   } else {
     Services.cookies.usePrivateMode(isPrivate, () => {
       enumerator = Services.cookies.enumerator;
     });
   }
 
   // Based on nsCookieService::GetCookieStringInternal
@@ -264,16 +282,20 @@ function* query(detailsIn, props, contex
         return false;
       }
     }
 
     if ("name" in details && details.name != cookie.name) {
       return false;
     }
 
+    if (userContextId != cookie.originAttributes.userContextId) {
+      return false;
+    }
+
     // "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."
     if ("domain" in details && !isSubdomain(cookie.rawHost, details.domain)) {
       return false;
     }
 
     // "Restricts the retrieved cookies to those whose path exactly matches this string.""
     if ("path" in details && details.path != cookie.path) {
       return false;
@@ -293,17 +315,17 @@ function* query(detailsIn, props, contex
     }
 
     return true;
   }
 
   while (enumerator.hasMoreElements()) {
     let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
     if (matches(cookie)) {
-      yield {cookie, isPrivate};
+      yield {cookie, isPrivate, storeId};
     }
   }
 }
 
 extensions.registerSchemaAPI("cookies", "addon_parent", context => {
   let {extension} = context;
   let self = {
     cookies: {
@@ -340,49 +362,58 @@ extensions.registerSchemaAPI("cookies", 
 
         let name = details.name !== null ? details.name : "";
         let value = details.value !== null ? details.value : "";
         let secure = details.secure !== null ? details.secure : false;
         let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
         let isSession = details.expirationDate === null;
         let expiry = isSession ? Number.MAX_SAFE_INTEGER : details.expirationDate;
         let isPrivate = context.incognito;
-        if (details.storeId == DEFAULT_STORE) {
+        let userContextId = 0;
+        if (global.isDefaultCookieStoreId(details.storeId)) {
           isPrivate = false;
-        } else if (details.storeId == PRIVATE_STORE) {
+        } else if (global.isPrivateCookieStoreId(details.storeId)) {
           isPrivate = true;
+        } else if (global.isContainerCookieStoreId(details.storeId)) {
+          let containerId = global.getContainerForCookieStoreId(details.storeId);
+          if (containerId === null) {
+            return Promise.reject({message: `Illegal storeId: ${details.storeId}`});
+          }
+          isPrivate = false;
+          userContextId = containerId;
         } else if (details.storeId !== null) {
           return Promise.reject({message: "Unknown storeId"});
         }
 
         let cookieAttrs = {host: details.domain, path: path, isSecure: secure};
         if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
           return Promise.reject({message: `Permission denied to set cookie ${JSON.stringify(details)}`});
         }
 
         // The permission check may have modified the domain, so use
         // the new value instead.
         Services.cookies.usePrivateMode(isPrivate, () => {
           Services.cookies.add(cookieAttrs.host, path, name, value,
-                               secure, httpOnly, isSession, expiry, {});
+                               secure, httpOnly, isSession, expiry, {userContextId});
         });
 
         return self.cookies.get(details);
       },
 
       remove: function(details) {
-        for (let {cookie, isPrivate} of query(details, ["url", "name", "storeId"], context)) {
+        for (let {cookie, isPrivate, storeId} of query(details, ["url", "name", "storeId"], context)) {
           Services.cookies.usePrivateMode(isPrivate, () => {
             Services.cookies.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
           });
+
           // Todo: could there be multiple per subdomain?
           return Promise.resolve({
             url: details.url,
             name: details.name,
-            storeId: isPrivate ? PRIVATE_STORE : DEFAULT_STORE,
+            storeId,
           });
         }
 
         return Promise.resolve(null);
       },
 
       getAllCookieStores: function() {
         let defaultTabs = [];
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -16,13 +16,14 @@ skip-if = (os == 'android') # browser.ta
 [test_chrome_ext_trustworthy_origin.html]
 [test_chrome_ext_webnavigation_resolved_urls.html]
 skip-if = (os == 'android') # browser.tabs is undefined. Bug 1258975 on android.
 [test_chrome_ext_shutdown_cleanup.html]
 [test_chrome_native_messaging_paths.html]
 skip-if = os != "mac" && os != "linux"
 [test_ext_cookies_expiry.html]
 [test_ext_cookies_permissions.html]
+[test_ext_cookies_containers.html]
 [test_ext_jsversion.html]
 [test_ext_schema.html]
 [test_chrome_ext_storage_cleanup.html]
 [test_chrome_ext_idle.html]
 [test_chrome_ext_downloads_saveAs.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_cookies_containers.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="chrome_head.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(function* setup() {
+  // make sure userContext is enabled.
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["privacy.userContext.enabled", true],
+  ]});
+});
+
+add_task(function* test_cookie_containers() {
+  function background() {
+    function assertExpected(expected, cookie) {
+      for (let key of Object.keys(cookie)) {
+        browser.test.assertTrue(key in expected, `found property ${key}`);
+        browser.test.assertEq(expected[key], cookie[key], `property value for ${key} is correct`);
+      }
+      browser.test.assertEq(Object.keys(expected).length, Object.keys(cookie).length, "all expected properties found");
+    }
+
+    const TEST_URL = "http://example.org/";
+    const THE_FUTURE = Date.now() + 5 * 60;
+
+    let expected = {
+      name: "name1",
+      value: "value1",
+      domain: "example.org",
+      hostOnly: true,
+      path: "/",
+      secure: false,
+      httpOnly: false,
+      session: false,
+      expirationDate: THE_FUTURE,
+      storeId: "firefox-container-1",
+    };
+
+    browser.cookies.set({url: TEST_URL, name: "name1", value: "value1",
+                         expirationDate: THE_FUTURE, storeId: "firefox-container-1"})
+    .then(cookie => {
+      browser.test.assertEq("firefox-container-1", cookie.storeId, "the cookie has the correct storeId");
+      return browser.cookies.get({url: TEST_URL, name: "name1"});
+    })
+    .then(cookie => {
+      browser.test.assertEq(null, cookie, "get() without storeId returns null");
+      return browser.cookies.get({url: TEST_URL, name: "name1", storeId: "firefox-container-1"});
+    })
+    .then(cookie => {
+      assertExpected(expected, cookie);
+      return browser.cookies.getAll({storeId: "firefox-default"});
+    })
+    .then(cookies => {
+      browser.test.assertEq(0, cookies.length, "getAll() with default storeId returns an empty array");
+      return browser.cookies.getAll({storeId: "firefox-container-1"});
+    })
+    .then(cookies => {
+      browser.test.assertEq(1, cookies.length, "one cookie found for matching domain");
+      assertExpected(expected, cookies[0]);
+      return browser.cookies.remove({url: TEST_URL, name: "name1", storeId: "firefox-container-1"});
+    })
+    .then(details => {
+      assertExpected({url: TEST_URL, name: "name1", storeId: "firefox-container-1"}, details);
+      return browser.cookies.get({url: TEST_URL, name: "name1", storeId: "firefox-container-1"});
+    })
+    .then(cookie => {
+      browser.test.assertEq(null, cookie, "removed cookie not found");
+    })
+    .then(() => {
+      browser.test.notifyPass("cookies");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["cookies", "*://example.org/"],
+    },
+  });
+
+  yield extension.startup();
+  yield extension.awaitFinish("cookies");
+  yield extension.unload();
+});
+
+</script>
+
+</body>
+</html>