Bug 1321303 - Part 7: Implement browsingData.removePasswords, r?aswan draft
authorBob Silverberg <bsilverberg@mozilla.com>
Tue, 20 Dec 2016 16:32:47 -0500
changeset 455479 354b3983d7f068ecb8821bce4d6d813749830ba5
parent 455478 21c3a57b8b7230e1eb58e866156f9316db4ef3b1
child 455480 6f709666858258c4f2f21c969f63533fe5b6f29f
push id40255
push userbmo:bob.silverberg@gmail.com
push dateTue, 03 Jan 2017 22:37:43 +0000
reviewersaswan
bugs1321303
milestone53.0a1
Bug 1321303 - Part 7: Implement browsingData.removePasswords, r?aswan MozReview-Commit-ID: E23EYrBs3Ze
browser/components/extensions/ext-browsingData.js
browser/components/extensions/schemas/browsing_data.json
browser/components/extensions/test/xpcshell/test_ext_browsingData_passwords.js
browser/components/extensions/test/xpcshell/xpcshell.ini
--- a/browser/components/extensions/ext-browsingData.js
+++ b/browser/components/extensions/ext-browsingData.js
@@ -13,16 +13,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cookieMgr",
                                    "@mozilla.org/cookiemanager;1",
                                    "nsICookieManager");
+XPCOMUtils.defineLazyServiceGetter(this, "loginManager",
+                                   "@mozilla.org/login-manager;1",
+                                   "nsILoginManager");
 
 /**
 * A number of iterations after which to yield time back
 * to the system.
 */
 const YIELD_PERIOD = 10;
 
 const PREF_DOMAIN = "privacy.cpd.";
@@ -77,16 +80,37 @@ function clearDownloads(options) {
 function clearFormData(options) {
   return sanitizer.items.formdata.clear(makeRange(options));
 }
 
 function clearHistory(options) {
   return sanitizer.items.history.clear(makeRange(options));
 }
 
+let clearPasswords = Task.async(function* (options) {
+  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) {
+          yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
+        }
+      }
+    }
+  } else {
+    // Remove everything.
+    loginManager.removeAllLogins();
+  }
+});
+
 function clearPluginData(options) {
   return sanitizer.items.pluginData.clear(makeRange(options));
 }
 
 function doRemoval(options, dataToRemove, extension) {
   if (options.originTypes &&
       (options.originTypes.protectedWeb || options.originTypes.extension)) {
     return Promise.reject(
@@ -108,16 +132,19 @@ function doRemoval(options, dataToRemove
           removalPromises.push(clearDownloads(options));
           break;
         case "formData":
           removalPromises.push(clearFormData(options));
           break;
         case "history":
           removalPromises.push(clearHistory(options));
           break;
+        case "passwords":
+          removalPromises.push(clearPasswords(options));
+          break;
         case "pluginData":
           removalPromises.push(clearPluginData(options));
           break;
         default:
           invalidDataTypes.push(dataType);
       }
     }
   }
@@ -171,14 +198,17 @@ extensions.registerSchemaAPI("browsingDa
         return doRemoval(options, {downloads: true});
       },
       removeFormData(options) {
         return doRemoval(options, {formData: true});
       },
       removeHistory(options) {
         return doRemoval(options, {history: true});
       },
+      removePasswords(options) {
+        return doRemoval(options, {passwords: true});
+      },
       removePluginData(options) {
         return doRemoval(options, {pluginData: true});
       },
     },
   };
 });
--- a/browser/components/extensions/schemas/browsing_data.json
+++ b/browser/components/extensions/schemas/browsing_data.json
@@ -369,17 +369,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",
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/xpcshell/test_ext_browsingData_passwords.js
@@ -0,0 +1,92 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyServiceGetter(this, "loginManager",
+                                   "@mozilla.org/login-manager;1",
+                                   "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 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);
+}
+
+function checkLoginExists(host, shouldExist) {
+  let count = { value: 0 };
+  loginManager.findLogins(count, host, "", null);
+  equal(count.value, shouldExist ? 1 : 0, `Login was ${shouldExist ? "" : "not "} found.`);
+}
+
+async function setupPasswords() {
+  loginManager.removeAllLogins();
+  addLogin(NEW_HOST, REFERENCE_DATE);
+  addLogin(OLD_HOST, REFERENCE_DATE - 10000);
+}
+
+add_task(async function testHistory() {
+  function background() {
+    browser.test.onMessage.addListener(async (msg, options) => {
+      if (msg == "removeHistory") {
+        await browser.browsingData.removePasswords(options);
+      } else {
+        await browser.browsingData.remove(options, {passwords: true});
+      }
+      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 testRemovalMethod("remove");
+
+  await extension.unload();
+});
--- a/browser/components/extensions/test/xpcshell/xpcshell.ini
+++ b/browser/components/extensions/test/xpcshell/xpcshell.ini
@@ -5,13 +5,14 @@ firefox-appdir = browser
 tags = webextensions
 
 [test_ext_bookmarks.js]
 [test_ext_browsingData.js]
 [test_ext_browsingData_cookies_cache.js]
 [test_ext_browsingData_downloads.js]
 [test_ext_browsingData_formdata.js]
 [test_ext_browsingData_history.js]
+[test_ext_browsingData_passwords.js]
 [test_ext_browsingData_settings.js]
 [test_ext_history.js]
 [test_ext_manifest_commands.js]
 [test_ext_manifest_omnibox.js]
 [test_ext_manifest_permissions.js]