Bug 1520960 - Allow login capture from form submissions in private browsing when pref'd on. r=MattN
authorSam Foster <sfoster@mozilla.com>
Fri, 08 Feb 2019 22:56:02 +0000
changeset 458348 b4645dc802f9f242e2ea7f4fba89bd374d549f74
parent 458347 6c45ce836a12f98585902a3c0037b990424aa048
child 458349 b880dbaefb043bd5ccd1bfefcffff4702e792429
push id35524
push userncsoregi@mozilla.com
push dateSat, 09 Feb 2019 09:40:21 +0000
treeherdermozilla-central@ec61d092ed67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1520960
milestone67.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 1520960 - Allow login capture from form submissions in private browsing when pref'd on. r=MattN * Add a new pref to determine if we should prompt to capture logins in private browsing * Avoid non-user-directed updates to last-use timestamps for a form submission login in private browsing Differential Revision: https://phabricator.services.mozilla.com/D18409
browser/app/profile/firefox.js
modules/libpref/init/all.js
toolkit/components/passwordmgr/LoginHelper.jsm
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/components/passwordmgr/nsLoginManagerPrompter.js
toolkit/components/passwordmgr/test/browser/browser_private_window.js
toolkit/components/passwordmgr/test/browser/head.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1666,16 +1666,17 @@ pref("browser.migrate.chrome.history.max
 pref("dom.mozBrowserFramesEnabled", true);
 
 pref("extensions.pocket.api", "api.getpocket.com");
 pref("extensions.pocket.enabled", true);
 pref("extensions.pocket.oAuthConsumerKey", "40249-e88c401e1b1f2242d9e441c4");
 pref("extensions.pocket.site", "getpocket.com");
 
 pref("signon.schemeUpgrades", true);
+pref("signon.privateBrowsingCapture.enabled", true);
 
 // Enable the "Simplify Page" feature in Print Preview. This feature
 // is disabled by default in toolkit.
 pref("print.use_simplify_page", true);
 
 // Space separated list of URLS that are allowed to send objects (instead of
 // only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
 pref("webchannel.allowObject.urlWhitelist", "https://content.cdn.mozilla.net https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4654,16 +4654,17 @@ pref("font.name-list.monospace.x-unicode
 
 // Login Manager prefs
 pref("signon.rememberSignons",              true);
 pref("signon.rememberSignons.visibilityToggle", true);
 pref("signon.autofillForms",                true);
 pref("signon.autofillForms.http",           false);
 pref("signon.autologin.proxy",              false);
 pref("signon.formlessCapture.enabled",      true);
+pref("signon.privateBrowsingCapture.enabled", false);
 pref("signon.storeWhenAutocompleteOff",     true);
 pref("signon.debug",                        false);
 pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
 pref("signon.schemeUpgrades",               false);
 // This temporarily prevents the master password to reprompt for autocomplete.
 pref("signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
 
 // Satchel (Form Manager) prefs
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -29,16 +29,18 @@ const {XPCOMUtils} = ChromeUtils.import(
 var LoginHelper = {
   /**
    * Warning: these only update if a logger was created.
    */
   debug: Services.prefs.getBoolPref("signon.debug"),
   formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
   schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
   insecureAutofill: Services.prefs.getBoolPref("signon.autofillForms.http"),
+  privateBrowsingCaptureEnabled:
+    Services.prefs.getBoolPref("signon.privateBrowsingCapture.enabled"),
 
   createLogger(aLogPrefix) {
     let getMaxLogLevel = () => {
       return this.debug ? "debug" : "warn";
     };
 
     let logger;
     function getConsole() {
@@ -55,16 +57,18 @@ var LoginHelper = {
     }
 
     // Watch for pref changes and update this.debug and the maxLogLevel for created loggers
     Services.prefs.addObserver("signon.", () => {
       this.debug = Services.prefs.getBoolPref("signon.debug");
       this.formlessCaptureEnabled = Services.prefs.getBoolPref("signon.formlessCapture.enabled");
       this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
       this.insecureAutofill = Services.prefs.getBoolPref("signon.autofillForms.http");
+      this.privateBrowsingCaptureEnabled =
+        Services.prefs.getBoolPref("signon.privateBrowsingCapture.enabled");
       if (logger) {
         logger.maxLogLevel = getMaxLogLevel();
       }
     });
 
     return {
       log: (...args) => {
         if (this.debug) {
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -928,17 +928,18 @@ var LoginManagerContent = {
    *
    * @param {FormLike} form
    */
   _onFormSubmit(form) {
     log("_onFormSubmit", form);
     var doc = form.ownerDocument;
     var win = doc.defaultView;
 
-    if (PrivateBrowsingUtils.isContentWindowPrivate(win)) {
+    if (PrivateBrowsingUtils.isContentWindowPrivate(win) &&
+        !LoginHelper.privateBrowsingCaptureEnabled) {
       // We won't do anything in private browsing mode anyway,
       // so there's no need to perform further checks.
       log("(form submission ignored in private browsing mode)");
       return;
     }
 
     // If password saving is disabled (globally or for host), bail out now.
     if (!gEnabled) {
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -10,16 +10,18 @@ const {Services} = ChromeUtils.import("r
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 ChromeUtils.defineModuleGetter(this, "AutoCompletePopup",
                                "resource://gre/modules/AutoCompletePopup.jsm");
 ChromeUtils.defineModuleGetter(this, "DeferredTask",
                                "resource://gre/modules/DeferredTask.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginHelper",
                                "resource://gre/modules/LoginHelper.jsm");
+ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
+                               "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerParent");
   return logger.log.bind(logger);
 });
 
 var EXPORTED_SYMBOLS = [ "LoginManagerParent" ];
 
@@ -310,16 +312,20 @@ var LoginManagerParent = {
           }
         }
       }
 
       return prompterSvc;
     }
 
     function recordLoginUse(login) {
+      if (!target || PrivateBrowsingUtils.isBrowserPrivate(target)) {
+        // don't record non-interactive use in private browsing
+        return;
+      }
       // Update the lastUsed timestamp and increment the use count.
       let propBag = Cc["@mozilla.org/hash-property-bag;1"].
                     createInstance(Ci.nsIWritablePropertyBag);
       propBag.setProperty("timeLastUsed", Date.now());
       propBag.setProperty("timesUsedIncrement", 1);
       Services.logins.modifyLogin(login, propBag);
     }
 
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -298,16 +298,22 @@ LoginManagerPrompter.prototype = {
     // not provide a window.  The callers which really care about this
     // will indeed pass down a window to us, and for those who don't,
     // we can just assume that we don't want to save the entered login
     // information.
     this.log("We have no chromeWindow so assume we're in a private context");
     return true;
   },
 
+  get _allowRememberLogin() {
+    if (!this._inPrivateBrowsing) {
+      return true;
+    }
+    return LoginHelper.privateBrowsingCaptureEnabled;
+  },
 
 
 
   /* ---------- nsIAuthPrompt prompts ---------- */
 
 
   /**
    * Wrapper around the prompt service prompt. Saving random fields here
@@ -767,34 +773,36 @@ LoginManagerPrompter.prototype = {
   set openerBrowser(aOpenerBrowser) {
     this._openerBrowser = aOpenerBrowser;
   },
 
   promptToSavePassword(aLogin) {
     this.log("promptToSavePassword");
     var notifyObj = this._getPopupNote();
     if (notifyObj) {
-      this._showLoginCaptureDoorhanger(aLogin, "password-save");
+      this._showLoginCaptureDoorhanger(aLogin, "password-save", {
+        dismissed: this._inPrivateBrowsing,
+      });
       Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
     } else {
       this._showSaveLoginDialog(aLogin);
     }
   },
 
   /**
    * Displays the PopupNotifications.jsm doorhanger for password save or change.
    *
    * @param {nsILoginInfo} login
    *        Login to save or change. For changes, this login should contain the
    *        new password.
    * @param {string} type
    *        This is "password-save" or "password-change" depending on the
    *        original notification type. This is used for telemetry and tests.
    */
-  _showLoginCaptureDoorhanger(login, type) {
+  _showLoginCaptureDoorhanger(login, type, options = {}) {
     let { browser } = this._getNotifyWindow();
     if (!browser) {
       return;
     }
 
     let saveMsgNames = {
       prompt: login.username === "" ? "saveLoginMsgNoUser"
                                     : "saveLoginMsg",
@@ -999,17 +1007,17 @@ LoginManagerPrompter.prototype = {
 
     this._getPopupNote().show(
       browser,
       "password",
       promptMsg,
       "password-notification-icon",
       mainAction,
       secondaryActions,
-      {
+      Object.assign({
         timeout: Date.now() + 10000,
         persistWhileVisible: true,
         passwordNotificationType: type,
         hideClose: !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton"),
         eventCallback(topic) {
           switch (topic) {
             case "showing":
               currentNotification = this;
@@ -1048,17 +1056,17 @@ LoginManagerPrompter.prototype = {
               chromeDoc.getElementById("password-notification-password")
                        .removeEventListener("input", onInput);
               chromeDoc.getElementById("password-notification-visibilityToggle")
                        .removeEventListener("command", onVisibilityToggle);
               break;
           }
           return false;
         },
-      }
+      }, options),
     );
   },
 
   _removeLoginNotifications() {
     var popupNote = this._getPopupNote();
     if (popupNote) {
       popupNote = popupNote.getNotification("password");
     }
--- a/toolkit/components/passwordmgr/test/browser/browser_private_window.js
+++ b/toolkit/components/passwordmgr/test/browser/browser_private_window.js
@@ -1,51 +1,117 @@
 "use strict";
 
+function getDialogDoc() {
+  // Trudge through all the open windows, until we find the one
+  // that has either commonDialog.xul or selectDialog.xul loaded.
+  // var enumerator = Services.wm.getEnumerator("navigator:browser");
+  for (let {docShell} of Services.wm.getEnumerator(null)) {
+    var containedDocShells = docShell.getDocShellEnumerator(
+      docShell.typeChrome,
+      docShell.ENUMERATE_FORWARDS);
+    for (let childDocShell of containedDocShells) {
+      // Get the corresponding document for this docshell
+      // We don't want it if it's not done loading.
+      if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
+        continue;
+      }
+      var childDoc = childDocShell.contentViewer.DOMDocument;
+      if (childDoc.location.href != "chrome://global/content/commonDialog.xul" &&
+          childDoc.location.href != "chrome://global/content/selectDialog.xul") {
+        continue;
+      }
+
+      // We're expecting the dialog to be focused. If it's not yet, try later.
+      // (In particular, this is needed on Linux to reliably check focused elements.)
+      if (Services.focus.focusedWindow != childDoc.defaultView) {
+        continue;
+      }
+
+      return childDoc;
+    }
+  }
+
+  return null;
+}
+
 async function submitForm(browser, formAction, selectorValues) {
   function contentSubmitForm([contentFormAction, contentSelectorValues]) {
     let doc = content.document;
     let form = doc.getElementById("form");
     form.action = contentFormAction;
     for (let [sel, value] of Object.entries(contentSelectorValues)) {
       try {
         doc.querySelector(sel).value = value;
       } catch (ex) {
         throw new Error(`submitForm: Couldn't set value of field at: ${sel}`);
       }
     }
     form.submit();
   }
   await ContentTask.spawn(browser, [formAction, selectorValues], contentSubmitForm);
-  let result = await getFormSubmissionResult(browser, formAction);
+  let result = await getResponseResult(browser, formAction);
   return result;
 }
 
-async function getFormSubmissionResult(browser, resultUrl) {
+async function getResponseResult(browser, resultUrl) {
   let fieldValues = await ContentTask.spawn(browser, [resultUrl], async function(contentResultUrl) {
     await ContentTaskUtils.waitForCondition(() => {
       return content.location.pathname.endsWith(contentResultUrl) &&
         content.document.readyState == "complete";
     }, `Wait for form submission load (${contentResultUrl})`);
     let username = content.document.getElementById("user").textContent;
     let password = content.document.getElementById("pass").textContent;
     return {
       username,
       password,
     };
   });
   return fieldValues;
 }
 
+async function waitForAuthPrompt() {
+  let promptDoc = await BrowserTestUtils.waitForCondition(() => {
+    return getAuthPrompt();
+  });
+  info("Got prompt: " + promptDoc);
+  return promptDoc;
+}
+
+function getAuthPrompt() {
+  let doc = getDialogDoc();
+  if (!doc) {
+    return false; // try again in a bit
+  }
+  return doc;
+}
+
+async function loadAccessRestrictedURL(browser, url, username, password) {
+  let browserLoaded = BrowserTestUtils.browserLoaded(browser);
+  BrowserTestUtils.loadURI(browser, url);
+
+  let promptDoc = await waitForAuthPrompt();
+  let dialogUI = promptDoc.defaultView.Dialog.ui;
+  ok(dialogUI, "Got expected HTTP auth dialog Dialog.ui");
+
+  // fill and submit the dialog form
+  dialogUI.loginTextbox.value = username;
+  dialogUI.password1Textbox.value = password;
+  promptDoc.getElementById("commonDialog").acceptDialog();
+  await browserLoaded;
+}
+
+const PRIVATE_BROWSING_CAPTURE_PREF = "signon.privateBrowsingCapture.enabled";
 let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
                                              Ci.nsILoginInfo, "init");
 let login = new nsLoginInfo("https://example.com", "https://example.com", null,
                             "notifyu1", "notifyp1", "user", "pass");
-let form1Url = `https://example.com/${DIRECTORY_PATH}subtst_privbrowsing_1.html`;
-let form2Url = `https://example.com/${DIRECTORY_PATH}subtst_privbrowsing_2.html`;
+const form1Url = `https://example.com/${DIRECTORY_PATH}subtst_privbrowsing_1.html`;
+const form2Url = `https://example.com/${DIRECTORY_PATH}subtst_privbrowsing_2.html`;
+const authUrl = `https://example.com/${DIRECTORY_PATH}authenticate.sjs`;
 
 let normalWin;
 let privateWin;
 
 // XXX: Note that tasks are currently run in sequence. Some tests may assume the state
 // resulting from successful or unsuccessful logins in previous tasks
 
 add_task(async function test_setup() {
@@ -65,123 +131,240 @@ add_task(async function test_normal_popu
       "#pass": "notifyp1",
     });
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
 
     let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
     ok(notif, "got notification popup");
     if (notif) {
+      ok(!notif.wasDismissed, "notification should not be dismissed");
       notif.remove();
     }
   });
 });
 
 add_task(async function test_private_popup_notification_2() {
-  info("test 2: run inside of private mode, popup notification should not appear");
+  info("test 2: run inside of private mode, dismissed popup notification should appear");
+
+  const capturePrefValue = Services.prefs.getBoolPref(PRIVATE_BROWSING_CAPTURE_PREF);
+  ok(capturePrefValue, `Expect ${PRIVATE_BROWSING_CAPTURE_PREF} to default to true`);
+
+  // clear existing logins for parity with the previous test
+  Services.logins.removeAllLogins();
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser: privateWin.gBrowser,
+    url: form1Url,
+  }, async function(browser) {
+    let fieldValues = await submitForm(browser, "formsubmit.sjs", {
+      "#user": "notifyu1",
+      "#pass": "notifyp1",
+    });
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+
+    let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    ok(notif, "Expected notification popup");
+    if (notif) {
+      ok(notif.wasDismissed, "notification should be dismissed");
+      notif.remove();
+    }
+  });
+  is(Services.logins.getAllLogins().length, 0, "No logins were saved");
+});
+
+add_task(async function test_private_popup_notification_no_capture_pref_2b() {
+  info("test 2b: run inside of private mode, with capture pref off," +
+       "popup notification should not appear");
+
+  const capturePrefValue = Services.prefs.getBoolPref(PRIVATE_BROWSING_CAPTURE_PREF);
+  Services.prefs.setBoolPref(PRIVATE_BROWSING_CAPTURE_PREF, false);
+
+  // clear existing logins for parity with the previous test
+  Services.logins.removeAllLogins();
+
   await BrowserTestUtils.withNewTab({
     gBrowser: privateWin.gBrowser,
     url: form1Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {
       "#user": "notifyu1",
       "#pass": "notifyp1",
     });
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
 
     let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    // restore the pref to its original value
+    Services.prefs.setBoolPref(PRIVATE_BROWSING_CAPTURE_PREF, capturePrefValue);
+
     ok(!notif, "Expected no notification popup");
     if (notif) {
       notif.remove();
     }
   });
+  is(Services.logins.getAllLogins().length, 0, "No logins were saved");
 });
 
 add_task(async function test_normal_popup_notification_3() {
-  info("test 3: run outside of private mode, popup notification should appear");
+  info("test 3: run with a login, outside of private mode, " +
+       "match existing username/password: no popup notification should appear");
+
+  Services.logins.removeAllLogins();
+  Services.logins.addLogin(login);
+  let allLogins = Services.logins.getAllLogins();
+  // Sanity check the HTTP login exists.
+  is(allLogins.length, 1, "Should have the HTTP login");
+  let timeLastUsed = allLogins[0].timeLastUsed;
+  let loginGuid = allLogins[0].guid;
+
   await BrowserTestUtils.withNewTab({
     gBrowser: normalWin.gBrowser,
     url: form1Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {
       "#user": "notifyu1",
       "#pass": "notifyp1",
     });
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
 
     let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
-    ok(notif, "got notification popup");
+    ok(!notif, "got no notification popup");
     if (notif) {
       notif.remove();
     }
   });
+  allLogins = Services.logins.getAllLogins();
+  is(allLogins[0].guid, loginGuid, "Sanity-check we are comparing the same login record");
+  ok(allLogins[0].timeLastUsed > timeLastUsed, "The timeLastUsed timestamp has been updated");
+});
+
+add_task(async function test_private_popup_notification_3b() {
+  info("test 3b: run with a login, in private mode," +
+       " match existing username/password: no popup notification should appear");
+
+  Services.logins.removeAllLogins();
+  Services.logins.addLogin(login);
+  let allLogins = Services.logins.getAllLogins();
+  // Sanity check the HTTP login exists.
+  is(allLogins.length, 1, "Should have the HTTP login");
+  let timeLastUsed = allLogins[0].timeLastUsed;
+  let loginGuid = allLogins[0].guid;
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser: privateWin.gBrowser,
+    url: form1Url,
+  }, async function(browser) {
+    let fieldValues = await submitForm(browser, "formsubmit.sjs", {
+      "#user": "notifyu1",
+      "#pass": "notifyp1",
+    });
+    is(fieldValues.username, "notifyu1", "Checking submitted username");
+    is(fieldValues.password, "notifyp1", "Checking submitted password");
+
+    let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    ok(!notif, "got no notification popup");
+    if (notif) {
+      notif.remove();
+    }
+  });
+  allLogins = Services.logins.getAllLogins();
+  is(allLogins[0].guid, loginGuid, "Sanity-check we are comparing the same login record");
+  is(allLogins[0].timeLastUsed, timeLastUsed, "The timeLastUsed timestamp has not been updated");
 });
 
 add_task(async function test_normal_new_password_4() {
   info("test 4: run with a login, outside of private mode," +
        " add a new password: popup notification should appear");
   Services.logins.removeAllLogins();
   Services.logins.addLogin(login);
+  let allLogins = Services.logins.getAllLogins();
   // Sanity check the HTTP login exists.
-  is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
+  is(allLogins.length, 1, "Should have the HTTP login");
+  let timeLastUsed = allLogins[0].timeLastUsed;
+  let loginGuid = allLogins[0].guid;
 
   await BrowserTestUtils.withNewTab({
     gBrowser: normalWin.gBrowser,
     url: form2Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {
       "#pass": "notifyp1",
       "#newpass": "notifyp2",
     });
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change", PopupNotifications, browser);
     ok(notif, "got notification popup");
     if (notif) {
+      ok(!notif.wasDismissed, "notification should not be dismissed");
       notif.remove();
     }
   });
+  // We put up a doorhanger, but didn't interact with it, so we expect the login timestamps
+  // to be unchanged
+  allLogins = Services.logins.getAllLogins();
+  is(allLogins[0].guid, loginGuid, "Sanity-check we are comparing the same login record");
+  is(allLogins[0].timeLastUsed, timeLastUsed, "The timeLastUsed timestamp was not updated");
 });
 
 add_task(async function test_private_new_password_5() {
   info("test 5: run with a login, in private mode," +
-      "add a new password: popup notification should not appear");
+      "add a new password: popup notification should appear");
+
+  const capturePrefValue = Services.prefs.getBoolPref(PRIVATE_BROWSING_CAPTURE_PREF);
+  ok(capturePrefValue, `Expect ${PRIVATE_BROWSING_CAPTURE_PREF} to default to true`);
+
+  let allLogins = Services.logins.getAllLogins();
+  // Sanity check the HTTP login exists.
+  is(allLogins.length, 1, "Should have the HTTP login");
+  let timeLastUsed = allLogins[0].timeLastUsed;
+  let loginGuid = allLogins[0].guid;
+
   await BrowserTestUtils.withNewTab({
     gBrowser: privateWin.gBrowser,
     url: form2Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {
       "#pass": "notifyp1",
       "#newpass": "notifyp2",
     });
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change", PopupNotifications, browser);
-    ok(!notif, "Expected no notification popup");
+    ok(notif, "Expected notification popup");
     if (notif) {
+      ok(!notif.wasDismissed, "notification should not be dismissed");
       notif.remove();
     }
   });
+  // We put up a doorhanger, but didn't interact with it, so we expect the login timestamps
+  // to be unchanged
+  allLogins = Services.logins.getAllLogins();
+  is(allLogins[0].guid, loginGuid, "Sanity-check we are comparing the same login record");
+  is(allLogins[0].timeLastUsed, timeLastUsed, "The timeLastUsed timestamp has not been updated");
 });
 
 add_task(async function test_normal_with_login_6() {
   info("test 6: run with a login, outside of private mode, " +
-      "submit with an existing password (from test 4): popup notification should appear");
+       "submit with an existing password (from test 4): popup notification should appear");
+
   await BrowserTestUtils.withNewTab({
     gBrowser: normalWin.gBrowser,
     url: form2Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {
       "#pass": "notifyp1",
       "#newpass": "notifyp2",
     });
     is(fieldValues.password, "notifyp1", "Checking submitted password");
     let notif = getCaptureDoorhanger("password-change", PopupNotifications, browser);
     ok(notif, "got notification popup");
     if (notif) {
+      ok(!notif.wasDismissed, "notification should not be dismissed");
       notif.remove();
     }
     Services.logins.removeLogin(login);
   });
 });
 
 add_task(async function test_normal_autofilled_7() {
   info("test 7: verify that the user/pass pair was autofilled");
@@ -193,24 +376,21 @@ add_task(async function test_normal_auto
   await BrowserTestUtils.withNewTab({
     gBrowser: normalWin.gBrowser,
     url: "about:blank",
   }, async function(browser) {
     // Add the observer before loading the form page
     let formFilled = ContentTask.spawn(browser, null, async function() {
       const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
       await TestUtils.topicObserved("passwordmgr-processed-form");
-      info("passwordmgr-processed-form observed");
       await Promise.resolve();
     });
 
-    info("withNewTab loading form uri");
     await BrowserTestUtils.loadURI(browser, form1Url);
     await formFilled;
-    info("withNewTab callback, form was filled");
 
     // the form should have been autofilled, so submit without updating field values
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {});
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
   });
 });
 
