Backed out 9 changesets (bug 1330111) for bc bustage in various tests a=backout
authorWes Kocher <wkocher@mozilla.com>
Mon, 06 Feb 2017 17:54:14 -0800
changeset 341046 955dd973d5b7ed2426e87e805a080902d7098e3d
parent 341045 f0453084d86e87070b2894eab61a2b58f1964768
child 341047 c5d678ebcbcd24dce64426492e3f20ca5a1bd261
push id86616
push userkwierso@gmail.com
push dateTue, 07 Feb 2017 01:54:26 +0000
treeherdermozilla-inbound@955dd973d5b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1330111
milestone54.0a1
backs oute87c86970a290090aa8e5b32e66a3605ede206b5
9e2e44a7a159ee0c27951df01c81f5ac762aba91
7abc377ef80925f0064ccc77fd42aef290434d08
f1e782d1c94472b7a55d4f3abd2e483bd6bd8334
d94d728469529b860313e5ae326123e4b3c3b079
23180cae4a46af87493a2b84ee7d1d749237e945
09c59f5c572a9c595abb55da30c8ceba488bc4f5
e208e0b5134904b69f24b0ab58edc0802bc59bae
271035a2bc64b6aa1121d996ffaf83571720bff2
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 9 changesets (bug 1330111) for bc bustage in various tests a=backout Backed out changeset e87c86970a29 (bug 1330111) Backed out changeset 9e2e44a7a159 (bug 1330111) Backed out changeset 7abc377ef809 (bug 1330111) Backed out changeset f1e782d1c944 (bug 1330111) Backed out changeset d94d72846952 (bug 1330111) Backed out changeset 23180cae4a46 (bug 1330111) Backed out changeset 09c59f5c572a (bug 1330111) Backed out changeset e208e0b51349 (bug 1330111) Backed out changeset 271035a2bc64 (bug 1330111) MozReview-Commit-ID: 6mfKgu07dow
browser/extensions/formautofill/content/FormAutofillContent.js
mobile/android/app/mobile.js
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/test/browser/browser.ini
toolkit/components/passwordmgr/test/browser/browser_context_menu_autocomplete_interaction.js
toolkit/components/passwordmgr/test/mochitest/mochitest.ini
toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
toolkit/components/passwordmgr/test/mochitest/test_username_focus.html
toolkit/components/passwordmgr/test/pwmgr_common.js
toolkit/components/satchel/nsFormFillController.cpp
toolkit/components/satchel/nsFormFillController.h
toolkit/components/satchel/nsIFormFillController.idl
toolkit/components/satchel/test/satchel_common.js
toolkit/modules/FormLikeFactory.jsm
--- a/browser/extensions/formautofill/content/FormAutofillContent.js
+++ b/browser/extensions/formautofill/content/FormAutofillContent.js
@@ -286,28 +286,28 @@ AutofillProfileAutoCompleteSearch.protot
   /**
    * Get the input's information from FormAutofillContent's cache.
    *
    * @returns {Object}
    *          Target input's information that cached in FormAutofillContent.
    */
   getInputDetails() {
     // TODO: Maybe we'll need to wait for cache ready if detail is empty.
-    return FormAutofillContent.getInputDetails(formFillController.focusedInput);
+    return FormAutofillContent.getInputDetails(formFillController.getFocusedInput());
   },
 
   /**
    * Get the form's information from FormAutofillContent's cache.
    *
    * @returns {Array<Object>}
    *          Array of the inputs' information for the target form.
    */
   getFormDetails() {
     // TODO: Maybe we'll need to wait for cache ready if details is empty.
-    return FormAutofillContent.getFormDetails(formFillController.focusedInput);
+    return FormAutofillContent.getFormDetails(formFillController.getFocusedInput());
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AutofillProfileAutoCompleteSearch]);
 
 let ProfileAutocomplete = {
   _registered: false,
   _factory: null,
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -167,17 +167,16 @@ pref("browser.download.manager.flashCoun
 pref("browser.download.manager.displayedHistoryDays", 7);
 pref("browser.download.manager.addToRecentDocs", true);
 
 /* download helper */
 pref("browser.helperApps.deleteTempFileOnExit", false);
 
 /* password manager */
 pref("signon.rememberSignons", true);
-pref("signon.autofillForms.http", true);
 pref("signon.expireMasterPassword", false);
 pref("signon.debug", false);
 
 /* form helper (scroll to and optionally zoom into editable fields)  */
 pref("formhelper.mode", 2);  // 0 = disabled, 1 = enabled, 2 = dynamic depending on screen size
 pref("formhelper.autozoom", true);
 
 /* find helper */
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -5,25 +5,23 @@
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
                           "LoginFormFactory",
                           "UserAutoCompleteResult" ];
 
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
-const AUTOCOMPLETE_AFTER_CONTEXTMENU_THRESHOLD_MS = 250;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Cu.import("resource://gre/modules/InsecurePasswordUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginRecipesContent",
                                   "resource://gre/modules/LoginRecipes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
@@ -36,23 +34,21 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerContent");
   return logger.log.bind(logger);
 });
 
 // These mirror signon.* prefs.
 var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
-var gLastContextMenuEventTimeStamp = 0;
 
 var observer = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsIFormSubmitObserver,
                                           Ci.nsIWebProgressListener,
-                                          Ci.nsIDOMEventListener,
                                           Ci.nsISupportsWeakReference]),
 
   // nsIFormSubmitObserver
   notify(formElement, aWindow, actionURI) {
     log("observer notified for form submission.");
 
     // We're invoked before the content's |onsubmit| handlers, so we
     // can grab form data before it might be modified (see bug 257781).
@@ -107,43 +103,16 @@ var observer = {
     if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
       log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
       return;
     }
 
     log("onStateChange handled:", channel);
     LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
   },
-
-  handleEvent(aEvent) {
-    if (!aEvent.isTrusted) {
-      return;
-    }
-
-    if (!gEnabled) {
-      return;
-    }
-
-    switch (aEvent.type) {
-      // Only used for username fields.
-      case "focus": {
-        LoginManagerContent._onUsernameFocus(aEvent);
-        break;
-      }
-
-      case "contextmenu": {
-        gLastContextMenuEventTimeStamp = aEvent.timeStamp;
-        break;
-      }
-
-      default: {
-        throw new Error("Unexpected event");
-      }
-    }
-  },
 };
 
 Services.obs.addObserver(observer, "earlyformsubmit", false);
 var prefBranch = Services.prefs.getBranch("signon.");
 prefBranch.addObserver("", observer.onPrefChange, false);
 
 observer.onPrefChange(); // read initial values
 
