Bug 1363012 - Implement browsingData.removePasswords WebExtension API method on android. r?grisha, mattw draft
authorTushar Saini (:shatur) <tushar.saini1285@gmail.com>
Tue, 20 Jun 2017 04:50:34 +0530
changeset 597811 012733623f09e2c28d65695e3766f8c1b8ec52e2
parent 594993 ced0f88fb478d50f122dd90aef879fc314bd20a7
child 634321 570b3b7b63733509e833de777b4d0d38764bcae2
push id65033
push userbmo:tushar.saini1285@gmail.com
push dateWed, 21 Jun 2017 00:29:33 +0000
reviewersgrisha, mattw
bugs1363012
milestone56.0a1
Bug 1363012 - Implement browsingData.removePasswords WebExtension API method on android. r?grisha, mattw MozReview-Commit-ID: 2tHGDfos8cl
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_passwords.html
--- a/mobile/android/components/extensions/ext-browsingData.js
+++ b/mobile/android/components/extensions/ext-browsingData.js
@@ -4,16 +4,41 @@
 
 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");
 
+const YIELD_PERIOD = 10;
+
+let clearPasswords = async function(options) {
+  let loginManager = Services.logins;
+  let yieldCounter = 0;
+
+  if (options.since) {
+    // Iterate through the logins and delete any updated after our cutoff.
+    let logins = loginManager.getAllLogins();
+    for (let login of logins) {
+      login.QueryInterface(Ci.nsILoginMetaInfo);
+      if (login.timePasswordChanged >= options.since) {
+        loginManager.removeLogin(login);
+        if (++yieldCounter % YIELD_PERIOD == 0) {
+          await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
+        }
+      }
+    }
+  } else {
+    // Remove everything.
+    loginManager.removeAllLogins();
+  }
+};
+
+
 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
@@ -49,12 +74,15 @@ this.browsingData = class extends Extens
             dataToRemove[name] = dataTrue.indexOf(`${PREF_KEY_PREFIX}${item}`) > -1;
             // Firefox doesn't have the same concept of dataRemovalPermitted
             // as Chrome, so it will always be true.
             dataRemovalPermitted[name] = true;
           }
 
           return Promise.resolve({options, dataToRemove, dataRemovalPermitted});
         },
+        removePasswords(options) {
+          return clearPasswords(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
@@ -376,17 +376,16 @@
           }
         ]
       },
       {
         "name": "removePasswords",
         "description": "Clears the browser's stored passwords.",
         "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,10 +1,11 @@
 [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_passwords.html]
 [test_ext_pageAction_show_hide.html]
 [test_ext_pageAction_getPopup_setPopup.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_browsingData_passwords.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>BrowsingData Settings 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;
+
+var loginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager);
+
+const REFERENCE_DATE = Date.now();
+const LOGIN_USERNAME = "username";
+const LOGIN_PASSWORD = "password";
+const LOGIN_USERNAME_FIELD = "username_field";
+const LOGIN_PASSWORD_FIELD = "password_field";
+const OLD_HOST = "http://mozilla.org";
+const NEW_HOST = "http://mozilla.com";
+
+function checkLoginExists(host, shouldExist) {
+  let count = {value: 0};
+  loginManager.findLogins(count, host, "", null);
+  is(count.value, shouldExist ? 1 : 0, `Login was ${shouldExist ? "" : "not "} found.`);
+}
+
+function addLogin(host, timestamp) {
+  checkLoginExists(host, false);
+  let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+              .createInstance(Ci.nsILoginInfo);
+  login.init(host, "", null, LOGIN_USERNAME, LOGIN_PASSWORD,
+             LOGIN_USERNAME_FIELD, LOGIN_PASSWORD_FIELD);
+  login.QueryInterface(Ci.nsILoginMetaInfo);
+  login.timePasswordChanged = timestamp;
+  loginManager.addLogin(login);
+  checkLoginExists(host, true);
+}
+
+async function setupPasswords() {
+  loginManager.removeAllLogins();
+  addLogin(NEW_HOST, REFERENCE_DATE);
+  addLogin(OLD_HOST, REFERENCE_DATE - 10000);
+}
+
+add_task(async function testPasswords() {
+  function background() {
+    browser.test.onMessage.addListener(async (msg, options) => {
+      await browser.browsingData.removePasswords(options);
+      browser.test.sendMessage("passwordsRemoved");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    background,
+    manifest: {
+      permissions: ["browsingData"],
+    },
+  });
+
+  async function testRemovalMethod(method) {
+    // Clear passwords with no since value.
+    await setupPasswords();
+    extension.sendMessage(method, {});
+    await extension.awaitMessage("passwordsRemoved");
+
+    checkLoginExists(OLD_HOST, false);
+    checkLoginExists(NEW_HOST, false);
+
+    // Clear passwords with recent since value.
+    await setupPasswords();
+    extension.sendMessage(method, {since: REFERENCE_DATE - 1000});
+    await extension.awaitMessage("passwordsRemoved");
+
+    checkLoginExists(OLD_HOST, true);
+    checkLoginExists(NEW_HOST, false);
+
+    // Clear passwords with old since value.
+    await setupPasswords();
+    extension.sendMessage(method, {since: REFERENCE_DATE - 20000});
+    await extension.awaitMessage("passwordsRemoved");
+
+    checkLoginExists(OLD_HOST, false);
+    checkLoginExists(NEW_HOST, false);
+  }
+
+  await extension.startup();
+
+  await testRemovalMethod("removePasswords");
+
+  await extension.unload();
+});
+
+</script>
+</body>
+</html>
\ No newline at end of file