@@ -224,68 +404,150 @@ add_task(async function test_private_not
     url: form1Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {});
     ok(!fieldValues.username, "Checking submitted username");
     ok(!fieldValues.password, "Checking submitted password");
   });
 });
 
-add_task(async function test_private_autocomplete_9() {
-  info("test 9: verify that the user/pass pair was available for autocomplete");
-  // Sanity check the HTTP login exists.
-  is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
+// Disabled for Bug 1523777
+// add_task(async function test_private_autocomplete_9() {
+//   info("test 9: verify that the user/pass pair was available for autocomplete");
+//   // Sanity check the HTTP login exists.
+//   is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
 
-  await BrowserTestUtils.withNewTab({
-    gBrowser: privateWin.gBrowser,
-    url: form1Url,
-  }, async function(browser) {
-    let popup = document.getElementById("PopupAutoComplete");
-    ok(popup, "Got popup");
+//   await BrowserTestUtils.withNewTab({
+//     gBrowser: privateWin.gBrowser,
+//     url: form1Url,
+//   }, async function(browser) {
+//     let popup = document.getElementById("PopupAutoComplete");
+//     ok(popup, "Got popup");
 
-    let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
+//     let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
 
-    // focus the user field. This should trigger the autocomplete menu
-    await ContentTask.spawn(browser, null, async function() {
-      content.document.getElementById("user").focus();
-    });
-    await promiseShown;
-    ok(promiseShown, "autocomplete shown");
+//     // focus the user field. This should trigger the autocomplete menu
+//     await ContentTask.spawn(browser, null, async function() {
+//       content.document.getElementById("user").focus();
+//     });
+//     await promiseShown;
+//     ok(promiseShown, "autocomplete shown");
 
-    let promiseFormInput = ContentTask.spawn(browser, null, async function() {
-      let doc = content.document;
-      await new Promise(resolve => {
-        doc.getElementById("form").addEventListener("input", resolve, { once: true });
-      });
-    });
-    info("sending keys");
-    // select the item and hit enter to fill the form
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
-    await promiseFormInput;
+//     let promiseFormInput = ContentTask.spawn(browser, null, async function() {
+//       let doc = content.document;
+//       await new Promise(resolve => {
+//         doc.getElementById("form").addEventListener("input", resolve, { once: true });
+//       });
+//     });
+//     info("sending keys");
+//     // select the item and hit enter to fill the form
+//     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+//     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+//     await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+//     await promiseFormInput;
 
-    let fieldValues = await submitForm(browser, "formsubmit.sjs", {});
-    is(fieldValues.username, "notifyu1", "Checking submitted username");
-    is(fieldValues.password, "notifyp1", "Checking submitted password");
-  });
-}).skip(); // Bug 1523777
+//     let fieldValues = await submitForm(browser, "formsubmit.sjs", {});
+//     is(fieldValues.username, "notifyu1", "Checking submitted username");
+//     is(fieldValues.password, "notifyp1", "Checking submitted password");
+//   });
+// });
 
 add_task(async function test_normal_autofilled_10() {
   info("test 10: verify that the user/pass pair does get autofilled in non-private window");
   // Sanity check the HTTP login exists.
   is(Services.logins.getAllLogins().length, 1, "Should have the HTTP login");
 
   await BrowserTestUtils.withNewTab({
     gBrowser: normalWin.gBrowser,
     url: form1Url,
   }, async function(browser) {
     let fieldValues = await submitForm(browser, "formsubmit.sjs", {});
     is(fieldValues.username, "notifyu1", "Checking submitted username");
     is(fieldValues.password, "notifyp1", "Checking submitted password");
   });
 });
 