@@ -458,20 +427,16 @@ var LoginManagerContent = {
   /**
    * Retrieves a reference to the state object associated with the given
    * document. This is initialized to an object with default values.
    */
   stateForDocument(document) {
     let loginFormState = this.loginFormStateByDocument.get(document);
     if (!loginFormState) {
       loginFormState = {
-        /**
-         * Keeps track of filled fields and values.
-         */
-        fillsByRootElement: new WeakMap(),
         loginFormRootElements: new Set(),
       };
       this.loginFormStateByDocument.set(document, loginFormState);
     }
     return loginFormState;
   },
 
   /**
@@ -552,58 +517,19 @@ var LoginManagerContent = {
 
   loginsFound({ form, loginsFound, recipes }) {
     let doc = form.ownerDocument;
     let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);
 
     this._fillForm(form, autofillForm, false, false, false, loginsFound, recipes);
   },
 
-  /**
-   * Focus event handler for username fields to decide whether to show autocomplete.
-   * @param {FocusEvent} event
-   */
-  _onUsernameFocus(event) {
-    let focusedField = event.target;
-    if (!focusedField.mozIsTextField(true) || focusedField.readOnly) {
-      return;
-    }
-
-    if (this._isLoginAlreadyFilled(focusedField)) {
-      log("_onUsernameFocus: Already filled");
-      return;
-    }
-
-    /*
-     * A `focus` event is fired before a `contextmenu` event if a user right-clicks into an
-     * unfocused field. In that case we don't want to show both autocomplete and a context menu
-     * overlapping so we spin the event loop to see if a `contextmenu` event is coming next. If no
-     * `contextmenu` event was seen and the focused field is still focused by the form fill
-     * controller then show the autocomplete popup.
-     */
-    setTimeout(function maybeOpenAutocompleteAfterFocus() {
-      // Even though the `focus` event happens first, its .timeStamp is greater in
-      // testing and I don't want to rely on that so the absolute value is used.
-      let timeDiff = Math.abs(gLastContextMenuEventTimeStamp - event.timeStamp);
-      if (timeDiff < AUTOCOMPLETE_AFTER_CONTEXTMENU_THRESHOLD_MS) {
-        log("Not opening autocomplete after focus since a context menu was opened within",
-            timeDiff, "ms");
-        return;
-      }
-
-      if (this._formFillService.focusedInput == focusedField) {
-        log("maybeOpenAutocompleteAfterFocus: Opening the autocomplete popup");
-        this._formFillService.showPopup();
-      } else {
-        log("maybeOpenAutocompleteAfterFocus: FormFillController has a different focused input");
-      }
-    }.bind(this), 0);
-  },
-
-  /**
+  /*
+   * onUsernameInput
+   *
    * Listens for DOMAutoComplete and blur events on an input field.
    */
   onUsernameInput(event) {
     if (!event.isTrusted)
       return;
 
     if (!gEnabled)
       return;
@@ -971,64 +897,67 @@ var LoginManagerContent = {
                                       oldPasswordField: mockOldPassword },
                                     { openerTopWindow });
   },
 
   /**
    * Attempt to find the username and password fields in a form, and fill them
    * in using the provided logins and recipes.
    *
-   * @param {LoginForm} form
+   * @param {HTMLFormElement} form
    * @param {bool} autofillForm denotes if we should fill the form in automatically
    * @param {bool} clobberUsername controls if an existing username can be overwritten.
    *                               If this is false and an inputElement of type password
    *                               is also passed, the username field will be ignored.
    *                               If this is false and no inputElement is passed, if the username
    *                               field value is not found in foundLogins, it will not fill the password.
    * @param {bool} clobberPassword controls if an existing password value can be
    *                               overwritten
    * @param {bool} userTriggered is an indication of whether this filling was triggered by
    *                             the user
    * @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
    * @param {Set} recipes that could be used to affect how the form is filled
    * @param {Object} [options = {}] is a list of options for this method.
             - [inputElement] is an optional target input element we want to fill
    */
   _fillForm(form, autofillForm, clobberUsername, clobberPassword,
-            userTriggered, foundLogins, recipes, {inputElement} = {}) {
-    if (form instanceof Ci.nsIDOMHTMLFormElement) {
-      throw new Error("_fillForm should only be called with FormLike objects");
-    }
-
+                        userTriggered, foundLogins, recipes, {inputElement} = {}) {
     log("_fillForm", form.elements);
     let ignoreAutocomplete = true;
-    // Will be set to one of AUTOFILL_RESULT in the `try` block.
-    let autofillResult = -1;
     const AUTOFILL_RESULT = {
       FILLED: 0,
       NO_PASSWORD_FIELD: 1,
       PASSWORD_DISABLED_READONLY: 2,
       NO_LOGINS_FIT: 3,
       NO_SAVED_LOGINS: 4,
       EXISTING_PASSWORD: 5,
       EXISTING_USERNAME: 6,
       MULTIPLE_LOGINS: 7,
       NO_AUTOFILL_FORMS: 8,
       AUTOCOMPLETE_OFF: 9,
       INSECURE: 10,
     };
 
+    function recordAutofillResult(result) {
+      if (userTriggered) {
+        // Ignore fills as a result of user action.
+        return;
+      }
+      const autofillResultHist = Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT");
+      autofillResultHist.add(result);
+    }
+
     try {
       // Nothing to do if we have no matching logins available,
       // and there isn't a need to show the insecure form warning.
       if (foundLogins.length == 0 &&
           (InsecurePasswordUtils.isFormSecure(form) ||
           !LoginHelper.showInsecureFieldWarning)) {
         // We don't log() here since this is a very common case.
-        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
+        recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
         return;
       }
 
       // Heuristically determine what the user/pass fields are
       // We do this before checking to see if logins are stored,
       // so that the user isn't prompted for a master password
       // without need.
       var [usernameField, passwordField, ignored] =
@@ -1048,24 +977,26 @@ var LoginManagerContent = {
         } else {
           throw new Error("Unexpected input element type.");
         }
       }
 
       // Need a valid password field to do anything.
       if (passwordField == null) {
         log("not filling form, no password field found");
-        autofillResult = AUTOFILL_RESULT.NO_PASSWORD_FIELD;
+        recordAutofillResult(AUTOFILL_RESULT.NO_PASSWORD_FIELD);
         return;
       }
 
+      this._formFillService.markAsLoginManagerField(passwordField);
+
       // If the password field is disabled or read-only, there's nothing to do.
       if (passwordField.disabled || passwordField.readOnly) {
         log("not filling form, password field disabled or read-only");
-        autofillResult = AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY;
+        recordAutofillResult(AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY);
         return;
       }
 
       // Attach autocomplete stuff to the username field, if we have
       // one. This is normally used to select from multiple accounts,
       // but even with one account we should refill if the user edits.
       // We would also need this attached to show the insecure login
       // warning, regardless of saved login.
@@ -1073,25 +1004,25 @@ var LoginManagerContent = {
         this._formFillService.markAsLoginManagerField(usernameField);
       }
 
       // Nothing to do if we have no matching logins available.
       // Only insecure pages reach this block and logs the same
       // telemetry flag.
       if (foundLogins.length == 0) {
         // We don't log() here since this is a very common case.
-        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
+        recordAutofillResult(AUTOFILL_RESULT.NO_SAVED_LOGINS);
         return;
       }
 
       // Prevent autofilling insecure forms.
       if (!userTriggered && !LoginHelper.insecureAutofill &&
           !InsecurePasswordUtils.isFormSecure(form)) {
         log("not filling form since it's insecure");
-        autofillResult = AUTOFILL_RESULT.INSECURE;
+        recordAutofillResult(AUTOFILL_RESULT.INSECURE);
         return;
       }
 
       var isAutocompleteOff = false;
       if (this._isAutocompleteDisabled(form) ||
           this._isAutocompleteDisabled(usernameField) ||
           this._isAutocompleteDisabled(passwordField)) {
         isAutocompleteOff = true;
@@ -1116,41 +1047,41 @@ var LoginManagerContent = {
         if (!fit)
           log("Ignored", l.username, "login: won't fit");
 
         return fit;
       }, this);
 
       if (logins.length == 0) {
         log("form not filled, none of the logins fit in the field");
-        autofillResult = AUTOFILL_RESULT.NO_LOGINS_FIT;
+        recordAutofillResult(AUTOFILL_RESULT.NO_LOGINS_FIT);
         return;
       }
 
       // Don't clobber an existing password.
       if (passwordField.value && !clobberPassword) {
         log("form not filled, the password field was already filled");
-        autofillResult = AUTOFILL_RESULT.EXISTING_PASSWORD;
+        recordAutofillResult(AUTOFILL_RESULT.EXISTING_PASSWORD);
         return;
       }
 
       // Select a login to use for filling in the form.
       var selectedLogin;
       if (!clobberUsername && usernameField && (usernameField.value ||
                                                 usernameField.disabled ||
                                                 usernameField.readOnly)) {
         // If username was specified in the field, it's disabled or it's readOnly, only fill in the
         // password if we find a matching login.
         var username = usernameField.value.toLowerCase();
 
         let matchingLogins = logins.filter(l =>
                                            l.username.toLowerCase() == username);
         if (matchingLogins.length == 0) {
           log("Password not filled. None of the stored logins match the username already present.");
-          autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
+          recordAutofillResult(AUTOFILL_RESULT.EXISTING_USERNAME);
           return;
         }
 
         // If there are multiple, and one matches case, use it
         for (let l of matchingLogins) {
           if (l.username == usernameField.value) {
             selectedLogin = l;
           }
@@ -1169,34 +1100,34 @@ var LoginManagerContent = {
         let matchingLogins;
         if (usernameField)
           matchingLogins = logins.filter(l => l.username);
         else
           matchingLogins = logins.filter(l => !l.username);
 
         if (matchingLogins.length != 1) {
           log("Multiple logins for form, so not filling any.");
-          autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
+          recordAutofillResult(AUTOFILL_RESULT.MULTIPLE_LOGINS);
           return;
         }
 
         selectedLogin = matchingLogins[0];
       }
 
       // We will always have a selectedLogin at this point.
 
       if (!autofillForm) {
         log("autofillForms=false but form can be filled");
-        autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
+        recordAutofillResult(AUTOFILL_RESULT.NO_AUTOFILL_FORMS);
         return;
       }
 
       if (isAutocompleteOff && !ignoreAutocomplete) {
         log("Not filling the login because we're respecting autocomplete=off");
-        autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
+        recordAutofillResult(AUTOFILL_RESULT.AUTOCOMPLETE_OFF);
         return;
       }
 
       // Fill the form
 
       if (usernameField) {
       // Don't modify the username field if it's disabled or readOnly so we preserve its case.
         let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
@@ -1207,114 +1138,32 @@ var LoginManagerContent = {
         // is desired.
         let userEnteredDifferentCase = userTriggered && userNameDiffers &&
                usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase();
 
         if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) {
           usernameField.setUserInput(selectedLogin.username);
         }
       }
-
-      let doc = form.ownerDocument;
       if (passwordField.value != selectedLogin.password) {
         passwordField.setUserInput(selectedLogin.password);
-        let autoFilledLogin = {
-          guid: selectedLogin.QueryInterface(Ci.nsILoginMetaInfo).guid,
-          username: selectedLogin.username,
-          usernameField: usernameField ? Cu.getWeakReference(usernameField) : null,
-          password: selectedLogin.password,
-          passwordField: Cu.getWeakReference(passwordField),
-        };
-        log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement);
-        this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin);
       }
 
       log("_fillForm succeeded");
-      autofillResult = AUTOFILL_RESULT.FILLED;
-
+      recordAutofillResult(AUTOFILL_RESULT.FILLED);
+      let doc = form.ownerDocument;
       let win = doc.defaultView;
       let messageManager = messageManagerFromWindow(win);
       messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
     } finally {
-      if (autofillResult == -1) {
-        // eslint-disable-next-line no-unsafe-finally
-        throw new Error("_fillForm: autofillResult must be specified");
-      }
-
-      if (!userTriggered) {
-        // Ignore fills as a result of user action for this probe.
-        Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT").add(autofillResult);
-
-        if (usernameField) {
-          let focusedElement = this._formFillService.focusedInput;
-          if (usernameField == focusedElement &&
-              autofillResult !== AUTOFILL_RESULT.FILLED) {
-            log("_fillForm: Opening username autocomplete popup since the form wasn't autofilled");
-            this._formFillService.showPopup();
-          }
-        }
-      }
-
-      if (usernameField) {
-        log("_fillForm: Attaching event listeners to usernameField");
-        usernameField.addEventListener("focus", observer);
-        usernameField.addEventListener("contextmenu", observer);
-      }
-
       Services.obs.notifyObservers(form.rootElement, "passwordmgr-processed-form", null);
     }
   },
 
   /**
-   * Given a field, determine whether that field was last filled as a username
-   * field AND whether the username is still filled in with the username AND
-   * whether the associated password field has the matching password.
-   *
-   * @note This could possibly be unified with getFieldContext but they have
-   * slightly different use cases. getFieldContext looks up recipes whereas this
-   * method doesn't need to since it's only returning a boolean based upon the
-   * recipes used for the last fill (in _fillForm).
-   *
-   * @param {HTMLInputElement} aUsernameField element contained in a FormLike
-   *                                          cached in _formLikeByRootElement.
-   * @returns {Boolean} whether the username and password fields still have the
-   *                    last-filled values, if previously filled.
-   */
-  _isLoginAlreadyFilled(aUsernameField) {
-    let formLikeRoot = FormLikeFactory.findRootForField(aUsernameField);
-    // Look for the existing FormLike.
-    let existingFormLike = this._formLikeByRootElement.get(formLikeRoot);
-    if (!existingFormLike) {
-      throw new Error("_isLoginAlreadyFilled called with a username field with " +
-                      "no rootElement FormLike");
-    }
-
-    log("_isLoginAlreadyFilled: existingFormLike", existingFormLike);
-    let filledLogin = this.stateForDocument(aUsernameField.ownerDocument).fillsByRootElement.get(formLikeRoot);
-    if (!filledLogin) {
-      return false;
-    }
-
-    // Unpack the weak references.
-    let autoFilledUsernameField = filledLogin.usernameField ? filledLogin.usernameField.get() : null;
-    let autoFilledPasswordField = filledLogin.passwordField.get();
-
-    // Check username and password values match what was filled.
-    if (!autoFilledUsernameField ||
-        autoFilledUsernameField != aUsernameField ||
-        autoFilledUsernameField.value != filledLogin.username ||
-        !autoFilledPasswordField ||
-        autoFilledPasswordField.value != filledLogin.password) {
-      return false;
-    }
-
-    return true;
-  },
-
-  /**
    * Verify if a field is a valid login form field and
    * returns some information about it's FormLike.
    *
    * @param {Element} aField
    *                  A form field we want to verify.
    *
    * @returns {Object} an object with information about the
    *                   FormLike username and password field
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -33,17 +33,16 @@ support-files =
 [browser_capture_doorhanger_httpsUpgrade.js]
 support-files =
   subtst_notifications_1.html
   subtst_notifications_8.html
 [browser_capture_doorhanger_window_open.js]
 support-files =
   subtst_notifications_11.html
   subtst_notifications_11_popup.html
-[browser_context_menu_autocomplete_interaction.js]
 [browser_username_select_dialog.js]
 support-files =
   subtst_notifications_change_p.html
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_exceptions_dialog.js]
 [browser_formless_submit_chrome.js]
 [browser_hasInsecureLoginForms.js]
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/browser/browser_context_menu_autocomplete_interaction.js
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Test the password manager context menu interaction with autocomplete.
- */
-
-"use strict";
-
-const TEST_HOSTNAME = "https://example.com";
-const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
-
-var gUnexpectedIsTODO = false;
-
-/**
- * Initialize logins needed for the tests and disable autofill
- * for login forms for easier testing of manual fill.
- */
-add_task(function* test_initialize() {
-  let autocompletePopup = document.getElementById("PopupAutoComplete");
-  Services.prefs.setBoolPref("signon.autofillForms", false);
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("signon.autofillForms");
-    autocompletePopup.removeEventListener("popupshowing", autocompleteUnexpectedPopupShowing);
-  });
-  for (let login of loginList()) {
-    Services.logins.addLogin(login);
-  }
-  autocompletePopup.addEventListener("popupshowing", autocompleteUnexpectedPopupShowing);
-});
-
-add_task(function* test_context_menu_username() {
-  yield BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: TEST_HOSTNAME + BASIC_FORM_PAGE_PATH,
-  }, function* (browser) {
-    yield openContextMenu(browser, "#form-basic-username");
-
-    let contextMenu = document.getElementById("contentAreaContextMenu");
-    Assert.equal(contextMenu.state, "open", "Context menu opened");
-    contextMenu.hidePopup();
-  });
-});
-
-add_task(function* test_context_menu_password() {
-  gUnexpectedIsTODO = true;
-  yield BrowserTestUtils.withNewTab({
-    gBrowser,
-    url: TEST_HOSTNAME + BASIC_FORM_PAGE_PATH,
-  }, function* (browser) {
-    yield openContextMenu(browser, "#form-basic-password");
-
-    let contextMenu = document.getElementById("contentAreaContextMenu");
-    Assert.equal(contextMenu.state, "open", "Context menu opened");
-    contextMenu.hidePopup();
-  });
-});
-
-function autocompleteUnexpectedPopupShowing(event) {
-  if (gUnexpectedIsTODO) {
-    todo(false, "Autocomplete shouldn't appear");
-  } else {
-    Assert.ok(false, "Autocomplete shouldn't appear");
-  }
-  event.target.hidePopup();
-}
-
-/**
- * Synthesize mouse clicks to open the context menu popup
- * for a target login input element.
- */
-function* openContextMenu(browser, loginInput) {
-  // First synthesize a mousedown. We need this to get the focus event with the "contextmenu" event.
-  let eventDetails1 = {type: "mousedown", button: 2};
-  BrowserTestUtils.synthesizeMouseAtCenter(loginInput, eventDetails1, browser);
-
-  // Then synthesize the contextmenu click over the input element.
-  let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
-  let eventDetails = {type: "contextmenu", button: 2};
-  BrowserTestUtils.synthesizeMouseAtCenter(loginInput, eventDetails, browser);
-  yield contextMenuShownPromise;
-
-  // Wait to see which popups are shown.
-  yield new Promise(resolve => setTimeout(resolve, 1000));
-}
-
-function loginList() {
-  return [
-    LoginTestUtils.testData.formLogin({
-      hostname: "https://example.com",
-      formSubmitURL: "https://example.com",
-      username: "username",
-      password: "password",
-    }),
-    LoginTestUtils.testData.formLogin({
-      hostname: "https://example.com",
-      formSubmitURL: "https://example.com",
-      username: "username2",
-      password: "password2",
-    }),
-  ];
-}
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -59,11 +59,9 @@ skip-if = os == "linux" || toolkit == 'a
 skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
 [test_prompt_noWindow.html]
 skip-if = e10s || toolkit == 'android' # Tests desktop prompts. e10s: bug 1217876
 [test_prompt_promptAuth.html]
 skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts
 [test_prompt_promptAuth_proxy.html]
 skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
 [test_recipe_login_fields.html]
