Bug 1362998 - Implement browsingData.removeCookies WebExtension API method on android. r?bsilverberg draft
authorTushar Saini (:shatur) <tushar.saini1285@gmail.com>
Sun, 23 Jul 2017 17:14:03 +0530
changeset 613974 46c9e562e85a5ab6c6a7e8c2aabe974bc642029b
parent 613973 23d9d005d92afdb5afabeae2ad672323c7d903ad
child 638741 7b364ef698cb01275e216fabb02149d39c93f32c
push id69876
push userbmo:tushar.saini1285@gmail.com
push dateSun, 23 Jul 2017 12:00:27 +0000
reviewersbsilverberg
bugs1362998
milestone56.0a1
Bug 1362998 - Implement browsingData.removeCookies WebExtension API method on android. r?bsilverberg MozReview-Commit-ID: 9h5YegFelhe
mobile/android/components/extensions/ext-browsingData.js
mobile/android/components/extensions/schemas/browsing_data.json
mobile/android/components/extensions/test/mochitest/chrome.ini
mobile/android/components/extensions/test/mochitest/test_ext_browsingData_cookies.html
--- a/mobile/android/components/extensions/ext-browsingData.js
+++ b/mobile/android/components/extensions/ext-browsingData.js
@@ -4,16 +4,51 @@
 
 Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
                                   "resource://gre/modules/SharedPreferences.jsm");
 
+let clearCookies = async function(options) {
+  if (options.originTypes &&
+      (options.originTypes.protectedWeb || options.originTypes.extension)) {
+    return Promise.reject(
+      {message: "Firefox does not support protectedWeb or extension as originTypes."});
+  }
+
+  let cookieMgr = Services.cookies;
+  let yieldCounter = 0;
+  const YIELD_PERIOD = 10;
+
+  if (options.since) {
+    // Convert it to microseconds
+    let since =  options.since*1000;
+    // Iterate through the cookies and delete any created after our cutoff.
+    let cookiesEnum = cookieMgr.enumerator;
+    while (cookiesEnum.hasMoreElements()) {
+      let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+      if (cookie.creationTime >= since) {
+        // This cookie was created after our cutoff, clear it.
+        cookieMgr.remove(cookie.host, cookie.name, cookie.path,
+                         false, cookie.originAttributes);
+
+        if (++yieldCounter % YIELD_PERIOD == 0) {
+          await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
+        }
+      }
+    }
+  } else {
+    // Remove everything.
+    cookieMgr.removeAll();
+  }
+};
+
 this.browsingData = class extends ExtensionAPI {
   getAPI(context) {
     return {
       browsingData: {
         settings() {
           const PREF_DOMAIN = "android.not_a_preference.privacy.clear";
           const PREF_KEY_PREFIX = "private.data.";
           // The following prefs are the only ones in Firefox that match corresponding
@@ -46,12 +81,15 @@ this.browsingData = class extends Extens
             // Firefox doesn't have the same concept of dataRemovalPermitted
             // as Chrome, so it will always be true.
             dataRemovalPermitted[name] = true;
           }
           // We do not provide option to delete history by time
           // so, since value is given 0, which means Everything
           return Promise.resolve({options: {since: 0}, dataToRemove, dataRemovalPermitted});
         },
+        removeCookies(options) {
+          return clearCookies(options);
+        },
       },
     };
   }
 };
\ No newline at end of file
--- a/mobile/android/components/extensions/schemas/browsing_data.json
+++ b/mobile/android/components/extensions/schemas/browsing_data.json
@@ -222,17 +222,16 @@
           }
         ]
       },
       {
         "name": "removeCookies",
         "description": "Clears the browser's cookies and server-bound certificates modified within a particular timeframe.",
         "type": "function",
         "async": "callback",
-        "unsupported": true,
         "parameters": [
           {
             "$ref": "RemovalOptions",
             "name": "options"
           },
           {
             "name": "callback",
             "type": "function",
--- a/mobile/android/components/extensions/test/mochitest/chrome.ini
+++ b/mobile/android/components/extensions/test/mochitest/chrome.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   head.js
   ../../../../../../toolkit/components/extensions/test/mochitest/chrome_cleanup_script.js
 tags = webextensions
 
 [test_ext_browserAction_getTitle_setTitle.html]
 [test_ext_browserAction_onClicked.html]
+[test_ext_browsingData_cookies.html]
 [test_ext_browsingData_settings.html]
 [test_ext_pageAction_show_hide.html]
 [test_ext_pageAction_getPopup_setPopup.html]
 skip-if = os == 'android' # bug 1373170
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_cookies.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>BrowsingData Cookies test</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const COOKIE = {
+  host: "example.com",
+  name: "test_cookie",
+  path: "/",
+};
+let since, oldCookie;
+
+function addCookie(cookie) {
+  let expiry =  Date.now() / 1000 + 10000;
+  Services.cookies.add(cookie.host, cookie.path, cookie.name, "test", false, false, false, expiry);
+  ok(Services.cookies.cookieExists(cookie), `Cookie ${cookie.name} was created.`);
+}
+
+async function setUpCookies() {
+  // Add a cookie which will end up with an older creationTime.
+  oldCookie = Object.assign({}, COOKIE, {name: Date.now()});
+  addCookie(oldCookie);
+  await new Promise(resolve => setTimeout(resolve, 20));
+  since = Date.now();
+  await new Promise(resolve => setTimeout(resolve, 10));
+
+  // Add a cookie which will end up with a more recent creationTime.
+  addCookie(COOKIE);
+}
+
+add_task(async function testCookies() {
+  function background() {
+    browser.test.onMessage.addListener(async (msg, options) => {
+      await browser.browsingData.removeCookies(options);
+      browser.test.sendMessage("cookiesRemoved");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["browsingData"],
+    },
+  });
+
+  async function testRemovalMethod(method) {
+    // Clear cookies with a recent since value.
+    await setUpCookies();
+    extension.sendMessage(method, {since});
+    await extension.awaitMessage("cookiesRemoved");
+
+    ok(Services.cookies.cookieExists(oldCookie), "Old cookie was not removed.");
+    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+
+    // Clear cookies with an old since value.
+    await setUpCookies();
+    addCookie(COOKIE);
+    extension.sendMessage(method, {since: since - 100000});
+    await extension.awaitMessage("cookiesRemoved");
+
+    ok(!Services.cookies.cookieExists(oldCookie), "Old cookie was removed.");
+    ok(!Services.cookies.cookieExists(COOKIE), "Recent cookie was removed.");
+
+    // Clear cookies with no since value and valid originTypes.
+    await setUpCookies();
+    extension.sendMessage(
+      method,
+      {originTypes: {unprotectedWeb: true, protectedWeb: false}});
+    await extension.awaitMessage("cookiesRemoved");
+
+    ok(!Services.cookies.cookieExists(COOKIE), `Cookie ${COOKIE.name}  was removed.`);
+    ok(!Services.cookies.cookieExists(oldCookie), `Cookie ${oldCookie.name}  was removed.`);
+  }
+
+  await extension.startup();
+
+  await testRemovalMethod("removeCookies");
+
+  await extension.unload();
+});
+</script>
+
+</body>
+</html>
\ No newline at end of file