diff options
Diffstat (limited to 'toolkit/components/formautofill/FormAutofillContent.sys.mjs')
-rw-r--r-- | toolkit/components/formautofill/FormAutofillContent.sys.mjs | 367 |
1 files changed, 27 insertions, 340 deletions
diff --git a/toolkit/components/formautofill/FormAutofillContent.sys.mjs b/toolkit/components/formautofill/FormAutofillContent.sys.mjs index 133e5e1d0a..c07e0d67b3 100644 --- a/toolkit/components/formautofill/FormAutofillContent.sys.mjs +++ b/toolkit/components/formautofill/FormAutofillContent.sys.mjs @@ -8,43 +8,18 @@ /* eslint-disable no-use-before-define */ -import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; -import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; - const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FormAutofill: "resource://autofill/FormAutofill.sys.mjs", - FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs", - PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", - FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs", ProfileAutocomplete: "resource://autofill/AutofillProfileAutoComplete.sys.mjs", - AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs", }); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, - "DELEGATE_AUTOCOMPLETE", - "toolkit.autocomplete.delegate", - false -); - const formFillController = Cc[ "@mozilla.org/satchel/form-fill-controller;1" ].getService(Ci.nsIFormFillController); -function getActorFromWindow(contentWindow, name = "FormAutofill") { - // In unit tests, contentWindow isn't a real window. - if (!contentWindow) { - return null; - } - - return contentWindow.windowGlobalChild - ? contentWindow.windowGlobalChild.getActor(name) - : null; -} - /** * Handles content's interactions for the process. * @@ -63,12 +38,6 @@ export var FormAutofillContent = { */ _popupPending: false, - /** - * @type {boolean} Flag indicating whether the form is waiting to be - * filled by Autofill. - */ - _autofillPending: false, - init() { this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillContent"); this.debug("init"); @@ -76,11 +45,13 @@ export var FormAutofillContent = { // eslint-disable-next-line mozilla/balanced-listeners Services.cpmm.sharedData.addEventListener("change", this); - let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled"); + const autofillEnabled = Services.cpmm.sharedData.get( + "FormAutofill:enabled" + ); // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure // autocomplete is registered before the focusin so register it in this case as long as the // pref is true. - let shouldEnableAutofill = + const shouldEnableAutofill = autofillEnabled === undefined && (lazy.FormAutofill.isAutofillAddressesEnabled || lazy.FormAutofill.isAutofillCreditCardsEnabled); @@ -88,120 +59,49 @@ export var FormAutofillContent = { lazy.ProfileAutocomplete.ensureRegistered(); } - /** - * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers. - */ - this._fieldDetailsManager = new lazy.FormStateManager( - this.formSubmitted.bind(this), - this._showPopup.bind(this) - ); + this.activeAutofillChild = null; }, get activeFieldDetail() { - return this._fieldDetailsManager.activeFieldDetail; + return this.activeAutofillChild?.activeFieldDetail; }, get activeFormDetails() { - return this._fieldDetailsManager.activeFormDetails; + return this.activeAutofillChild?.activeFormDetails; }, get activeInput() { - return this._fieldDetailsManager.activeInput; + return this.activeAutofillChild?.activeInput; }, get activeHandler() { - return this._fieldDetailsManager.activeHandler; + return this.activeAutofillChild?.activeHandler; }, get activeSection() { - return this._fieldDetailsManager.activeSection; - }, - - /** - * Send the profile to parent for doorhanger and storage saving/updating. - * - * @param {object} profile Submitted form's address/creditcard guid and record. - * @param {object} domWin Current content window. - */ - _onFormSubmit(profile, domWin) { - let actor = getActorFromWindow(domWin); - actor.sendAsyncMessage("FormAutofill:OnFormSubmit", profile); + return this.activeAutofillChild?.activeSection; }, - /** - * Handle a form submission and early return when: - * 1. In private browsing mode. - * 2. Could not map any autofill handler by form element. - * 3. Number of filled fields is less than autofill threshold - * - * @param {HTMLElement} formElement Root element which receives submit event. - * @param {string} formSubmissionReason Reason for invoking the form submission - * (see options for FORM_SUBMISSION_REASON in FormAutofillUtils)) - * @param {Window} domWin Content window; passed for unit tests and when - * invoked by the FormAutofillSection - * @param {object} handler FormAutofillHander, if known by caller - */ - formSubmitted( - formElement, - formSubmissionReason, - domWin = formElement.ownerGlobal, - handler = undefined - ) { - this.debug(`Handling form submission - infered by ${formSubmissionReason}`); - - // Unregister the progress listener since we detected a form submission - // (domWin is null in unit tests) - getActorFromWindow(domWin)?.unregisterProgressListener(); - - lazy.AutofillTelemetry.recordFormSubmissionHeuristicCount( - formSubmissionReason - ); - - if (!lazy.FormAutofill.isAutofillEnabled) { - this.debug("Form Autofill is disabled"); - return; - } - - // The `domWin` truthiness test is used by unit tests to bypass this check. - if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) { - this.debug("Ignoring submission in a private window"); - return; - } - - handler = handler || this._fieldDetailsManager._getFormHandler(formElement); - const records = this._fieldDetailsManager.getRecords(formElement, handler); - - if (!records || !handler) { - this.debug("Form element could not map to an existing handler"); - return; + set autofillPending(flag) { + if (this.activeAutofillChild) { + this.activeAutofillChild.autofillPending = flag; } + }, - [records.address, records.creditCard].forEach((rs, idx) => { - lazy.AutofillTelemetry.recordSubmittedSectionCount( - idx == 0 - ? lazy.AutofillTelemetry.ADDRESS - : lazy.AutofillTelemetry.CREDIT_CARD, - rs?.length - ); - - rs?.forEach(r => { - lazy.AutofillTelemetry.recordFormInteractionEvent( - "submitted", - r.section, - { - record: r, - form: handler.form, - } - ); - delete r.section; - }); - }); - - this._onFormSubmit(records, domWin); + updateActiveAutofillChild(autofillChild) { + this.activeAutofillChild = autofillChild; }, - _showPopup() { - formFillController.showPopup(); + showPopup() { + if (Services.cpmm.sharedData.get("FormAutofill:enabled")) { + this.debug("updateActiveElement: opening pop up"); + formFillController.showPopup(); + } else { + this.debug( + "updateActiveElement: Deferring pop-up until Autofill is ready" + ); + this._popupPending = true; + } }, handleEvent(evt) { @@ -215,7 +115,7 @@ export var FormAutofillContent = { if (this._popupPending) { this._popupPending = false; this.debug("handleEvent: Opening deferred popup"); - this._showPopup(); + formFillController.showPopup(); } } else { lazy.ProfileAutocomplete.ensureUnregistered(); @@ -224,219 +124,6 @@ export var FormAutofillContent = { } } }, - - /** - * All active items should be updated according the active element of - * `formFillController.focusedInput`. All of them including element, - * handler, section, and field detail, can be retrieved by their own getters. - * - * @param {HTMLElement|null} element The active item should be updated based - * on this or `formFillController.focusedInput` will be taken. - */ - updateActiveInput(element) { - element = element || formFillController.focusedInput; - if (!element) { - this.debug("updateActiveElement: no element selected"); - return; - } - this._fieldDetailsManager.updateActiveInput(element); - 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) { - this.debug("updateActiveElement: skipping check; autofill is imminent"); - } else if (element.value?.length !== 0) { - this.debug( - `updateActiveElement: Not opening popup because field is not empty.` - ); - } else { - this.debug( - "updateActiveElement: checking if empty field is cc-*: ", - this.activeFieldDetail?.fieldName - ); - - if ( - this.activeFieldDetail?.fieldName?.startsWith("cc-") || - AppConstants.platform === "android" - ) { - if (Services.cpmm.sharedData.get("FormAutofill:enabled")) { - this.debug("updateActiveElement: opening pop up"); - this._showPopup(); - } else { - this.debug( - "updateActiveElement: Deferring pop-up until Autofill is ready" - ); - this._popupPending = true; - } - } - } - }, - - set autofillPending(flag) { - this.debug("Setting autofillPending to", flag); - this._autofillPending = flag; - }, - - /** - * Identifies and marks each autofill field - * - * @param {HTMLElement} element - * Element that serves as an anchor for the formautofill heuristics to retrieve - * the root form and run the formautofill heuristics on the form elements - * @returns {boolean} - * whether any autofill fields were identified - */ - identifyAutofillFields(element) { - this.debug( - `identifyAutofillFields: ${element.ownerDocument.location?.hostname}` - ); - - if (lazy.DELEGATE_AUTOCOMPLETE || !this.savedFieldNames) { - this.debug("identifyAutofillFields: savedFieldNames are not known yet"); - let actor = getActorFromWindow(element.ownerGlobal); - if (actor) { - actor.sendAsyncMessage("FormAutofill:InitStorage"); - } - } - - const validDetails = - this._fieldDetailsManager.identifyAutofillFields(element); - - validDetails?.forEach(detail => this._markAsAutofillField(detail.element)); - - return !!validDetails.length; - }, - - clearForm() { - let focusedInput = - this.activeInput || - lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput; - if (!focusedInput) { - return; - } - - this.activeSection.clearPopulatedForm(); - - let fieldName = FormAutofillContent.activeFieldDetail?.fieldName; - if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) { - lazy.AutofillTelemetry.recordFormInteractionEvent( - "cleared", - this.activeSection, - { fieldName } - ); - } - }, - - previewProfile(doc) { - let docWin = doc.ownerGlobal; - let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin); - let lastAutoCompleteResult = - lazy.ProfileAutocomplete.lastProfileAutoCompleteResult; - let focusedInput = this.activeInput; - let actor = getActorFromWindow(docWin); - - if ( - selectedIndex === -1 || - !focusedInput || - !lastAutoCompleteResult || - lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" - ) { - actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {}); - - lazy.ProfileAutocomplete._clearProfilePreview(); - } else { - let focusedInputDetails = this.activeFieldDetail; - let profile = JSON.parse( - lastAutoCompleteResult.getCommentAt(selectedIndex) - ); - let allFieldNames = FormAutofillContent.activeSection.allFieldNames; - let profileFields = allFieldNames.filter( - fieldName => !!profile[fieldName] - ); - - let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName( - focusedInputDetails.fieldName - ); - let categories = - lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields); - actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", { - focusedCategory, - categories, - }); - - lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex); - } - }, - - onPopupClosed(selectedRowStyle) { - this.debug("Popup has closed."); - lazy.ProfileAutocomplete._clearProfilePreview(); - - let lastAutoCompleteResult = - lazy.ProfileAutocomplete.lastProfileAutoCompleteResult; - let focusedInput = FormAutofillContent.activeInput; - if ( - lastAutoCompleteResult && - FormAutofillContent._keyDownEnterForInput && - focusedInput === FormAutofillContent._keyDownEnterForInput && - focusedInput === - lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput - ) { - if (selectedRowStyle == "autofill-footer") { - let actor = getActorFromWindow(focusedInput.ownerGlobal); - actor.sendAsyncMessage("FormAutofill:OpenPreferences"); - } else if (selectedRowStyle == "autofill-clear-button") { - FormAutofillContent.clearForm(); - } - } - }, - - onPopupOpened() { - this.debug( - "Popup has opened, automatic =", - formFillController.passwordPopupAutomaticallyOpened - ); - - let fieldName = FormAutofillContent.activeFieldDetail?.fieldName; - if (fieldName && this.activeSection) { - lazy.AutofillTelemetry.recordFormInteractionEvent( - "popup_shown", - this.activeSection, - { fieldName } - ); - } - }, - - _markAsAutofillField(field) { - // Since Form Autofill popup is only for input element, any non-Input - // element should be excluded here. - if (!HTMLInputElement.isInstance(field)) { - return; - } - - formFillController.markAsAutofillField(field); - }, - - _onKeyDown(e) { - delete FormAutofillContent._keyDownEnterForInput; - let lastAutoCompleteResult = - lazy.ProfileAutocomplete.lastProfileAutoCompleteResult; - let focusedInput = FormAutofillContent.activeInput; - if ( - e.keyCode != e.DOM_VK_RETURN || - !lastAutoCompleteResult || - !focusedInput || - focusedInput != - lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput - ) { - return; - } - FormAutofillContent._keyDownEnterForInput = focusedInput; - }, - - didDestroy() { - this._fieldDetailsManager.didDestroy(); - }, }; FormAutofillContent.init(); |