--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -427,16 +427,20 @@ 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;
},
/**
@@ -895,34 +899,38 @@ 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 {HTMLFormElement} form
+ * @param {LoginForm} 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} = {}) {
+ userTriggered, foundLogins, recipes, {inputElement} = {}) {
+ if (form instanceof Ci.nsIDOMHTMLFormElement) {
+ throw new Error("_fillForm should only be called with FormLike objects");
+ }
+
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,
@@ -1129,23 +1137,34 @@ 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;
- 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");
}
@@ -1155,16 +1174,62 @@ var LoginManagerContent = {
Services.telemetry.getHistogramById("PWMGR_FORM_AUTOFILL_RESULT").add(autofillResult);
}
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