-[test_username_focus.html]
-skip-if = toolkit == 'android' # android:autocomplete.
 [test_xhr_2.html]
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html
@@ -51,64 +51,38 @@ let iframeDoc;
 add_task(function* setup() {
   yield new Promise(resolve => {
     iframe.addEventListener("load", function() {
       resolve();
     }, {once: true});
   });
 
   iframeDoc = iframe.contentDocument;
-
-  SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur");
 });
 
 add_task(function* test_initial_focus() {
   let results = yield notifyMenuChanged(2, "name");
   checkArrayValues(results, ["name", "name1"], "Two results");
   doKey("down");
   doKey("return");
   yield promiseFormsProcessed();
   is(iframeDoc.getElementById("form-basic-password").value, "pass", "Check first password filled");
   let popupState = yield getPopupState();
   is(popupState.open, false, "Check popup is now closed");
 });
 
-// This depends on the filling from the previous test.
-add_task(function* test_not_reopened_if_filled() {
+add_task(function* test_not_reopened_after_selecting() {
+  let formFillController = SpecialPowers.Cc["@mozilla.org/satchel/form-fill-controller;1"].
+                           getService(SpecialPowers.Ci.nsIFormFillController);
+let usernameField = iframeDoc.getElementById("form-basic-username");
   listenForUnexpectedPopupShown();
-  let usernameField = iframeDoc.getElementById("form-basic-username");
-  usernameField.focus();
-  info("Waiting to see if a popupshown occurs");
+  formFillController.markAsLoginManagerField(usernameField);
+  SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur");
   yield new Promise(resolve => setTimeout(resolve, 1000));
 
   // cleanup
   gPopupShownExpected = true;
-  iframeDoc.getElementById("form-basic-submit").focus();
-});
-
-add_task(function* test_reopened_after_edit_not_matching_saved() {
-  let usernameField = iframeDoc.getElementById("form-basic-username");
-  usernameField.value = "nam";
-  let shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-  iframeDoc.getElementById("form-basic-submit").focus();
-});
-
-add_task(function* test_not_reopened_after_selecting() {
-  let formFillController = SpecialPowers.Cc["@mozilla.org/satchel/form-fill-controller;1"].
-                           getService(SpecialPowers.Ci.nsIFormFillController);
-  let usernameField = iframeDoc.getElementById("form-basic-username");
-  usernameField.value = "";
-  iframeDoc.getElementById("form-basic-password").value = "";
-  listenForUnexpectedPopupShown();
-  formFillController.markAsLoginManagerField(usernameField);
-  info("Waiting to see if a popupshown occurs");
-  yield new Promise(resolve => setTimeout(resolve, 1000));
-
-  // Cleanup
-  gPopupShownExpected = true;
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -828,19 +828,16 @@ add_task(function* test_form12_formless(
 });
 
 add_task(function* test_form12_open_on_trusted_focus() {
   uname = $_(12, "uname");
   pword = $_(12, "pword");
   uname.value = "";
   pword.value = "";
 
-  // Move focus to the password field so we can test the first click on the
-  // username field.
-  pword.focus();
   checkACForm("", "");
   const firePrivEventPromise = new Promise((resolve) => {
     uname.addEventListener("click", (e) => {
       ok(e.isTrusted, "Ensure event is trusted");
       resolve();
     });
   });
   const shownPromise = promiseACShown();
--- a/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
@@ -1,21 +1,22 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Test insecure form field autocomplete</title>
+  <title>Test basic login autocomplete</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="satchel_common.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
+Login Manager test: multiple login autocomplete
 
 <script>
 var chromeScript = runChecksAfterCommonInit();
 
 var setupScript = runInParent(function setup() {
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
   Cu.import("resource://gre/modules/Services.jsm");
 
deleted file mode 100644
--- a/toolkit/components/passwordmgr/test/mochitest/test_username_focus.html
+++ /dev/null
@@ -1,263 +0,0 @@
-
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test interaction between autocomplete and focus on username fields</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
-  <script type="text/javascript" src="satchel_common.js"></script>
-  <script type="text/javascript" src="pwmgr_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<script>
-let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
-
-let readyPromise = registerRunTests();
-let chromeScript = runInParent(function chromeSetup() {
-  const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
-  let pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
-
-  let login1A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                 createInstance(Ci.nsILoginInfo);
-  let login1B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                 createInstance(Ci.nsILoginInfo);
-  let login2A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                 createInstance(Ci.nsILoginInfo);
-  let login2B  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                 createInstance(Ci.nsILoginInfo);
-  let login2C  = Cc["@mozilla.org/login-manager/loginInfo;1"].
-                 createInstance(Ci.nsILoginInfo);
-
-  login1A.init("http://mochi.test:8888", "http://username-focus-1", null,
-               "testuser1A", "testpass1A", "", "");
-
-  login2A.init("http://mochi.test:8888", "http://username-focus-2", null,
-               "testuser2A", "testpass2A", "", "");
-  login2B.init("http://mochi.test:8888", "http://username-focus-2", null,
-               "testuser2B", "testpass2B", "", "");
-
-  pwmgr.addLogin(login1A);
-  pwmgr.addLogin(login2A);
-  pwmgr.addLogin(login2B);
-});
-</script>
-
-<p id="display"></p>
-<div id="content">
-  <!-- first 3 forms have a matching user+pass login -->
-
-  <!-- user+pass form. -->
-  <form id="form-autofilled" action="http://username-focus-1">
-    <input  type="text"     name="uname">
-    <input  type="password" name="pword">
-    <button type="submit" name="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form, username prefilled -->
-  <form id="form-autofilled-prefilled-un" action="http://username-focus-1">
-    <input  type="text"     name="uname" value="testuser1A">
-    <input  type="password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form. -->
-  <form id="form-autofilled-focused-dynamic" action="http://username-focus-1">
-    <input  type="text"             name="uname">
-    <input  type="not-yet-password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-
-  <!-- next 5 forms have matching user+pass (2x) logins -->
-
-  <!-- user+pass form. -->
-  <form id="form-multiple" action="http://username-focus-2">
-    <input  type="text"     name="uname">
-    <input  type="password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form dynamic with existing focus -->
-  <form id="form-multiple-dynamic" action="http://username-focus-2">
-    <input  type="text"             name="uname">
-    <input  type="not-yet-password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form, username prefilled -->
-  <form id="form-multiple-prefilled-un1" action="http://username-focus-2">
-    <input  type="text"     name="uname" value="testuser2A">
-    <input  type="password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form, different username prefilled -->
-  <form id="form-multiple-prefilled-un2" action="http://username-focus-2">
-    <input  type="text"     name="uname" value="testuser2B">
-    <input  type="password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-  <!-- user+pass form, username prefilled with existing focus -->
-  <form id="form-multiple-prefilled-focused-dynamic" action="http://username-focus-2">
-    <input  type="text"             name="uname" value="testuser2B">
-    <input  type="not-yet-password" name="pword">
-    <button type="submit">Submit</button>
-  </form>
-
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-function removeFocus() {
-  $_("-autofilled", "submit").focus();
-}
-
-add_task(function* setup() {
-  yield SpecialPowers.pushPrefEnv({"set": [
-    ["security.insecure_field_warning.contextual.enabled", false],
-  ]});
-
-  ok(readyPromise, "check promise is available");
-  yield readyPromise;
-});
-
-add_task(function* test_autofilled() {
-  let usernameField = $_("-autofilled", "uname");
-  info("Username and password already filled so don't show autocomplete");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-
-  removeFocus();
-  usernameField.value = "testuser";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* test_autofilled_prefilled_un() {
-  let usernameField = $_("-autofilled-prefilled-un", "uname");
-  info("Username and password already filled so don't show autocomplete");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-
-  removeFocus();
-  usernameField.value = "testuser";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* test_autofilled_focused_dynamic() {
-  let usernameField = $_("-autofilled-focused-dynamic", "uname");
-  let passwordField = $_("-autofilled-focused-dynamic", "pword");
-  info("Username and password will be filled while username focused");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-  info("triggering autofill");
-  noPopupPromise = promiseNoUnexpectedPopupShown();
-  passwordField.type = "password";
-  yield noPopupPromise;
-
-  let popupState = yield getPopupState();
-  is(popupState.open, false, "Check popup is closed");
-
-  removeFocus();
-  passwordField.value = "test";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-// Begin testing forms that have multiple saved logins
-
-add_task(function* test_multiple() {
-  let usernameField = $_("-multiple", "uname");
-  info("Fields not filled due to multiple so autocomplete upon focus");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* test_multiple_dynamic() {
-  let usernameField = $_("-multiple-dynamic", "uname");
-  let passwordField = $_("-multiple-dynamic", "pword");
-  info("Fields not filled but username is focused upon marking so open");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-
-  info("triggering _fillForm code");
-  let shownPromise = promiseACShown();
-  passwordField.type = "password";
-  yield shownPromise;
-});
-
-add_task(function* test_multiple_prefilled_un1() {
-  let usernameField = $_("-multiple-prefilled-un1", "uname");
-  info("Username and password already filled so don't show autocomplete");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-
-  removeFocus();
-  usernameField.value = "testuser";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* test_multiple_prefilled_un2() {
-  let usernameField = $_("-multiple-prefilled-un2", "uname");
-  info("Username and password already filled so don't show autocomplete");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-
-  removeFocus();
-  usernameField.value = "testuser";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* test_multiple_prefilled_focused_dynamic() {
-  let usernameField = $_("-multiple-prefilled-focused-dynamic", "uname");
-  let passwordField = $_("-multiple-prefilled-focused-dynamic", "pword");
-  info("Username and password will be filled while username focused");
-  let noPopupPromise = promiseNoUnexpectedPopupShown();
-  usernameField.focus();
-  yield noPopupPromise;
-  info("triggering autofill");
-  noPopupPromise = promiseNoUnexpectedPopupShown();
-  passwordField.type = "password";
-  yield noPopupPromise;
-
-  let popupState = yield getPopupState();
-  is(popupState.open, false, "Check popup is closed");
-
-  removeFocus();
-  passwordField.value = "test";
-  info("Focus when we don't have an exact match");
-  shownPromise = promiseACShown();
-  usernameField.focus();
-  yield shownPromise;
-});
-
-add_task(function* cleanup() {
-  removeFocus();
-});
-</script>
-</pre>
-</body>
-</html>
--- a/toolkit/components/passwordmgr/test/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/pwmgr_common.js
@@ -161,48 +161,45 @@ function commonInit(selfFilling) {
   if (this.sendAsyncMessage) {
     sendAsyncMessage("registerRunTests");
   } else {
     registerRunTests();
   }
 }
 
 function registerRunTests() {
-  return new Promise(resolve => {
-    // We provide a general mechanism for our tests to know when they can
-    // safely run: we add a final form that we know will be filled in, wait
-    // for the login manager to tell us that it's filled in and then continue
-    // with the rest of the tests.
-    window.addEventListener("DOMContentLoaded", (event) => {
-      var form = document.createElement("form");
-      form.id = "observerforcer";
-      var username = document.createElement("input");
-      username.name = "testuser";
-      form.appendChild(username);
-      var password = document.createElement("input");
-      password.name = "testpass";
-      password.type = "password";
-      form.appendChild(password);
+  // We provide a general mechanism for our tests to know when they can
+  // safely run: we add a final form that we know will be filled in, wait
+  // for the login manager to tell us that it's filled in and then continue
+  // with the rest of the tests.
+  window.addEventListener("DOMContentLoaded", (event) => {
+    var form = document.createElement("form");
+    form.id = "observerforcer";
+    var username = document.createElement("input");
+    username.name = "testuser";
+    form.appendChild(username);
+    var password = document.createElement("input");
+    password.name = "testpass";
+    password.type = "password";
+    form.appendChild(password);
 
-      var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
-        var formLikeRoot = subject.QueryInterface(SpecialPowers.Ci.nsIDOMNode);
-        if (formLikeRoot.id !== "observerforcer")
-          return;
-        SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
-        formLikeRoot.remove();
-        SimpleTest.executeSoon(() => {
-          var runTestEvent = new Event("runTests");
-          window.dispatchEvent(runTestEvent);
-          resolve();
-        });
+    var observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
+      var formLikeRoot = subject.QueryInterface(SpecialPowers.Ci.nsIDOMNode);
+      if (formLikeRoot.id !== "observerforcer")
+        return;
+      SpecialPowers.removeObserver(observer, "passwordmgr-processed-form");
+      formLikeRoot.remove();
+      SimpleTest.executeSoon(() => {
+        var runTestEvent = new Event("runTests");
+        window.dispatchEvent(runTestEvent);
       });
-      SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
+    });
+    SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false);
 
-      document.body.appendChild(form);
-    });
+    document.body.appendChild(form);
   });
 }
 
 const masterPassword = "omgsecret!";
 
 function enableMasterPassword() {
   setMasterPassword(true);
 }
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -286,16 +286,17 @@ nsFormFillController::MarkAsLoginManager
   nsFocusManager *fm = nsFocusManager::GetFocusManager();
   if (fm) {
     nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
     if (SameCOMIdentity(focusedContent, node)) {
       nsCOMPtr<nsIDOMHTMLInputElement> input = do_QueryInterface(node);
       if (!mFocusedInput) {
         MaybeStartControllingInput(input);
       }
+      ShowPopup();
     }
   }
 
   if (!mLoginManager)
     mLoginManager = do_GetService("@mozilla.org/login-manager;1");
 
   return NS_OK;
 }
@@ -311,19 +312,21 @@ nsFormFillController::MarkAsAutofillFiel
   NS_ENSURE_STATE(node);
   mAutofillInputs.Put(node, true);
   node->AddMutationObserverUnlessExists(this);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement **aInput) {
-  *aInput = mFocusedInput;
-  NS_IF_ADDREF(*aInput);
+nsFormFillController::GetFocusedInput(nsIDOMHTMLInputElement** aRetVal) {
+  if (!aRetVal) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+  *aRetVal = mFocusedInput;
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////
 //// nsIAutoCompleteInput
 
 NS_IMETHODIMP
 nsFormFillController::GetPopup(nsIAutoCompletePopup **aPopup)
@@ -694,37 +697,29 @@ nsFormFillController::GetUserContextId(u
 ////////////////////////////////////////////////////////////////////////
 //// nsIAutoCompleteSearch
 
 NS_IMETHODIMP
 nsFormFillController::StartSearch(const nsAString &aSearchString, const nsAString &aSearchParam,
                                   nsIAutoCompleteResult *aPreviousResult, nsIAutoCompleteObserver *aListener)
 {
   nsresult rv;
-  if (!mFocusedInputNode) {
-    return NS_ERROR_FAILURE;
-  }
-
   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
-  MOZ_ASSERT(formControl);
 
   // If the login manager has indicated it's responsible for this field, let it
   // handle the autocomplete. Otherwise, handle with form history.
-  if (mPwmgrInputs.Get(mFocusedInputNode) || formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+  if (mFocusedInputNode && (mPwmgrInputs.Get(mFocusedInputNode) ||
+                            formControl->GetType() == NS_FORM_INPUT_PASSWORD)) {
 
     // Handle the case where a password field is focused but
     // MarkAsLoginManagerField wasn't called because password manager is disabled.
     if (!mLoginManager) {
       mLoginManager = do_GetService("@mozilla.org/login-manager;1");
     }
 
-    if (NS_WARN_IF(!mLoginManager)) {
-      return NS_ERROR_FAILURE;
-    }
-
     // XXX aPreviousResult shouldn't ever be a historyResult type, since we're not letting
     // satchel manage the field?
     mLastListener = aListener;
     rv = mLoginManager->AutoCompleteSearchAsync(aSearchString,
                                                 aPreviousResult,
                                                 mFocusedInput,
                                                 this);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -992,20 +987,18 @@ nsFormFillController::MaybeStartControll
 
   bool autocomplete = nsContentUtils::IsAutocompleteEnabled(aInput);
 
   nsCOMPtr<nsIDOMHTMLElement> datalist;
   aInput->GetList(getter_AddRefs(datalist));
   bool hasList = datalist != nullptr;
 
   bool isPwmgrInput = false;
-  if (mPwmgrInputs.Get(inputNode) ||
-      formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
-    isPwmgrInput = true;
-  }
+  if (mPwmgrInputs.Get(inputNode))
+      isPwmgrInput = true;
 
   if (isPwmgrInput || hasList || autocomplete) {
     StartControllingInput(aInput);
   }
 }
 
 nsresult
 nsFormFillController::Focus(nsIDOMEvent* aEvent)
@@ -1015,27 +1008,29 @@ nsFormFillController::Focus(nsIDOMEvent*
   MaybeStartControllingInput(input);
 
   // Bail if we didn't start controlling the input.
   if (!mFocusedInputNode) {
     mContextMenuFiredBeforeFocus = false;
     return NS_OK;
   }
 
-#ifndef ANDROID
   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(mFocusedInputNode);
   MOZ_ASSERT(formControl);
 
   // If this focus doesn't immediately follow a contextmenu event then show
-  // the autocomplete popup for all password fields.
-  if (!mContextMenuFiredBeforeFocus
-      && formControl->GetType() == NS_FORM_INPUT_PASSWORD) {
+  // the autocomplete popup
+  if (!mContextMenuFiredBeforeFocus &&
+      (mPwmgrInputs.Get(mFocusedInputNode)
+#ifndef ANDROID
+       || formControl->GetType() == NS_FORM_INPUT_PASSWORD
+#endif
+       )) {
     ShowPopup();
   }
-#endif
 
   mContextMenuFiredBeforeFocus = false;
   return NS_OK;
 }
 
 nsresult
 nsFormFillController::KeyPress(nsIDOMEvent* aEvent)
 {
@@ -1163,17 +1158,17 @@ nsFormFillController::MouseDown(nsIDOMEv
   int16_t button;
   mouseEvent->GetButton(&button);
   if (button != 0)
     return NS_OK;
 
   return ShowPopup();
 }
 
-NS_IMETHODIMP
+nsresult
 nsFormFillController::ShowPopup()
 {
   bool isOpen = false;
   GetPopupOpen(&isOpen);
   if (isOpen) {
     return SetPopupOpen(false);
   }
 
--- a/toolkit/components/satchel/nsFormFillController.h
+++ b/toolkit/components/satchel/nsFormFillController.h
@@ -72,16 +72,17 @@ protected:
    */
   void MaybeStartControllingInput(nsIDOMHTMLInputElement* aElement);
 
   nsresult PerformInputListAutoComplete(const nsAString& aSearch,
                                         nsIAutoCompleteResult** aResult);
 
   void RevalidateDataList();
   bool RowMatch(nsFormHistory *aHistory, uint32_t aIndex, const nsAString &aInputName, const nsAString &aInputValue);
+  nsresult ShowPopup();
 
   inline nsIDocShell *GetDocShellForInput(nsIDOMHTMLInputElement *aInput);
   inline nsPIDOMWindowOuter *GetWindowForDocShell(nsIDocShell *aDocShell);
   inline int32_t GetIndexOfDocShell(nsIDocShell *aDocShell);
 
   void MaybeRemoveMutationObserver(nsINode* aNode);
 
   void RemoveForDocument(nsIDocument* aDoc);
--- a/toolkit/components/satchel/nsIFormFillController.idl
+++ b/toolkit/components/satchel/nsIFormFillController.idl
@@ -16,21 +16,16 @@ interface nsIDOMHTMLInputElement;
  * is focused.  When this happens, the input will be bound to the
  * global nsIAutoCompleteController service.
  */
 
 [scriptable, uuid(07f0a0dc-f6e9-4cdd-a55f-56d770523a4c)]
 interface nsIFormFillController : nsISupports
 {
   /*
-   * The input element the form fill controller is currently bound to.
-   */
-  readonly attribute nsIDOMHTMLInputElement focusedInput;
-
-  /*
    * Start controlling form fill behavior for the given browser
    *
    * @param docShell - The docShell to attach to
    * @param popup - The popup to show when autocomplete results are available
    */
   void attachToBrowser(in nsIDocShell docShell, in nsIAutoCompletePopup popup);
 
   /*
@@ -52,13 +47,15 @@ interface nsIFormFillController : nsISup
   /*
    * Mark the specified <input> element as being managed by a form autofill component.
    * Autocomplete requests will be handed off to the autofill component.
    *
    * @param aInput - The HTML <input> element to mark
    */
   void markAsAutofillField(in nsIDOMHTMLInputElement aInput);
 
-  /*
-   * Open the autocomplete popup, if possible.
+  /**
+   * Return the focused input which is cached in form fill controller.
+   *
+   * @returns The focused input.
    */
-  void showPopup();
+  nsIDOMHTMLInputElement getFocusedInput();
 };
--- a/toolkit/components/satchel/test/satchel_common.js
+++ b/toolkit/components/satchel/test/satchel_common.js
@@ -224,51 +224,44 @@ function getPopupState(then = null) {
         then(state);
       }
       resolve(state);
     });
   });
 }
 
 function listenForUnexpectedPopupShown() {
-  gPopupShownListener = function onPopupShown() {
+  gChromeScript.addMessageListener("onpopupshown", function onPopupShown() {
     if (!gPopupShownExpected) {
       ok(false, "Unexpected autocomplete popupshown event");
     }
-  };
-}
-
-function* promiseNoUnexpectedPopupShown() {
-  gPopupShownExpected = false;
-  listenForUnexpectedPopupShown();
-  SimpleTest.requestFlakyTimeout("Giving a chance for an unexpected popupshown to occur");
-  yield new Promise(resolve => setTimeout(resolve, 1000));
+  });
 }
 
 /**
  * Resolve at the next popupshown event for the autocomplete popup
  * @return {Promise} with the results
  */
 function promiseACShown() {
   gPopupShownExpected = true;
   return new Promise(resolve => {
-    gPopupShownListener = ({ results }) => {
+    gChromeScript.addMessageListener("onpopupshown", ({ results }) => {
       gPopupShownExpected = false;
       resolve(results);
-    };
+    });
   });
 }
 
 function satchelCommonSetup() {
   var chromeURL = SimpleTest.getTestFileURL("parent_utils.js");
   gChromeScript = SpecialPowers.loadChromeScript(chromeURL);
   gChromeScript.addMessageListener("onpopupshown", ({ results }) => {
     gLastAutoCompleteResults = results;
     if (gPopupShownListener)
-      gPopupShownListener({results});
+      gPopupShownListener();
   });
 
   SimpleTest.registerCleanupFunction(() => {
     gChromeScript.sendAsyncMessage("cleanup");
     gChromeScript.destroy();
   });
 }
 
--- a/toolkit/modules/FormLikeFactory.jsm
+++ b/toolkit/modules/FormLikeFactory.jsm
@@ -63,61 +63,42 @@ let FormLikeFactory = {
    * @throws Error if aField isn't a password or username field in a document
    */
   createFromField(aField) {
     if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
         !aField.ownerDocument) {
       throw new Error("createFromField requires a field in a document");
     }
 
-    let rootElement = this.findRootForField(aField);
-    if (rootElement instanceof Ci.nsIDOMHTMLFormElement) {
-      return this.createFromForm(rootElement);
+    if (aField.form) {
+      return this.createFromForm(aField.form);
     }
 
     let doc = aField.ownerDocument;
     let elements = [];
-    for (let el of rootElement.querySelectorAll("input")) {
-      // Exclude elements inside the rootElement that are already in a <form> as
-      // they will be handled by their own FormLike.
+    for (let el of doc.documentElement.querySelectorAll("input")) {
       if (!el.form) {
         elements.push(el);
       }
     }
     let formLike = {
       action: doc.baseURI,
       autocomplete: "on",
+      // Exclude elements inside the rootElement that are already in a <form> as
+      // they will be handled by their own FormLike.
       elements,
       ownerDocument: doc,
-      rootElement,
+      rootElement: doc.documentElement,
     };
 
     this._addToJSONProperty(formLike);
     return formLike;
   },
 
   /**
-   * Determine the Element that encapsulates the related fields. For example, if
-   * a page contains a login form and a checkout form which are "submitted"
-   * separately, and the username field is passed in, ideally this would return
-   * an ancestor Element of the username and password fields which doesn't
-   * include any of the checkout fields.
-   *
-   * @param {HTMLInputElement} aField - a field in a document
-   * @return {HTMLElement} - the root element surrounding related fields
-   */
-  findRootForField(aField) {
-    if (aField.form) {
-      return aField.form;
-    }
-
-    return aField.ownerDocument.documentElement;
-  },
-
-  /**
    * Add a `toJSON` property to a FormLike so logging which ends up going
    * through dump doesn't include usless garbage from DOM objects.
    */
   _addToJSONProperty(aFormLike) {
     function prettyElementOutput(aElement) {
       let idText = aElement.id ? "#" + aElement.id : "";
       let classText = "";
       for (let className of aElement.classList) {