Bug 1362998 - Implement browsingData.removeCookies WebExtension API method on android. r=bsilverberg
authorTushar Saini (:shatur) <tushar.saini1285@gmail.com>
Sun, 23 Jul 2017 17:14:03 +0530
changeset 419321 33154264e3d9e232ef0bab0cd271ba63f296775c
parent 419276 40b0b14356e190be8fac53b56369426d7b7f55f6
child 419322 23cefa3181cec993fb435c2ce2b71463034bf27e
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsilverberg
bugs1362998
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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