+add_task(async function test_normal_http_basic_auth() {
+  info("test normal/basic-auth: verify that we get a doorhanger after basic-auth login");
+  Services.logins.removeAllLogins();
+  clearHttpAuths();
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser: normalWin.gBrowser,
+    url: "https://example.com",
+  }, async function(browser) {
+    await loadAccessRestrictedURL(browser, authUrl, "test", "testpass");
+    ok(true, "Auth-required page loaded");
+
+    // verify result in the response document
+    let fieldValues = await ContentTask.spawn(browser, [], async function() {
+      let username = content.document.getElementById("user").textContent;
+      let password = content.document.getElementById("pass").textContent;
+      let ok = content.document.getElementById("ok").textContent;
+      return {
+        username,
+        password,
+        ok,
+      };
+    });
+    is(fieldValues.ok, "PASS", "Checking authorization passed");
+    is(fieldValues.username, "test", "Checking authorized username");
+    is(fieldValues.password, "testpass", "Checking authorized password");
+
+    let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    ok(notif, "got notification popup");
+    if (notif) {
+      ok(!notif.wasDismissed, "notification should not be dismissed");
+      notif.remove();
+    }
+  });
+});
+
+add_task(async function test_private_http_basic_auth() {
+  info("test private/basic-auth: verify that we don't get a doorhanger after basic-auth login");
+  Services.logins.removeAllLogins();
+  clearHttpAuths();
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser: privateWin.gBrowser,
+    url: "https://example.com",
+  }, async function(browser) {
+    await loadAccessRestrictedURL(browser, authUrl, "test", "testpass");
+    let fieldValues = await getResponseResult(browser, "authenticate.sjs");
+    is(fieldValues.username, "test", "Checking authorized username");
+    is(fieldValues.password, "testpass", "Checking authorized password");
+
+    let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    ok(!notif, "got no notification popup");
+    if (notif) {
+      notif.remove();
+    }
+  });
+});
+
+add_task(async function test_private_http_basic_auth_no_capture_pref() {
+  info("test private/basic-auth: verify that we don't get a doorhanger after basic-auth login" +
+       "with capture pref off");
+
+  Services.logins.removeAllLogins();
+  clearHttpAuths();
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser: privateWin.gBrowser,
+    url: "https://example.com",
+  }, async function(browser) {
+    await loadAccessRestrictedURL(browser, authUrl, "test", "testpass");
+    let fieldValues = await getResponseResult(browser, "authenticate.sjs");
+    is(fieldValues.username, "test", "Checking authorized username");
+    is(fieldValues.password, "testpass", "Checking authorized password");
+
+    let notif = getCaptureDoorhanger("password-save", PopupNotifications, browser);
+    ok(!notif, "got no notification popup");
+    if (notif) {
+      notif.remove();
+    }
+  });
+});
+
 add_task(async function test_cleanup() {
-  Services.logins.removeAllLogins();
   await BrowserTestUtils.closeWindow(normalWin);
   await BrowserTestUtils.closeWindow(privateWin);
 });
