Bug 1321303 - Part 7: Implement browsingData.removePasswords, r=aswan,Dolske
authorBob Silverberg <bsilverberg@mozilla.com>
Tue, 20 Dec 2016 16:32:47 -0500
changeset 375462 8a161e6bae182cd3c39a0b25ebdd18103b3d39db
parent 375461 95426f1151bcfe155c32bb7b4af580012d04fe3b
child 375463 4054f1c5e04a012e63ffa95533939a73b911f8ef
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, Dolske
bugs1321303
milestone53.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 1321303 - Part 7: Implement browsingData.removePasswords, r=aswan,Dolske 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
@@ -5,23 +5,21 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
                                   "resource:///modules/Sanitizer.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+                                  "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
                                   "resource://gre/modules/Timer.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "cookieMgr",
-                                   "@mozilla.org/cookiemanager;1",
-                                   "nsICookieManager");
-
 /**
 * A number of iterations after which to yield time back
 * to the system.
 */
 const YIELD_PERIOD = 10;
 
 const PREF_DOMAIN = "privacy.cpd.";
 
@@ -38,26 +36,27 @@ function makeRange(options) {
 }
 
 function clearCache() {
   // Clearing the cache does not support timestamps.
   return sanitizer.items.cache.clear();
 }
 
 let clearCookies = Task.async(function* (options) {
+  let cookieMgr = Services.cookies;
   // This code has been borrowed from sanitize.js.
   let yieldCounter = 0;
 
   if (options.since) {
     // 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 > PlacesUtils.toPRTime(options.since)) {
+      if (cookie.creationTime >= PlacesUtils.toPRTime(options.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) {
           yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
         }
       }
@@ -75,16 +74,38 @@ 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 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) {
+          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(
@@ -106,16 +127,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);
       }
     }
   }
@@ -169,14 +193,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 checkLoginExists(host, shouldExist) {
+  let count = {value: 0};
+  loginManager.findLogins(count, host, "", null);
+  equal(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) => {
+      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
@@ -2,13 +2,14 @@
 head = head.js
 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_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]