forked from mirrors/gecko-dev
Bug 1893623 - P1. Trigger autofill from the parent process r=credential-management-reviewers,sgalich
Currently, when users autocomplete a field for an address, credit card, or login, Firefox also "autofills" the relevant fields. Here is a quick summary of how we currently manage this process: 1. Users click on an input field, the autocomplete popup is displayed, and Firefox searches for options so users can choose which value to autocomplete. 2. AutoCompleteChild searches for the value to autocomplete based on the type of the input field, along with the entire profile. For example, when we autocomplete a cc-number field, we also send cc-name, cc-exp, etc., to the child process. 3. AutoCompleteController autocompletes the focused input. 4. AutoCompleteController notifies the corresponding module, which then autofills the remaining fields. Currently, step 4 is triggered directly in the child process. This patch moves the logic of step 4 from the child process to the parent process. This change is a prerequisite for supporting autofill across frames and will also enable us not to send the entire profile in step 2. Differential Revision: https://phabricator.services.mozilla.com/D208752
This commit is contained in:
parent
58465b2927
commit
a6fc3d1c7d
13 changed files with 224 additions and 124 deletions
|
|
@ -141,6 +141,7 @@ export class AutoCompleteChild extends JSWindowActorChild {
|
|||
dir,
|
||||
inputElementIdentifier,
|
||||
formOrigin,
|
||||
actorName: this.lastAutoCompleteProviderName,
|
||||
});
|
||||
|
||||
this._input = input;
|
||||
|
|
@ -308,7 +309,7 @@ export class AutoCompleteChild extends JSWindowActorChild {
|
|||
|
||||
for (const provider of providers) {
|
||||
// Search result could be empty. However, an autocomplete provider might
|
||||
// want to show an autoclmplete popup when there is no search result. For example,
|
||||
// want to show an autocomplete popup when there is no search result. For example,
|
||||
// <datalist> for FormHisotry, insecure warning for LoginManager.
|
||||
const searchResult = result.find(r => r.actorName == provider.actorName);
|
||||
const acResult = provider.searchResultToAutoCompleteResult(
|
||||
|
|
@ -320,6 +321,10 @@ export class AutoCompleteChild extends JSWindowActorChild {
|
|||
// We have not yet supported showing autocomplete entries from multiple providers,
|
||||
// Note: The prioty is defined in AutoCompleteParent.
|
||||
if (acResult) {
|
||||
// `lastAutoCompleteProviderName` should be removed once we implement
|
||||
// the mapping of autocomplete entry to provider in the parent process.
|
||||
this.lastAutoCompleteProviderName = provider.actorName;
|
||||
|
||||
this.lastProfileAutoCompleteResult = acResult;
|
||||
listener.onSearchCompletion(acResult);
|
||||
return;
|
||||
|
|
@ -332,6 +337,12 @@ export class AutoCompleteChild extends JSWindowActorChild {
|
|||
this.lastProfileAutoCompleteResult = null;
|
||||
this.#ongoingSearches.clear();
|
||||
}
|
||||
|
||||
selectEntry() {
|
||||
// we don't need to pass the selected index to the parent process because
|
||||
// the selected index is maintained in the parent.
|
||||
this.sendAsyncMessage("AutoComplete:SelectEntry");
|
||||
}
|
||||
}
|
||||
|
||||
AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
|
||||
|
|
|
|||
|
|
@ -143,9 +143,13 @@ var AutoCompleteResultView = {
|
|||
this.results = [];
|
||||
},
|
||||
|
||||
setResults(actor, results) {
|
||||
setResults(actor, results, providerActorName) {
|
||||
this.currentActor = actor;
|
||||
this.results = results;
|
||||
|
||||
if (providerActorName) {
|
||||
this.providerActor = actor.manager.getActor(providerActorName);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -203,7 +207,7 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
}
|
||||
}
|
||||
|
||||
showPopupWithResults({ rect, dir, results }) {
|
||||
showPopupWithResults({ rect, dir, results, actorName }) {
|
||||
if (!results.length || this.openedPopup) {
|
||||
// We shouldn't ever be showing an empty popup, and if we
|
||||
// already have a popup open, the old one needs to close before
|
||||
|
|
@ -237,7 +241,8 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
);
|
||||
this.openedPopup.style.direction = dir;
|
||||
|
||||
AutoCompleteResultView.setResults(this, results);
|
||||
AutoCompleteResultView.setResults(this, results, actorName);
|
||||
|
||||
this.openedPopup.view = AutoCompleteResultView;
|
||||
this.openedPopup.selectedIndex = -1;
|
||||
|
||||
|
|
@ -394,6 +399,13 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
}
|
||||
|
||||
switch (message.name) {
|
||||
case "AutoComplete:SelectEntry": {
|
||||
if (this.openedPopup) {
|
||||
this.autofillProfile(this.openedPopup.selectedIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "AutoComplete:SetSelectedIndex": {
|
||||
let { index } = message.data;
|
||||
if (this.openedPopup) {
|
||||
|
|
@ -403,8 +415,14 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
}
|
||||
|
||||
case "AutoComplete:MaybeOpenPopup": {
|
||||
let { results, rect, dir, inputElementIdentifier, formOrigin } =
|
||||
message.data;
|
||||
let {
|
||||
results,
|
||||
rect,
|
||||
dir,
|
||||
inputElementIdentifier,
|
||||
formOrigin,
|
||||
actorName,
|
||||
} = message.data;
|
||||
if (lazy.DELEGATE_AUTOCOMPLETE) {
|
||||
lazy.GeckoViewAutocomplete.delegateSelection({
|
||||
browsingContext: this.browsingContext,
|
||||
|
|
@ -413,7 +431,7 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
formOrigin,
|
||||
});
|
||||
} else {
|
||||
this.showPopupWithResults({ results, rect, dir });
|
||||
this.showPopupWithResults({ results, rect, dir, actorName });
|
||||
this.notifyListeners();
|
||||
}
|
||||
break;
|
||||
|
|
@ -437,7 +455,7 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
case "AutoComplete:StartSearch": {
|
||||
const { searchString, data } = message.data;
|
||||
const result = await this.#startSearch(searchString, data);
|
||||
return result;
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
// Returning false to pacify ESLint, but this return value is
|
||||
|
|
@ -540,6 +558,36 @@ export class AutoCompleteParent extends JSWindowActorParent {
|
|||
|
||||
stopSearch() {}
|
||||
|
||||
previewAutofillProfile(index) {
|
||||
const actor = AutoCompleteResultView.providerActor;
|
||||
if (!actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear preview when the selected index is not valid
|
||||
if (index < 0) {
|
||||
actor.previewFields(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = AutoCompleteResultView.results[index];
|
||||
actor.previewFields(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a field is autocompleted, fill relevant fields
|
||||
*/
|
||||
autofillProfile(index) {
|
||||
// Find the provider of this autocomplete
|
||||
const actor = AutoCompleteResultView.providerActor;
|
||||
if (index < 0 || !actor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = AutoCompleteResultView.results[index];
|
||||
actor.autofillFields(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the browser that is requesting the input
|
||||
* that the open popup should be focused.
|
||||
|
|
|
|||
|
|
@ -1235,6 +1235,8 @@ nsresult nsAutoCompleteController::EnterMatch(bool aIsPopupSelection,
|
|||
SetSearchStringInternal(value);
|
||||
}
|
||||
|
||||
popup->SelectEntry();
|
||||
|
||||
obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
|
||||
|
||||
input->OnTextEntered(aEvent);
|
||||
|
|
|
|||
|
|
@ -92,4 +92,8 @@ interface nsIAutoCompletePopup : nsISupports
|
|||
*/
|
||||
void stopSearch();
|
||||
|
||||
/**
|
||||
* Notify the autocomplete popup that an autocomplete entry is selected.
|
||||
*/
|
||||
void selectEntry();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -338,13 +338,9 @@ export const ProfileAutocomplete = {
|
|||
// The observer notification is for autocomplete in a different process.
|
||||
break;
|
||||
}
|
||||
lazy.FormAutofillContent.autofillPending = true;
|
||||
Services.obs.notifyObservers(null, "autofill-fill-starting");
|
||||
await this._fillFromAutocompleteRow(
|
||||
lazy.FormAutofillContent.activeInput
|
||||
);
|
||||
Services.obs.notifyObservers(null, "autofill-fill-complete");
|
||||
lazy.FormAutofillContent.autofillPending = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -415,10 +411,7 @@ export const ProfileAutocomplete = {
|
|||
);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await lazy.FormAutofillContent.activeHandler.autofillFormFields(profile);
|
||||
},
|
||||
|
||||
_clearProfilePreview() {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,9 @@ const observer = {
|
|||
* Handles content's interactions for the frame.
|
||||
*/
|
||||
export class FormAutofillChild extends JSWindowActorChild {
|
||||
// Flag indicating whether the form is waiting to be filled by Autofill.
|
||||
#autofillPending = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
|
@ -109,9 +112,6 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
this._hasDOMContentLoadedHandler = false;
|
||||
this._hasPendingTask = false;
|
||||
|
||||
// Flag indicating whether the form is waiting to be filled by Autofill.
|
||||
this._autofillPending = false;
|
||||
|
||||
/**
|
||||
* @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
|
||||
*/
|
||||
|
|
@ -478,14 +478,14 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
this.unregisterProgressListener();
|
||||
}
|
||||
|
||||
receiveMessage(message) {
|
||||
async receiveMessage(message) {
|
||||
if (!lazy.FormAutofill.isAutofillEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.name) {
|
||||
case "FormAutofill:PreviewProfile": {
|
||||
this.previewProfile(message.data.selectedIndex);
|
||||
this.previewProfile(message.data);
|
||||
break;
|
||||
}
|
||||
case "FormAutofill:ClearForm": {
|
||||
|
|
@ -493,7 +493,7 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
break;
|
||||
}
|
||||
case "FormAutofill:FillForm": {
|
||||
this.activeHandler.autofillFormFields(message.data);
|
||||
await this.autofillFields(message.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -615,7 +615,7 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
this.debug("updateActiveElement: checking for popup-on-focus");
|
||||
// We know this element just received focus. If it's a credit card field,
|
||||
// open its popup.
|
||||
if (this._autofillPending) {
|
||||
if (this.#autofillPending) {
|
||||
this.debug("updateActiveElement: skipping check; autofill is imminent");
|
||||
} else if (element.value?.length !== 0) {
|
||||
this.debug(
|
||||
|
|
@ -636,11 +636,6 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
}
|
||||
}
|
||||
|
||||
set autofillPending(flag) {
|
||||
this.debug("Setting autofillPending to", flag);
|
||||
this._autofillPending = flag;
|
||||
}
|
||||
|
||||
clearForm() {
|
||||
let focusedInput =
|
||||
this.activeInput ||
|
||||
|
|
@ -670,22 +665,32 @@ export class FormAutofillChild extends JSWindowActorChild {
|
|||
?.lastProfileAutoCompleteFocusedInput;
|
||||
}
|
||||
|
||||
previewProfile(selectedIndex) {
|
||||
if (
|
||||
selectedIndex === -1 ||
|
||||
!this.activeInput ||
|
||||
this.lastProfileAutoCompleteResult?.getStyleAt(selectedIndex) !=
|
||||
"autofill"
|
||||
) {
|
||||
lazy.ProfileAutocomplete._clearProfilePreview();
|
||||
previewProfile(profile) {
|
||||
if (profile && this.activeSection) {
|
||||
const adaptedProfile = this.activeSection.getAdaptedProfiles([
|
||||
profile,
|
||||
])[0];
|
||||
this.activeSection.previewFormFields(adaptedProfile);
|
||||
} else {
|
||||
lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
|
||||
this.activeSection.clearPreviewedFormFields();
|
||||
}
|
||||
}
|
||||
|
||||
async autofillFields(profile) {
|
||||
this.#autofillPending = true;
|
||||
Services.obs.notifyObservers(null, "autofill-fill-starting");
|
||||
try {
|
||||
Services.obs.notifyObservers(null, "autofill-fill-starting");
|
||||
await this.activeHandler.autofillFormFields(profile);
|
||||
Services.obs.notifyObservers(null, "autofill-fill-complete");
|
||||
} finally {
|
||||
this.#autofillPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
onPopupClosed() {
|
||||
this.debug("Popup has closed.");
|
||||
lazy.ProfileAutocomplete._clearProfilePreview();
|
||||
this.activeSection?.clearPreviewedFormFields();
|
||||
}
|
||||
|
||||
onPopupOpened() {
|
||||
|
|
|
|||
|
|
@ -696,4 +696,25 @@ export class FormAutofillParent extends JSWindowActorParent {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
previewFields(result) {
|
||||
try {
|
||||
const profile =
|
||||
result.style == "autofill" ? JSON.parse(result.comment) : null;
|
||||
this.sendAsyncMessage("FormAutofill:PreviewProfile", profile);
|
||||
} catch (e) {
|
||||
lazy.log.debug("Fail to get preview profile: ", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
autofillFields(result) {
|
||||
if (result.style == "autofill") {
|
||||
try {
|
||||
const profile = JSON.parse(result.comment);
|
||||
this.sendAsyncMessage("FormAutofill:FillForm", profile);
|
||||
} catch (e) {
|
||||
lazy.log.debug("Fail to get autofill profile.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -413,6 +413,7 @@ export class FormAutofillSection {
|
|||
profile[`${fieldDetail.fieldName}-formatted`] ||
|
||||
profile[fieldDetail.fieldName] ||
|
||||
"";
|
||||
|
||||
if (HTMLSelectElement.isInstance(element)) {
|
||||
// Unlike text input, select element is always previewed even if
|
||||
// the option is already selected.
|
||||
|
|
|
|||
|
|
@ -159,40 +159,6 @@ const observer = {
|
|||
loginManagerChild()._onNavigation(window.document);
|
||||
},
|
||||
|
||||
// nsIObserver
|
||||
observe(subject, topic, _data) {
|
||||
switch (topic) {
|
||||
case "autocomplete-did-enter-text": {
|
||||
let input = subject.QueryInterface(Ci.nsIAutoCompleteInput);
|
||||
let { selectedIndex } = input.popup;
|
||||
if (selectedIndex < 0 || selectedIndex >= input.controller.matchCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
let { focusedInput } = lazy.gFormFillService;
|
||||
if (focusedInput.nodePrincipal.isNullPrincipal) {
|
||||
// If we have a null principal then prevent any more password manager code from running and
|
||||
// incorrectly using the document `location`.
|
||||
return;
|
||||
}
|
||||
|
||||
let window = focusedInput.ownerGlobal;
|
||||
let loginManagerChild = LoginManagerChild.forWindow(window);
|
||||
|
||||
let style = input.controller.getStyleAt(selectedIndex);
|
||||
if (style == "login" || style == "loginWithOrigin") {
|
||||
let details = JSON.parse(
|
||||
input.controller.getCommentAt(selectedIndex)
|
||||
);
|
||||
loginManagerChild.onFieldAutoComplete(focusedInput, details.guid);
|
||||
} else if (style == "generatedPassword") {
|
||||
loginManagerChild._filledWithGeneratedPassword(focusedInput);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// nsIDOMEventListener
|
||||
handleEvent(aEvent) {
|
||||
if (!aEvent.isTrusted) {
|
||||
|
|
@ -437,9 +403,6 @@ const observer = {
|
|||
},
|
||||
};
|
||||
|
||||
// Add this observer once for the process.
|
||||
Services.obs.addObserver(observer, "autocomplete-did-enter-text");
|
||||
|
||||
/**
|
||||
* Form scenario defines what can be done with form.
|
||||
*/
|
||||
|
|
@ -1529,6 +1492,16 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
this.notifyObserversOfFormProcessed(msg.data.formid);
|
||||
break;
|
||||
}
|
||||
case "PasswordManager:fillFields": {
|
||||
const login = lazy.LoginHelper.vanillaObjectToLogin(msg.data);
|
||||
this.fillFields(login);
|
||||
break;
|
||||
}
|
||||
case "PasswordManager:fillGeneratedPassword": {
|
||||
const { focusedInput } = lazy.gFormFillService;
|
||||
this.filledWithGeneratedPassword(focusedInput);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
@ -2178,7 +2151,7 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
/**
|
||||
* A username or password was autocompleted into a field.
|
||||
*/
|
||||
onFieldAutoComplete(acInputField, loginGUID) {
|
||||
onFieldAutoComplete(acInputField, login) {
|
||||
if (!lazy.LoginHelper.enabled) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -2193,7 +2166,7 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
}
|
||||
|
||||
if (lazy.LoginHelper.isUsernameFieldType(acInputField)) {
|
||||
this.onUsernameAutocompleted(acInputField, loginGUID);
|
||||
this.onUsernameAutocompleted(acInputField, [login]);
|
||||
} else if (acInputField.hasBeenTypePassword) {
|
||||
// Ensure the field gets re-masked and edits don't overwrite the generated
|
||||
// password in case a generated password was filled into it previously.
|
||||
|
|
@ -2207,7 +2180,7 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
* A username field was filled or tabbed away from so try fill in the
|
||||
* associated password in the password field.
|
||||
*/
|
||||
onUsernameAutocompleted(acInputField, loginGUID = null) {
|
||||
async onUsernameAutocompleted(acInputField, loginsFound = null) {
|
||||
lazy.log(`Autocompleting input field with name: ${acInputField.name}`);
|
||||
|
||||
let acForm = lazy.LoginFormFactory.createFromField(acInputField);
|
||||
|
|
@ -2223,43 +2196,49 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
const docState = this.stateForDocument(acInputField.ownerDocument);
|
||||
let { usernameField, newPasswordField: passwordField } =
|
||||
docState._getFormFields(acForm, false, recipes);
|
||||
if (usernameField == acInputField) {
|
||||
// Fill the form when a password field is present.
|
||||
if (passwordField) {
|
||||
this._getLoginDataFromParent(acForm, {
|
||||
guid: loginGUID,
|
||||
showPrimaryPassword: false,
|
||||
})
|
||||
.then(({ form, loginsFound, recipes }) => {
|
||||
if (!loginGUID) {
|
||||
// not an explicit autocomplete menu selection, filter for exact matches only
|
||||
loginsFound = this._filterForExactFormOriginLogins(
|
||||
loginsFound,
|
||||
acForm
|
||||
);
|
||||
// filter the list for exact matches with the username
|
||||
// NOTE: this could be an empty string which is a valid username
|
||||
let searchString = usernameField.value.toLowerCase();
|
||||
loginsFound = loginsFound.filter(
|
||||
l => l.username.toLowerCase() == searchString
|
||||
);
|
||||
}
|
||||
// Ignore the event, it's for some input we don't care about.
|
||||
if (usernameField != acInputField) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fillForm(form, loginsFound, recipes, {
|
||||
autofillForm: true,
|
||||
clobberPassword: true,
|
||||
userTriggered: true,
|
||||
});
|
||||
})
|
||||
.catch(console.error);
|
||||
// Use `loginGUID !== null` to distinguish whether this is called when the
|
||||
// field is filled or tabbed away from. For the latter, don't highlight the field.
|
||||
} else if (loginGUID !== null) {
|
||||
if (!passwordField) {
|
||||
// Use `loginsFound !== null` to distinguish whether this is called when the
|
||||
// field is filled or tabbed away from. For the latter, don't highlight the field.
|
||||
if (loginsFound !== null) {
|
||||
LoginFormState._highlightFilledField(usernameField);
|
||||
}
|
||||
} else {
|
||||
// Ignore the event, it's for some input we don't care about.
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill the form when a password field is present.
|
||||
if (!loginsFound) {
|
||||
const loginData = await this._getLoginDataFromParent(acForm, {
|
||||
showPrimaryPassword: false,
|
||||
}).catch(console.error);
|
||||
|
||||
if (!loginData?.loginsFound.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// not an explicit autocomplete menu selection, filter for exact matches only
|
||||
loginsFound = this._filterForExactFormOriginLogins(
|
||||
loginData.loginsFound,
|
||||
acForm
|
||||
);
|
||||
// filter the list for exact matches with the username
|
||||
// NOTE: this could be an empty string which is a valid username
|
||||
const searchString = usernameField.value.toLowerCase();
|
||||
loginsFound = loginsFound.filter(
|
||||
l => l.username.toLowerCase() == searchString
|
||||
);
|
||||
recipes = loginData.recipes;
|
||||
}
|
||||
|
||||
this._fillForm(acForm, loginsFound, recipes, {
|
||||
autofillForm: true,
|
||||
clobberPassword: true,
|
||||
userTriggered: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2643,7 +2622,7 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
* field is handled accordingly.
|
||||
* @param {HTMLInputElement} passwordField
|
||||
*/
|
||||
_filledWithGeneratedPassword(passwordField) {
|
||||
filledWithGeneratedPassword(passwordField) {
|
||||
LoginFormState._highlightFilledField(passwordField);
|
||||
this._passwordEditedOrGenerated(passwordField, {
|
||||
triggeredByFillingGenerated: true,
|
||||
|
|
@ -3120,7 +3099,7 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
}
|
||||
|
||||
if (style === "generatedPassword") {
|
||||
this._filledWithGeneratedPassword(passwordField);
|
||||
this.filledWithGeneratedPassword(passwordField);
|
||||
}
|
||||
|
||||
lazy.log("_fillForm succeeded");
|
||||
|
|
@ -3367,4 +3346,9 @@ export class LoginManagerChild extends JSWindowActorChild {
|
|||
#isWebAuthnCredentials(autocompleteInfo) {
|
||||
return autocompleteInfo.credentialType == "webauthn";
|
||||
}
|
||||
|
||||
fillFields(login) {
|
||||
let { focusedInput } = lazy.gFormFillService;
|
||||
this.onFieldAutoComplete(focusedInput, login);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1549,6 +1549,23 @@ export class LoginManagerParent extends JSWindowActorParent {
|
|||
async searchAutoCompleteEntries(searchString, data) {
|
||||
return this.doAutocompleteSearch(data.formOrigin, data);
|
||||
}
|
||||
|
||||
previewFields(_result) {
|
||||
// Logins do not show previews
|
||||
}
|
||||
|
||||
autofillFields(result) {
|
||||
if (result.style == "login" || result.style == "loginWithOrigin") {
|
||||
try {
|
||||
const profile = JSON.parse(result.comment);
|
||||
this.sendAsyncMessage("PasswordManager:fillFields", profile.login);
|
||||
} catch (e) {
|
||||
lazy.log("Fail to get autofill profile: ", e.message);
|
||||
}
|
||||
} else if (result.style == "generatedPassword") {
|
||||
this.sendAsyncMessage("PasswordManager:fillGeneratedPassword");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS = 10000;
|
||||
|
|
|
|||
|
|
@ -146,21 +146,29 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
"resetLoginsAndGeneratedPasswords", () => {
|
||||
LoginTestUtils.clearData();
|
||||
LoginTestUtils.resetGeneratedPasswordsCache();
|
||||
return Promise.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
function resetLoginsAndGeneratedPasswords() {
|
||||
return setupScript.sendAsyncMessage("resetLoginsAndGeneratedPasswords");
|
||||
return setupScript.sendQuery("resetLoginsAndGeneratedPasswords");
|
||||
}
|
||||
|
||||
async function triggerPasswordGeneration(form) {
|
||||
await openPopupOn(form.pword);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
synthesizeKey("KEY_Enter");
|
||||
|
||||
const storageAddPromise = promiseStorageChanged(["addLogin"]);
|
||||
await SimpleTest.promiseWaitForCondition(() => !!form.pword.value, "Wait for generated password to get filled");
|
||||
await storageAddPromise;
|
||||
}
|
||||
|
||||
add_setup(async () => {
|
||||
SpecialPowers.pushPrefEnv({"set": [["signon.webauthn.autocomplete", false]]});
|
||||
})
|
||||
|
||||
add_named_task("autocomplete menu contains option to generate password", async () => {
|
||||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form1);
|
||||
|
|
@ -206,7 +214,7 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form1);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pword.value == form.pwordNext.value, "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("password field is not masked initially after password generation", async () => {
|
||||
|
|
@ -270,7 +278,7 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
form.pword.blur();
|
||||
await messageSentPromise;
|
||||
|
||||
is(form.pwordNext.value, generatedPassword, "Value of the confirm field still holds the original generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == generatedPassword, "Value of the confirm field still holds the original generated password");
|
||||
ok(form.pwordNext.matches(":autofill"), "Highlight is still applied to password confirmation field");
|
||||
});
|
||||
|
||||
|
|
@ -314,21 +322,21 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
form.pwordNext.focus()
|
||||
sendString("edited value");
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, "edited value", "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == "edited value", "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("password confirmation does not get filled with the generated password if its readonly", async () => {
|
||||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form3);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, "", "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == "", "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("password confirmation does not get filled with the generated password if its disabled", async () => {
|
||||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form4);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, "", "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == "", "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("password confirmation matching autocomplete info gets filled with the generated password", async () => {
|
||||
|
|
@ -336,14 +344,14 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
const form = setContentForTask(formTemplates.form5);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordBetween.value, "", "Value of the between field has not been filled");
|
||||
is(form.pwordNext.value, form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("password confirmation matching autocomplete info gets ignored if its disabled, even if has autocomplete info", async () => {
|
||||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form6);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
is(form.pwordAfter.value, "", "Value of the disabled confirmation field has not been filled");
|
||||
});
|
||||
|
||||
|
|
@ -365,7 +373,7 @@ Login Manager test: filling generated passwords into confirm password fields
|
|||
await resetLoginsAndGeneratedPasswords();
|
||||
const form = setContentForTask(formTemplates.form9);
|
||||
await triggerPasswordGeneration(form);
|
||||
is(form.pwordNext.value, form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
await SimpleTest.promiseWaitForCondition(() => form.pwordNext.value == form.pword.value, "Value of the confirm field has been filled with generated password");
|
||||
});
|
||||
|
||||
add_named_task("do not fill third password field after the confirm-password field", async () => {
|
||||
|
|
|
|||
|
|
@ -205,4 +205,12 @@ export class FormHistoryParent extends JSWindowActorParent {
|
|||
}
|
||||
entry.totalScore = Math.round(entry.frecency * Math.max(1, boundaryCalc));
|
||||
}
|
||||
|
||||
previewFields(_result) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
autofillFields(_result) {
|
||||
// Not implemented
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -832,9 +832,7 @@
|
|||
|
||||
setTimeout(() => {
|
||||
let selectedIndex = popup ? popup.selectedIndex : -1;
|
||||
actor.manager
|
||||
.getActor("FormAutofill")
|
||||
.sendAsyncMessage("FormAutofill:PreviewProfile", { selectedIndex });
|
||||
actor.previewAutofillProfile(selectedIndex);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue