Backed out 2 changesets (bug 1569847) for browser-chrome failures at browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
authorCoroiu Cristina <ccoroiu@mozilla.com>
Mon, 26 Aug 2019 16:21:14 +0300
changeset 489841 fa5a2f0bcdc6b747bc8ff88db9481439198b42a5
parent 489840 65d4c80d42c0b07bc3364fd67d1a7efc45b5f1ec
child 489842 c46043c8d06fe5eab0758e3aa027e81a2c2778e7
push id113972
push userccoroiu@mozilla.com
push dateMon, 26 Aug 2019 13:22:31 +0000
treeherdermozilla-inbound@fa5a2f0bcdc6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1569847
milestone70.0a1
backs out3d3776e4a61a65f4a7e070bb12036caf7c21e367
1b5f7672e2d2be7a2e6b4b17dbc681f79c43b558
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
Backed out 2 changesets (bug 1569847) for browser-chrome failures at browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js Backed out changeset 3d3776e4a61a (bug 1569847) Backed out changeset 1b5f7672e2d2 (bug 1569847)
browser/components/BrowserGlue.jsm
browser/components/about/AboutProtectionsHandler.jsm
browser/components/aboutlogins/AboutLoginsParent.jsm
browser/components/aboutlogins/LoginBreaches.jsm
browser/components/aboutlogins/content/components/login-item.css
browser/components/aboutlogins/content/components/login-list.css
browser/components/aboutlogins/moz.build
browser/components/aboutlogins/tests/browser/browser.ini
browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
browser/components/aboutlogins/tests/unit/test_getBreachesForLogins.js
browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
browser/components/aboutlogins/tests/unit/xpcshell.ini
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/LoginStore.jsm
toolkit/components/passwordmgr/storage-json.js
toolkit/components/passwordmgr/test/unit/test_vulnerable_passwords.js
toolkit/components/passwordmgr/test/unit/xpcshell.ini
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -514,17 +514,16 @@ XPCOMUtils.defineLazyModuleGetters(this,
   Corroborate: "resource://gre/modules/Corroborate.jsm",
   Discovery: "resource:///modules/Discovery.jsm",
   ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
   FirefoxMonitor: "resource:///modules/FirefoxMonitor.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   HomePage: "resource:///modules/HomePage.jsm",
   HybridContentTelemetry: "resource://gre/modules/HybridContentTelemetry.jsm",
   Integration: "resource://gre/modules/Integration.jsm",
-  LoginBreaches: "resource:///modules/LoginBreaches.jsm",
   LiveBookmarkMigrator: "resource:///modules/LiveBookmarkMigrator.jsm",
   NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
   Normandy: "resource://normandy/Normandy.jsm",
   ObjectUtils: "resource://gre/modules/ObjectUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   PageActions: "resource:///modules/PageActions.jsm",
   PageThumbs: "resource://gre/modules/PageThumbs.jsm",
   PdfJs: "resource://pdf.js/PdfJs.jsm",
@@ -986,18 +985,16 @@ BrowserGlue.prototype = {
         } else if (data == "migrateMatchBucketsPrefForUI66") {
           this._migrateMatchBucketsPrefForUI66().then(() => {
             Services.obs.notifyObservers(
               null,
               "browser-glue-test",
               "migrateMatchBucketsPrefForUI66-done"
             );
           });
-        } else if (data == "add-breaches-sync-handler") {
-          this._addBreachesSyncHandler();
         }
         break;
       case "initial-migration-will-import-default-bookmarks":
         this._migrationImportsDefaultBookmarks = true;
         break;
       case "initial-migration-did-import-default-bookmarks":
         this._initPlaces(true);
         break;
@@ -2201,40 +2198,23 @@ BrowserGlue.prototype = {
       this._gmpInstallManager = new obj.GMPInstallManager();
       // We don't really care about the results, if someone is interested they
       // can check the log.
       this._gmpInstallManager.simpleCheckAndInstall().catch(() => {});
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       RemoteSettings.init();
-      this._addBreachesSyncHandler();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       RemoteSecuritySettings.init();
     });
   },
 
-  _addBreachesSyncHandler() {
-    if (
-      Services.prefs.getBoolPref(
-        "signon.management.page.breach-alerts.enabled",
-        false
-      )
-    ) {
-      RemoteSettings(LoginBreaches.REMOTE_SETTINGS_COLLECTION).on(
-        "sync",
-        async event => {
-          await LoginBreaches.update(event.data.current);
-        }
-      );
-    }
-  },
-
   _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
     // If user has already dismissed quit request, then do nothing
     if (aCancelQuit instanceof Ci.nsISupportsPRBool && aCancelQuit.data) {
       return;
     }
 
     // There are several cases where we won't show a dialog here:
     // 1. There is only 1 tab open in 1 window