--- a/toolkit/components/passwordmgr/test/browser/head.js
+++ b/toolkit/components/passwordmgr/test/browser/head.js
@@ -7,16 +7,17 @@ ChromeUtils.import("resource://testing-c
 add_task(async function common_initialize() {
   await SpecialPowers.pushPrefEnv({"set": [["signon.rememberSignons", true]]});
 });
 
 registerCleanupFunction(async function cleanup_removeAllLoginsAndResetRecipes() {
   await SpecialPowers.popPrefEnv();
 
   Services.logins.removeAllLogins();
+  clearHttpAuths();
 
   let recipeParent = LoginTestUtils.recipes.getRecipeParent();
   if (!recipeParent) {
     // No need to reset the recipes if the recipe module wasn't even loaded.
     return;
   }
   await recipeParent.then(recipeParentResult => recipeParentResult.reset());
 });
@@ -66,16 +67,23 @@ function checkOnlyLoginWasUsedTwice({ ju
   ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
   if (justChanged) {
     is(logins[0].timeLastUsed, logins[0].timePasswordChanged, "timeLastUsed == timePasswordChanged");
   } else {
     is(logins[0].timeCreated, logins[0].timePasswordChanged, "timeChanged not updated");
   }
 }
 
+function clearHttpAuths() {
+  let authMgr = Cc["@mozilla.org/network/http-auth-manager;1"].
+              getService(Ci.nsIHttpAuthManager);
+  authMgr.clearAll();
+}
+
+
 // Begin popup notification (doorhanger) functions //
 
 const REMEMBER_BUTTON = "button";
 const NEVER_MENUITEM = 0;
 
 const CHANGE_BUTTON = "button";
 const DONT_CHANGE_BUTTON = "secondaryButton";