diff options
Diffstat (limited to 'toolkit/components/formautofill/FormAutofillChild.sys.mjs')
-rw-r--r-- | toolkit/components/formautofill/FormAutofillChild.sys.mjs | 217 |
1 files changed, 185 insertions, 32 deletions
diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs index af84459432..f45c962d2b 100644 --- a/toolkit/components/formautofill/FormAutofillChild.sys.mjs +++ b/toolkit/components/formautofill/FormAutofillChild.sys.mjs @@ -8,15 +8,18 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + AddressResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs", AutoCompleteChild: "resource://gre/actors/AutoCompleteChild.sys.mjs", AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs", + CreditCardResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs", + GenericAutocompleteItem: "resource://gre/modules/FillHelpers.sys.mjs", + InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.sys.mjs", FormAutofill: "resource://autofill/FormAutofill.sys.mjs", FormAutofillContent: "resource://autofill/FormAutofillContent.sys.mjs", FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs", + FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs", FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", - ProfileAutocomplete: - "resource://autofill/AutofillProfileAutoComplete.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", FORM_SUBMISSION_REASON: "resource://gre/actors/FormHandlerChild.sys.mjs", }); @@ -94,6 +97,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(); @@ -104,9 +110,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. */ @@ -473,14 +476,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": { @@ -488,7 +491,7 @@ export class FormAutofillChild extends JSWindowActorChild { break; } case "FormAutofill:FillForm": { - this.activeHandler.autofillFormFields(message.data); + await this.autofillFields(message.data); break; } } @@ -610,7 +613,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( @@ -631,16 +634,8 @@ export class FormAutofillChild extends JSWindowActorChild { } } - set autofillPending(flag) { - this.debug("Setting autofillPending to", flag); - this._autofillPending = flag; - } - clearForm() { - let focusedInput = - this.activeInput || - lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput; - if (!focusedInput) { + if (!this.activeSection) { return; } @@ -656,26 +651,41 @@ export class FormAutofillChild extends JSWindowActorChild { } } - previewProfile(selectedIndex) { - let lastAutoCompleteResult = - lazy.ProfileAutocomplete.lastProfileAutoCompleteResult; - let focusedInput = this.activeInput; + get lastProfileAutoCompleteResult() { + return this.manager.getActor("AutoComplete")?.lastProfileAutoCompleteResult; + } - if ( - selectedIndex === -1 || - !focusedInput || - !lastAutoCompleteResult || - lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill" - ) { - lazy.ProfileAutocomplete._clearProfilePreview(); + get lastProfileAutoCompleteFocusedInput() { + return this.manager.getActor("AutoComplete") + ?.lastProfileAutoCompleteFocusedInput; + } + + 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() { @@ -701,6 +711,149 @@ export class FormAutofillChild extends JSWindowActorChild { return; } - formFillController.markAsAutofillField(field); + this.manager + .getActor("AutoComplete") + ?.markAsAutoCompletableField(field, this); + } + + get actorName() { + return "FormAutofill"; + } + + /** + * Get the search options when searching for autocomplete entries in the parent + * + * @param {HTMLInputElement} input - The input element to search for autocompelte entries + * @returns {object} the search options for the input + */ + getAutoCompleteSearchOption(input) { + const fieldDetail = this._fieldDetailsManager + ._getFormHandler(input) + ?.getFieldDetailByElement(input); + + const scenarioName = lazy.FormScenarios.detect({ input }).signUpForm + ? "SignUpFormScenario" + : ""; + return { fieldName: fieldDetail?.fieldName, scenarioName }; + } + + /** + * Ask the provider whether it might have autocomplete entry to show + * for the given input. + * + * @param {HTMLInputElement} input - The input element to search for autocompelte entries + * @returns {boolean} true if we shold search for autocomplete entries + */ + shouldSearchForAutoComplete(input) { + const fieldDetail = this._fieldDetailsManager + ._getFormHandler(input) + ?.getFieldDetailByElement(input); + if (!fieldDetail) { + return false; + } + const fieldName = fieldDetail.fieldName; + const isAddressField = lazy.FormAutofillUtils.isAddressField(fieldName); + const searchPermitted = isAddressField + ? lazy.FormAutofill.isAutofillAddressesEnabled + : lazy.FormAutofill.isAutofillCreditCardsEnabled; + // If the specified autofill feature is pref off, do not search + if (!searchPermitted) { + return false; + } + + // No profile can fill the currently-focused input. + if (!lazy.FormAutofillContent.savedFieldNames.has(fieldName)) { + return false; + } + + // The current form has already been populated and the field is not + // an empty credit card field. + const isCreditCardField = + lazy.FormAutofillUtils.isCreditCardField(fieldName); + const isInputAutofilled = + this.activeHandler.getFilledStateByElement(input) == + lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED; + const filledRecordGUID = this.activeSection.filledRecordGUID; + if ( + !isInputAutofilled && + filledRecordGUID && + !(isCreditCardField && this.activeInput.value === "") + ) { + return false; + } + + // (address only) less than 3 inputs are covered by all saved fields in the storage. + if ( + isAddressField && + this.activeSection.allFieldNames.filter(field => + lazy.FormAutofillContent.savedFieldNames.has(field) + ).length < lazy.FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD + ) { + return false; + } + + return true; + } + + /** + * Convert the search result to autocomplete results + * + * @param {string} searchString - The string to search for + * @param {HTMLInputElement} input - The input element to search for autocompelte entries + * @param {Array<object>} records - autocomplete records + * @returns {AutocompleteResult} + */ + searchResultToAutoCompleteResult(searchString, input, records) { + if (!records) { + return null; + } + + const entries = records.records; + const externalEntries = records.externalEntries; + + const fieldDetail = this._fieldDetailsManager + ._getFormHandler(input) + ?.getFieldDetailByElement(input); + if (!fieldDetail) { + return null; + } + + const adaptedRecords = this.activeSection.getAdaptedProfiles(entries); + const isSecure = lazy.InsecurePasswordUtils.isFormSecure( + this.activeHandler.form + ); + const isInputAutofilled = + this.activeHandler.getFilledStateByElement(input) == + lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED; + const allFieldNames = this.activeSection.allFieldNames; + + const AutocompleteResult = lazy.FormAutofillUtils.isAddressField( + fieldDetail.fieldName + ) + ? lazy.AddressResult + : lazy.CreditCardResult; + + const acResult = new AutocompleteResult( + searchString, + fieldDetail.fieldName, + allFieldNames, + adaptedRecords, + { isSecure, isInputAutofilled } + ); + + acResult.externalEntries.push( + ...externalEntries.map( + entry => + new lazy.GenericAutocompleteItem( + entry.image, + entry.title, + entry.subtitle, + entry.fillMessageName, + entry.fillMessageData + ) + ) + ); + + return acResult; } } |