--- a/browser/components/about/AboutProtectionsHandler.jsm
+++ b/browser/components/about/AboutProtectionsHandler.jsm
@@ -10,22 +10,30 @@ var EXPORTED_SYMBOLS = ["AboutProtection
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 const { RemotePages } = ChromeUtils.import(
   "resource://gre/modules/remotepagemanager/RemotePageManagerParent.jsm"
 );
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+ChromeUtils.defineModuleGetter(
+  this,
+  "fxAccounts",
+  "resource://gre/modules/FxAccounts.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
+);
+
 XPCOMUtils.defineLazyModuleGetters(this, {
-  fxAccounts: "resource://gre/modules/FxAccounts.jsm",
   FXA_PWDMGR_HOST: "resource://gre/modules/FxAccountsCommon.js",
   FXA_PWDMGR_REALM: "resource://gre/modules/FxAccountsCommon.js",
-  LoginBreaches: "resource:///modules/LoginBreaches.jsm",
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "TrackingDBService",
   "@mozilla.org/tracking-db-service;1",
   "nsITrackingDBService"
 );
@@ -200,17 +208,17 @@ var AboutProtectionsHandler = {
     try {
       if ((await fxAccounts.accountStatus()) && token) {
         monitorData = await this.fetchUserBreachStats(token);
 
         // Get the stats for number of potentially breached Lockwise passwords if no master
         // password is set.
         if (!LoginHelper.isMasterPasswordSet()) {
           const logins = await LoginHelper.getAllUserFacingLogins();
-          potentiallyBreachedLogins = await LoginBreaches.getPotentialBreachesByLoginGUID(
+          potentiallyBreachedLogins = await LoginHelper.getBreachesForLogins(
             logins
           );
         }
       } else {
         // If no account exists, then the user is not logged in with an fxAccount.
         monitorData = {
           errorMessage: "No account",
         };
--- a/browser/components/aboutlogins/AboutLoginsParent.jsm
+++ b/browser/components/aboutlogins/AboutLoginsParent.jsm
@@ -4,26 +4,47 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["AboutLoginsParent"];
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+ChromeUtils.defineModuleGetter(
+  this,
+  "E10SUtils",
+  "resource://gre/modules/E10SUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "MigrationUtils",
+  "resource:///modules/MigrationUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "Services",
+  "resource://gre/modules/Services.jsm"
+);
+ChromeUtils.defineModuleGetter(
+  this,
+  "UIState",
+  "resource://services-sync/UIState.jsm"
+);
 
-XPCOMUtils.defineLazyModuleGetters(this, {
-  E10SUtils: "resource://gre/modules/E10SUtils.jsm",
-  LoginBreaches: "resource:////modules/LoginBreaches.jsm",
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
-  MigrationUtils: "resource:///modules/MigrationUtils.jsm",
-  Services: "resource://gre/modules/Services.jsm",
-  UIState: "resource://services-sync/UIState.jsm",
-  PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
-});
+ChromeUtils.defineModuleGetter(
+  this,
+  "PlacesUtils",
+  "resource://gre/modules/PlacesUtils.jsm"
+);
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   return LoginHelper.createLogger("AboutLoginsParent");
 });
 XPCOMUtils.defineLazyPreferenceGetter(
   this,
   "BREACH_ALERTS_ENABLED",
   "signon.management.page.breach-alerts.enabled",
@@ -95,19 +116,19 @@ var AboutLoginsParent = {
       case "AboutLogins:DeleteLogin": {
         let login = LoginHelper.vanillaObjectToLogin(message.data.login);
         Services.logins.removeLogin(login);
         break;
       }
       case "AboutLogins:DismissBreachAlert": {
         const login = message.data.login;
 
-        await LoginBreaches.recordDismissal(login.guid);
+        await LoginHelper.recordBreachAlertDismissal(login.guid);
         const logins = await this.getAllLogins();
-        const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+        const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
           logins
         );
         const messageManager = message.target.messageManager;
         messageManager.sendAsyncMessage(
           "AboutLogins:UpdateBreaches",
           breachesByLoginGUID
         );
         break;
@@ -402,17 +423,17 @@ var AboutLoginsParent = {
           };
 
           messageManager.sendAsyncMessage(
             "AboutLogins:LocalizeBadges",
             selectedBadgeLanguages
           );
 
           if (BREACH_ALERTS_ENABLED) {
-            const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+            const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
               logins
             );
             messageManager.sendAsyncMessage(
               "AboutLogins:UpdateBreaches",
               breachesByLoginGUID
             );
           }
 
deleted file mode 100644
--- a/browser/components/aboutlogins/LoginBreaches.jsm
+++ /dev/null
@@ -1,171 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * Manages breach alerts for saved logins using data from Firefox Monitor via
- * RemoteSettings.
- */
-
-"use strict";
-
-const EXPORTED_SYMBOLS = ["LoginBreaches"];
-
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { XPCOMUtils } = ChromeUtils.import(
-  "resource://gre/modules/XPCOMUtils.jsm"
-);
-
-XPCOMUtils.defineLazyModuleGetters(this, {
-  LoginHelper: "resource://gre/modules/LoginHelper.jsm",
-  RemoteSettings: "resource://services-settings/remote-settings.js",
-  RemoteSettingsClient: "resource://services-settings/RemoteSettingsClient.jsm",
-});
-
-this.LoginBreaches = {
-  REMOTE_SETTINGS_COLLECTION: "fxmonitor-breaches",
-
-  async recordDismissal(loginGuid) {
-    await Services.logins.initializationPromise;
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-
-    return storageJSON.recordBreachAlertDismissal(loginGuid);
-  },
-
-  async update(breaches = null) {
-    const logins = await LoginHelper.getAllUserFacingLogins();
-    await this.getBreachesForLogins(logins, breaches);
-  },
-
-  /**
-   * Return a Map of login GUIDs to a potential breach affecting that login
-   * by considering only breaches affecting passwords.
-   *
-   * This only uses the breach `Domain` and `timePasswordChanged` to determine
-   * if a login may be breached which means it may contain false-positives if
-   * login timestamps are incorrect, the user didn't save their password change
-   * in Firefox, or the breach didn't contain all accounts, etc. As a result,
-   * consumers should avoid making stronger claims than the data supports.
-   *
-   * @param {nsILoginInfo[]} logins Saved logins to check for potential breaches.
-   * @param {object[]} [breaches = null] Only ones involving passwords will be used.
-   * @returns {Map} with a key for each login GUID potentially in a breach.
-   */
-  async getPotentialBreachesByLoginGUID(logins, breaches = null) {
-    const breachesByLoginGUID = new Map();
-    if (!breaches) {
-      try {
-        breaches = await RemoteSettings(this.REMOTE_SETTINGS_COLLECTION).get();
-      } catch (ex) {
-        if (ex instanceof RemoteSettingsClient.UnknownCollectionError) {
-          log.warn(
-            "Could not get Remote Settings collection.",
-            this.REMOTE_SETTINGS_COLLECTION,
-            ex
-          );
-          return breachesByLoginGUID;
-        }
-        throw ex;
-      }
-    }
-    const BREACH_ALERT_URL = Services.prefs.getStringPref(
-      "signon.management.page.breachAlertUrl"
-    );
-    const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
-
-    await Services.logins.initializationPromise;
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-    const dismissedBreachAlertsByLoginGUID = storageJSON.getBreachAlertDismissalsByLoginGUID();
-
-    // Determine potentially breached logins by checking their origin and the last time
-    // they were changed. It's important to note here that we are NOT considering the
-    // username and password of that login.
-    for (const login of logins) {
-      const loginURI = Services.io.newURI(login.origin);
-      let loginHost;
-      try {
-        // nsIURI.host can throw if the URI scheme doesn't have a host.
-        loginHost = loginURI.host;
-      } catch (ex) {
-        continue;
-      }
-      for (const breach of breaches) {
-        if (
-          !breach.Domain ||
-          !Services.eTLD.hasRootDomain(loginHost, breach.Domain) ||
-          !this._breachInvolvedPasswords(breach) ||
-          !this._breachWasAfterPasswordLastChanged(breach, login)
-        ) {
-          continue;
-        }
-
-        if (!storageJSON.isPotentiallyVulnerablePassword(login)) {
-          storageJSON.addPotentiallyVulnerablePassword(login);
-        }
-
-        if (
-          this._breachAlertIsDismissed(
-            login,
-            breach,
-            dismissedBreachAlertsByLoginGUID
-          )
-        ) {
-          continue;
-        }
-
-        let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
-        breach.breachAlertURL = breachAlertURL.href;
-        breachesByLoginGUID.set(login.guid, breach);
-      }
-    }
-    return breachesByLoginGUID;
-  },
-
-  /**
-   * Return information about logins using passwords that were potentially in a
-   * breach.
-   * @see the caveats in the documentation for `getPotentialBreachesByLoginGUID`.
-   *
-   * @param {nsILoginInfo[]} logins to check the passwords of.
-   * @returns {Map} from login GUID to `true` for logins that have a password
-   *                that may be vulnerable.
-   */
-  getPotentiallyVulnerablePasswordsByLoginGUID(logins) {
-    const vulnerablePasswordsByLoginGUID = new Map();
-    const storageJSON =
-      Services.logins.wrappedJSObject._storage.wrappedJSObject;
-    for (const login of logins) {
-      if (storageJSON.isPotentiallyVulnerablePassword(login)) {
-        vulnerablePasswordsByLoginGUID.set(login.guid, true);
-      }
-    }
-    return vulnerablePasswordsByLoginGUID;
-  },
-
-  _breachAlertIsDismissed(login, breach, dismissedBreachAlerts) {
-    const breachAddedDate = new Date(breach.AddedDate).getTime();
-    const breachAlertIsDismissed =
-      dismissedBreachAlerts[login.guid] &&
-      dismissedBreachAlerts[login.guid].timeBreachAlertDismissed >
-        breachAddedDate;
-    return breachAlertIsDismissed;
-  },
-
-  _breachInvolvedPasswords(breach) {
-    return (
-      breach.hasOwnProperty("DataClasses") &&
-      breach.DataClasses.includes("Passwords")
-    );
-  },
-
-  _breachWasAfterPasswordLastChanged(breach, login) {
-    const breachDate = new Date(breach.BreachDate).getTime();
-    return login.timePasswordChanged < breachDate;
-  },
-};
-
-XPCOMUtils.defineLazyGetter(this, "log", () => {
-  return LoginHelper.createLogger("LoginBreaches");
-});
--- a/browser/components/aboutlogins/content/components/login-item.css
+++ b/browser/components/aboutlogins/content/components/login-item.css
@@ -49,17 +49,16 @@ input[type="url"][readOnly] {
 }
 
 .title {
   margin-top: 0;
   margin-bottom: 0;
   flex-grow: 1;
   overflow: hidden;
   text-overflow: ellipsis;
-  white-space: nowrap;
 }
 
 .delete-button,
 .edit-button {
   background-repeat: no-repeat;
   background-position: 8px;
   -moz-context-properties: fill;
   fill: currentColor;
--- a/browser/components/aboutlogins/content/components/login-list.css
+++ b/browser/components/aboutlogins/content/components/login-list.css
@@ -112,17 +112,16 @@ ol {
   overflow: hidden;
 }
 
 .title,
 .username {
   display: block;
   text-overflow: ellipsis;
   overflow: hidden;
-  white-space: nowrap;
 }
 
 .favicon-wrapper {
   height: 16px;
   width: 16px;
   background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   background-repeat: no-repeat;
   margin-inline-end: 12px;
--- a/browser/components/aboutlogins/moz.build
+++ b/browser/components/aboutlogins/moz.build
@@ -6,17 +6,16 @@
 
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Password Manager')
 
 EXTRA_JS_MODULES += [
     'AboutLoginsParent.jsm',
-    'LoginBreaches.jsm',
 ]
 
 FINAL_TARGET_FILES.actors += [
     'AboutLoginsChild.jsm',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
--- a/browser/components/aboutlogins/tests/browser/browser.ini
+++ b/browser/components/aboutlogins/tests/browser/browser.ini
@@ -4,17 +4,16 @@ prefs =
 support-files =
   head.js
 
 # Run first so content events from previous tests won't trickle in.
 # Skip ASAN and debug since waiting for content events is already slow.
 [browser_aaa_eventTelemetry_run_first.js]
 skip-if = asan || debug
 [browser_breachAlertDismissals.js]
-skip-if = debug || verify # bug 1576513
 [browser_confirmDeleteDialog.js]
 [browser_contextmenuFillLogins.js]
 [browser_copyToClipboardButton.js]
 [browser_createLogin.js]
 [browser_deleteLogin.js]
 [browser_dismissFooter.js]
 [browser_fxAccounts.js]
 [browser_loginListChanges.js]
--- a/browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
+++ b/browser/components/aboutlogins/tests/browser/browser_breachAlertDismissals.js
@@ -1,15 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let { LoginBreaches } = ChromeUtils.import(
-  "resource:///modules/LoginBreaches.jsm"
-);
-
 const TEST_BREACHES = [
   {
     AddedDate: "2019-12-20T23:56:26Z",
     BreachDate: "2018-12-16",
     Domain: "breached.com",
     Name: "Breached",
     PwnCount: 1643100,
     DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
@@ -30,17 +26,17 @@ add_task(async function setup() {
     BrowserTestUtils.removeTab(gBrowser.selectedTab);
     Services.logins.removeAllLogins();
   });
 });
 
 add_task(async function test_show_login() {
   let browser = gBrowser.selectedBrowser;
   TEST_LOGIN3.timePasswordChanged = 12345;
-  let testBreaches = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  let testBreaches = await LoginHelper.getBreachesForLogins(
     [TEST_LOGIN3],
     TEST_BREACHES
   );
   browser.messageManager.sendAsyncMessage(
     "AboutLogins:UpdateBreaches",
     testBreaches
   );
   await ContentTask.spawn(browser, TEST_LOGIN3, async () => {
rename from browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
rename to browser/components/aboutlogins/tests/unit/test_getBreachesForLogins.js
--- a/browser/components/aboutlogins/tests/unit/test_getPotentialBreachesByLoginGUID.js
+++ b/browser/components/aboutlogins/tests/unit/test_getBreachesForLogins.js
@@ -1,26 +1,23 @@
 /**
- * Test LoginBreaches.getPotentialBreachesByLoginGUID
+ * Test LoginHelper.getBreachesForLogins
  */
 
 "use strict";
 
-const { RemoteSettings } = ChromeUtils.import(
-  "resource://services-settings/remote-settings.js"
-);
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
-  Ci.nsIObserver
+const { AboutLoginsParent } = ChromeUtils.import(
+  "resource:///modules/AboutLoginsParent.jsm"
 );
 
 ChromeUtils.defineModuleGetter(
   this,
-  "LoginBreaches",
-  "resource:///modules/LoginBreaches.jsm"
+  "LoginHelper",
+  "resource://gre/modules/LoginHelper.jsm"
 );
 
 const TEST_BREACHES = [
   {
     AddedDate: "2018-12-20T23:56:26Z",
     BreachDate: "2018-12-16",
     Domain: "breached.com",
     Name: "Breached",
@@ -57,17 +54,17 @@ const TEST_BREACHES = [
   },
 ];
 
 const NOT_BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
   origin: "https://www.example.com",
   formActionOrigin: "https://www.example.com",
   username: "username",
   password: "password",
-  timePasswordChanged: new Date("2018-12-15").getTime(),
+  timePasswordChanged: Date.now(),
 });
 const BREACHED_LOGIN = LoginTestUtils.testData.formLogin({
   origin: "https://www.breached.com",
   formActionOrigin: "https://www.breached.com",
   username: "username",
   password: "password",
   timePasswordChanged: new Date("2018-12-15").getTime(),
 });
@@ -96,142 +93,135 @@ const LOGIN_FOR_BREACHED_SITE_WITHOUT_PA
 const LOGIN_WITH_NON_STANDARD_URI = LoginTestUtils.testData.formLogin({
   origin: "someApp://random/path/to/login",
   formActionOrigin: "someApp://random/path/to/login",
   username: "username",
   password: "password",
   timePasswordChanged: new Date("2018-12-15").getTime(),
 });
 
-add_task(async function test_notBreachedLogin() {
+add_task(async function test_getBreachesForLogins_notBreachedLogin() {
   Services.logins.addLogin(NOT_BREACHED_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     0,
     "Should be 0 breached logins."
   );
 });
 
-add_task(async function test_breachedLogin() {
+add_task(async function test_getBreachesForLogins_breachedLogin() {
   Services.logins.addLogin(BREACHED_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN, BREACHED_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login: " + BREACHED_LOGIN.origin
   );
 });
 
-add_task(async function test_notBreachedSubdomain() {
+add_task(async function test_getBreachesForLogins_notBreachedSubdomain() {
   Services.logins.addLogin(NOT_BREACHED_SUBDOMAIN_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_LOGIN, NOT_BREACHED_SUBDOMAIN_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     0,
     "Should be 0 breached logins."
   );
 });
 
-add_task(async function test_breachedSubdomain() {
+add_task(async function test_getBreachesForLogins_breachedSubdomain() {
   Services.logins.addLogin(BREACHED_SUBDOMAIN_LOGIN);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [NOT_BREACHED_SUBDOMAIN_LOGIN, BREACHED_SUBDOMAIN_LOGIN],
     TEST_BREACHES
   );
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login: " + BREACHED_SUBDOMAIN_LOGIN.origin
   );
 });
 
-add_task(async function test_breachedSiteWithoutPasswords() {
-  Services.logins.addLogin(LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS);
+add_task(
+  async function test_getBreachesForLogins_breachedSiteWithoutPasswords() {
+    Services.logins.addLogin(LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    0,
-    "Should be 0 breached login: " +
-      LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
-  );
-});
-
-add_task(async function test_breachAlertHiddenAfterDismissal() {
-  BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";
-
-  await Services.logins.initializationPromise;
-  const storageJSON = Services.logins.wrappedJSObject._storage.wrappedJSObject;
-
-  storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS],
+      TEST_BREACHES
+    );
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      0,
+      "Should be 0 breached login: " +
+        LOGIN_FOR_BREACHED_SITE_WITHOUT_PASSWORDS.origin
+    );
+  }
+);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    0,
-    "Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
-  );
+add_task(
+  async function test_getBreachesForLogins_breachAlertHiddenAfterDismissal() {
+    BREACHED_LOGIN.guid = "{d2de5ac1-4de6-e544-a7af-1f75abcba92b}";
 
-  info("Clear login storage");
-  Services.logins.removeAllLogins();
+    await Services.logins.initializationPromise;
+    const storageJSON =
+      Services.logins.wrappedJSObject._storage.wrappedJSObject;
+
+    storageJSON.recordBreachAlertDismissal(BREACHED_LOGIN.guid);
 
-  const breachesByLoginGUID2 = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
-    TEST_BREACHES
-  );
-  Assert.strictEqual(
-    breachesByLoginGUID2.size,
-    1,
-    "Breached login should re-appear after clearing storage: " +
-      BREACHED_LOGIN.origin
-  );
-});
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
+      TEST_BREACHES
+    );
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      0,
+      "Should be 0 breached logins after dismissal: " + BREACHED_LOGIN.origin
+    );
+  }
+);
 
-add_task(async function test_newBreachAfterDismissal() {
+add_task(async function test_getBreachesForLogins_newBreachAfterDismissal() {
   TEST_BREACHES[0].AddedDate = new Date().toISOString();
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
+  const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
     [BREACHED_LOGIN, NOT_BREACHED_LOGIN],
     TEST_BREACHES
   );
 
   Assert.strictEqual(
     breachesByLoginGUID.size,
     1,
     "Should be 1 breached login after new breach following the dismissal of a previous breach: " +
       BREACHED_LOGIN.origin
   );
 });
 
-add_task(async function test_ExceptionsThrownByNonStandardURIsAreCaught() {
-  Services.logins.addLogin(LOGIN_WITH_NON_STANDARD_URI);
+add_task(
+  async function test_getBreachesForLogins_ExceptionsThrownByNonStandardURIsAreCaught() {
+    Services.logins.addLogin(LOGIN_WITH_NON_STANDARD_URI);
 
-  const breachesByLoginGUID = await LoginBreaches.getPotentialBreachesByLoginGUID(
-    [LOGIN_WITH_NON_STANDARD_URI, BREACHED_LOGIN],
-    TEST_BREACHES
-  );
+    const breachesByLoginGUID = await LoginHelper.getBreachesForLogins(
+      [LOGIN_WITH_NON_STANDARD_URI, BREACHED_LOGIN],
+      TEST_BREACHES
+    );
 
-  Assert.strictEqual(
-    breachesByLoginGUID.size,
-    1,
-    "Exceptions thrown by logins with non-standard URIs should be caught."
-  );
-});
+    Assert.strictEqual(
+      breachesByLoginGUID.size,
+      1,
+      "Exceptions thrown by logins with non-standard URIs should be caught."
+    );
+  }
+);
--- a/browser/components/aboutlogins/tests/unit/xpcshell.ini
+++ b/browser/components/aboutlogins/tests/unit/xpcshell.ini
@@ -1,5 +1,5 @@
 [DEFAULT]
 head = head.js
 firefox-appdir = browser
 
-[test_getPotentialBreachesByLoginGUID.js]
+[test_getBreachesForLogins.js]
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -8,24 +8,37 @@
  * This JavaScript module exists in order to share code between the different
  * XPCOM components that constitute the Login Manager, including implementations
  * of nsILoginManager and nsILoginManagerStorage.
  */
 
 "use strict";
 
 const EXPORTED_SYMBOLS = ["LoginHelper"];
+const REMOTE_SETTINGS_BREACHES_COLLECTION = "fxmonitor-breaches";
 
 // Globals
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
+ChromeUtils.defineModuleGetter(
+  this,
+  "RemoteSettings",
+  "resource://services-settings/remote-settings.js"
+);
+
+ChromeUtils.defineModuleGetter(
+  this,
+  "RemoteSettingsClient",
+  "resource://services-settings/RemoteSettingsClient.jsm"
+);
+
 /**
  * Contains functions shared by different Login Manager components.
  */
 this.LoginHelper = {
   debug: null,
   enabled: null,
   formlessCaptureEnabled: null,
   generationAvailable: null,
@@ -1094,21 +1107,95 @@ this.LoginHelper = {
     } catch (e) {
       if (e.result == Cr.NS_ERROR_ABORT) {
         // If the user cancels the MP prompt then return no logins.
         return [];
       }
       throw e;
     }
   },
+
+  async recordBreachAlertDismissal(loginGuid) {
+    await Services.logins.initializationPromise;
+    const storageJSON =
+      Services.logins.wrappedJSObject._storage.wrappedJSObject;
+
+    return storageJSON.recordBreachAlertDismissal(loginGuid);
+  },
+
+  async getBreachesForLogins(logins, breaches = null) {
+    const breachesByLoginGUID = new Map();
+    if (!breaches) {
+      try {
+        breaches = await RemoteSettings(
+          REMOTE_SETTINGS_BREACHES_COLLECTION
+        ).get();
+      } catch (ex) {
+        if (ex instanceof RemoteSettingsClient.UnknownCollectionError) {
+          log.warn(
+            "Could not get Remote Settings collection.",
+            REMOTE_SETTINGS_BREACHES_COLLECTION,
+            ex
+          );
+          return breachesByLoginGUID;
+        }
+        throw ex;
+      }
+    }
+    const BREACH_ALERT_URL = Services.prefs.getStringPref(
+      "signon.management.page.breachAlertUrl"
+    );
+    const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
+
+    await Services.logins.initializationPromise;
+    const storageJSON =
+      Services.logins.wrappedJSObject._storage.wrappedJSObject;
+    const dismissedBreachAlertsByLoginGUID = storageJSON.getBreachAlertDismissalsByLoginGUID();
+
+    // Determine potentially breached logins by checking their origin and the last time
+    // they were changed. It's important to note here that we are NOT considering the
+    // username and password of that login.
+    for (const login of logins) {
+      const loginURI = Services.io.newURI(login.origin);
+      let loginHost;
+      try {
+        // nsIURI.host can throw if the URI scheme doesn't have a host.
+        loginHost = loginURI.host;
+      } catch (ex) {
+        continue;
+      }
+      for (const breach of breaches) {
+        if (!breach.Domain) {
+          continue;
+        }
+        const breachDate = new Date(breach.BreachDate).getTime();
+        const breachAddedDate = new Date(breach.AddedDate).getTime();
+        if (
+          Services.eTLD.hasRootDomain(loginHost, breach.Domain) &&
+          breach.hasOwnProperty("DataClasses") &&
+          breach.DataClasses.includes("Passwords") &&
+          login.timePasswordChanged < breachDate &&
+          (!dismissedBreachAlertsByLoginGUID[login.guid] ||
+            dismissedBreachAlertsByLoginGUID[login.guid]
+              .timeBreachAlertDismissed < breachAddedDate)
+        ) {
+          let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
+          breach.breachAlertURL = breachAlertURL.href;
+          breachesByLoginGUID.set(login.guid, breach);
+        }
+      }
+    }
+    return breachesByLoginGUID;
+  },
 };
 
 LoginHelper.init();
 
 XPCOMUtils.defineLazyPreferenceGetter(
   LoginHelper,
   "showInsecureFieldWarning",
   "security.insecure_field_warning.contextual.enabled"
 );
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
-  return LoginHelper.createLogger("LoginHelper");
+  let logger = LoginHelper.createLogger("LoginHelper");
+  return logger;
 });
--- a/toolkit/components/passwordmgr/LoginStore.jsm
+++ b/toolkit/components/passwordmgr/LoginStore.jsm
@@ -93,20 +93,16 @@ LoginStore.prototype._dataPostProcessor 
     data.nextId = 1;
   }
 
   // Create any arrays that are not present in the saved file.
   if (!data.logins) {
     data.logins = [];
   }
 
-  if (!data.potentiallyVulnerablePasswords) {
-    data.potentiallyVulnerablePasswords = [];
-  }
-
   if (!data.dismissedBreachAlertsByLoginGUID) {
     data.dismissedBreachAlertsByLoginGUID = {};
   }
 
   // Indicate that the current version of the code has touched the file.
   data.version = kDataVersion;
 
   return data;
--- a/toolkit/components/passwordmgr/storage-json.js
+++ b/toolkit/components/passwordmgr/storage-json.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/**
+/*
  * nsILoginManagerStorage implementation for the JSON back-end.
  */
 
 "use strict";
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
@@ -52,34 +52,16 @@ this.LoginManagerStorage_json.prototype 
     if (!this.__crypto) {
       this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].getService(
         Ci.nsILoginManagerCrypto
       );
     }
     return this.__crypto;
   },
 
-  __decryptedPotentiallyVulnerablePasswords: null,
-  get _decryptedPotentiallyVulnerablePasswords() {
-    if (!this.__decryptedPotentiallyVulnerablePasswords) {
-      this._store.ensureDataReady();
-      this.__decryptedPotentiallyVulnerablePasswords = [];
-      for (const potentiallyVulnerablePassword of this._store.data
-        .potentiallyVulnerablePasswords) {
-        const decryptedPotentiallyVulnerablePassword = this._crypto.decrypt(
-          potentiallyVulnerablePassword.encryptedPassword
-        );
-        this.__decryptedPotentiallyVulnerablePasswords.push(
-          decryptedPotentiallyVulnerablePassword
-        );
-      }
-    }
-    return this.__decryptedPotentiallyVulnerablePasswords;
-  },
-
   initialize() {
     try {
       // Force initialization of the crypto module.
       // See bug 717490 comment 17.
       this._crypto;
 
       // Set the reference to LoginStore synchronously.
       let jsonPath = OS.Path.join(OS.Constants.Path.profileDir, "logins.json");
@@ -566,19 +548,16 @@ this.LoginManagerStorage_json.prototype 
   /**
    * Removes all logins from storage.
    */
   removeAllLogins() {
     this._store.ensureDataReady();
 
     this.log("Removing all logins");
     this._store.data.logins = [];
-    this._store.data.potentiallyVulnerablePasswords = [];
-    this.__decryptedPotentiallyVulnerablePasswords = null;
-    this._store.data.dismissedBreachAlertsByLoginGUID = {};
     this._store.saveSoon();
 
     LoginHelper.notifyStorageChanged("removeAllLogins", null);
   },
 
   findLogins(origin, formActionOrigin, httpRealm) {
     let loginData = {
       origin,
@@ -613,36 +592,16 @@ this.LoginManagerStorage_json.prototype 
       }
     }
     let [logins, ids] = this._searchLogins(matchData);
 
     this.log("_countLogins: counted logins:", logins.length);
     return logins.length;
   },
 
-  addPotentiallyVulnerablePassword(login) {
-    this._store.ensureDataReady();
-    // this breached password is already stored
-    if (this.isPotentiallyVulnerablePassword(login)) {
-      return;
-    }
-    this.__decryptedPotentiallyVulnerablePasswords.push(login.password);
-
-    this._store.data.potentiallyVulnerablePasswords.push({
-      encryptedPassword: this._crypto.encrypt(login.password),
-    });
-    this._store.saveSoon();
-  },
-
-  isPotentiallyVulnerablePassword(login) {
-    return this._decryptedPotentiallyVulnerablePasswords.includes(
-      login.password
-    );
-  },
-
   get uiBusy() {
     return this._crypto.uiBusy;
   },
 
   get isLoggedIn() {
     return this._crypto.isLoggedIn;
   },
 
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/unit/test_vulnerable_passwords.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-add_task(async function setup() {
-  await Services.logins.initializationPromise;
-});
-
-add_task(async function test_vulnerable_password_methods() {
-  const storageJSON = Services.logins.wrappedJSObject._storage.wrappedJSObject;
-
-  let logins = TestData.loginList();
-  Assert.greater(logins.length, 0, "Initial logins length should be > 0.");
-
-  for (let loginInfo of logins) {
-    Services.logins.addLogin(loginInfo);
-    Assert.ok(
-      !storageJSON.isPotentiallyVulnerablePassword(loginInfo),
-      "No logins should be vulnerable until addVulnerablePasswords is called."
-    );
-  }
-
-  const vulnerableLogin = logins.shift();
-  storageJSON.addPotentiallyVulnerablePassword(vulnerableLogin);
-
-  Assert.ok(
-    storageJSON.isPotentiallyVulnerablePassword(vulnerableLogin),
-    "Login should be vulnerable after calling addVulnerablePassword."
-  );
-  for (let loginInfo of logins) {
-    Assert.ok(
-      !storageJSON.isPotentiallyVulnerablePassword(loginInfo),
-      "No other logins should be vulnerable when addVulnerablePassword is called" +
-        " with a single argument"
-    );
-  }
-});
--- a/toolkit/components/passwordmgr/test/unit/xpcshell.ini
+++ b/toolkit/components/passwordmgr/test/unit/xpcshell.ini
@@ -52,10 +52,8 @@ skip-if = os != "win"
 [test_PasswordGenerator.js]
 skip-if = os == "android" # Not packaged/used on Android
 [test_recipes_add.js]
 [test_recipes_content.js]
 [test_search_schemeUpgrades.js]
 [test_shadowHTTPLogins.js]
 [test_storage.js]
 [test_telemetry.js]
-[test_vulnerable_passwords.js]
-skip-if = os == "android" # Not implemented for storage-mozStorage