From d1f68d0d791edb511ae0a0a6fe94866e95a4b6c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:30:20 +0000 Subject: [PATCH] Refactor - auto update credential provider script --- .../AddressMetaDataExtension.sys.mjs | 3 + .../Assets/CC_Script/AddressParser.sys.mjs | 36 + .../Assets/CC_Script/AddressRecord.sys.mjs | 20 + .../CC_Script/AutofillFormFactory.sys.mjs | 4 + .../CC_Script/AutofillTelemetry.sys.mjs | 47 - .../Client/Assets/CC_Script/Constants.ios.mjs | 3 +- .../Assets/CC_Script/CreditCard.sys.mjs | 3 +- .../Assets/CC_Script/CreditCardRecord.sys.mjs | 45 + .../Assets/CC_Script/FieldScanner.sys.mjs | 75 +- .../Assets/CC_Script/FormAutofill.sys.mjs | 45 +- .../CC_Script/FormAutofillHandler.sys.mjs | 1036 +++++++++++------ .../CC_Script/FormAutofillHeuristics.sys.mjs | 241 ++-- .../CC_Script/FormAutofillSection.sys.mjs | 225 ++-- .../CC_Script/FormAutofillUtils.sys.mjs | 255 ++-- .../Assets/CC_Script/FormLikeFactory.sys.mjs | 4 +- .../Client/Assets/CC_Script/Helpers.ios.mjs | 17 +- .../Assets/CC_Script/HeuristicsRegExp.sys.mjs | 89 +- .../Assets/CC_Script/LabelUtils.sys.mjs | 210 ++-- .../CC_Script/LoginManager.shared.sys.mjs | 116 +- .../Assets/CC_Script/NewPasswordModel.sys.mjs | 3 + .../Client/Assets/CC_Script/Overrides.ios.js | 3 - .../CC_Script/PasswordGenerator.sys.mjs | 5 +- .../Assets/CC_Script/PhoneNumber.sys.mjs | 23 + 23 files changed, 1556 insertions(+), 952 deletions(-) diff --git a/firefox-ios/Client/Assets/CC_Script/AddressMetaDataExtension.sys.mjs b/firefox-ios/Client/Assets/CC_Script/AddressMetaDataExtension.sys.mjs index f4dca18dde145..ef5bdcd2d7349 100644 --- a/firefox-ios/Client/Assets/CC_Script/AddressMetaDataExtension.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/AddressMetaDataExtension.sys.mjs @@ -342,6 +342,8 @@ export const AddressMetaDataExtension = { }, "data/JP": { alpha_3_code: "JPN", + fmt: "〒%Z%n%S%C%n%A%n%O%n%N", + require: "ACSZ", }, "data/JE": { alpha_3_code: "JEY", @@ -474,6 +476,7 @@ export const AddressMetaDataExtension = { }, "data/NL": { alpha_3_code: "NLD", + address_reversed: true, }, "data/NC": { alpha_3_code: "NCL", diff --git a/firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs b/firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs index e3d4c0e6cde00..daadcfdef22e1 100644 --- a/firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs @@ -313,4 +313,40 @@ export class AddressParser { static mergeWhitespace(s) { return s?.replace(/\s{2,}/g, " "); } + + // This is quite fragile, but handles a number of basic cases. This is intended + // to split the street number suffix from the street number. For example: + // 35B will be split into number=35,suffix=B + // Returns an array with [housenumber, suffix] + static parseHouseSuffix(address, structuredAddress) { + let streetNumber = structuredAddress?.street_number; + if (!streetNumber) { + return null; + } + + let numberIndex = address.indexOf(streetNumber); + if (numberIndex < 0) { + return [streetNumber]; + } + + let match = streetNumber.match(/^(\d+)(\w?)/); + if (!match) { + return [streetNumber]; + } + + let suffix; + let result = [match[1]]; + // If the house number is after the street, include the rest of the address + // as part of the suffix, otherwise just include the suffix on the number. + if (numberIndex > structuredAddress?.street_name.length) { + suffix = address.substring(numberIndex + match[1].length).trim(); + } else { + suffix = match[2]; + } + if (suffix) { + result.push(suffix.replace("\n", " ")); + } + + return result; + } } diff --git a/firefox-ios/Client/Assets/CC_Script/AddressRecord.sys.mjs b/firefox-ios/Client/Assets/CC_Script/AddressRecord.sys.mjs index 599a802dcd130..313b27cedc8da 100644 --- a/firefox-ios/Client/Assets/CC_Script/AddressRecord.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/AddressRecord.sys.mjs @@ -7,6 +7,7 @@ import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofil import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs"; import { PhoneNumber } from "resource://gre/modules/shared/PhoneNumber.sys.mjs"; import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs"; +import { AddressParser } from "resource://gre/modules/shared/AddressParser.sys.mjs"; /** * The AddressRecord class serves to handle and normalize internal address records. @@ -32,6 +33,7 @@ export class AddressRecord { static computeFields(address) { this.#computeNameFields(address); this.#computeAddressLineFields(address); + this.#computeStreetAndHouseNumberFields(address); this.#computeCountryFields(address); this.#computeTelFields(address); } @@ -66,6 +68,24 @@ export class AddressRecord { } } + static #computeStreetAndHouseNumberFields(address) { + if (!("address-housenumber" in address) && "street-address" in address) { + let streetAddress = address["street-address"]; + let parsedAddress = AddressParser.parseStreetAddress(streetAddress); + if (parsedAddress) { + address["address-housenumber"] = parsedAddress.street_number; + + let splitNumber = AddressParser.parseHouseSuffix( + streetAddress, + parsedAddress + ); + if (splitNumber?.length >= 2) { + address["address-extra-housesuffix"] = splitNumber[1]; + } + } + } + } + static #computeCountryFields(address) { // Compute country name if (!("country-name" in address)) { diff --git a/firefox-ios/Client/Assets/CC_Script/AutofillFormFactory.sys.mjs b/firefox-ios/Client/Assets/CC_Script/AutofillFormFactory.sys.mjs index 68db143640172..450e8e96fad29 100644 --- a/firefox-ios/Client/Assets/CC_Script/AutofillFormFactory.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/AutofillFormFactory.sys.mjs @@ -39,4 +39,8 @@ export const AutofillFormFactory = { } return lazy.FormLikeFactory.createFromField(aField, { ignoreForm }); }, + + createFromDocumentRoot(aDocRoot) { + return lazy.FormLikeFactory.createFromDocumentRoot(aDocRoot); + }, }; diff --git a/firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs b/firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs index 9825aeacfe845..15e4f952342d5 100644 --- a/firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/AutofillTelemetry.sys.mjs @@ -12,10 +12,6 @@ class AutofillTelemetryBase { EVENT_CATEGORY = null; EVENT_OBJECT_FORM_INTERACTION = null; - HISTOGRAM_NUM_USES = null; - HISTOGRAM_PROFILE_NUM_USES = null; - HISTOGRAM_PROFILE_NUM_USES_KEY = null; - #initFormEventExtra(value) { let extra = {}; for (const field of Object.values(this.SUPPORTED_FIELDS)) { @@ -183,17 +179,6 @@ class AutofillTelemetryBase { throw new Error("Not implemented."); } - recordNumberOfUse(records) { - let histogram = Services.telemetry.getKeyedHistogramById( - this.HISTOGRAM_PROFILE_NUM_USES - ); - histogram.clear(); - - for (let record of records) { - histogram.add(this.HISTOGRAM_PROFILE_NUM_USES_KEY, record.timesUsed); - } - } - recordIframeLayoutDetection(flowId, fieldDetails) { const fieldsInMainFrame = []; const fieldsInIframe = []; @@ -238,9 +223,6 @@ export class AddressTelemetry extends AutofillTelemetryBase { EVENT_OBJECT_FORM_INTERACTION = "AddressForm"; EVENT_OBJECT_FORM_INTERACTION_EXT = "AddressFormExt"; - HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES"; - HISTOGRAM_PROFILE_NUM_USES_KEY = "address"; - // Fields that are recorded in `address_form` and `address_form_ext` telemetry SUPPORTED_FIELDS = { "street-address": "street_address", @@ -316,10 +298,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase { EVENT_CATEGORY = "creditcard"; EVENT_OBJECT_FORM_INTERACTION = "CcFormV2"; - HISTOGRAM_NUM_USES = "CREDITCARD_NUM_USES"; - HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES"; - HISTOGRAM_PROFILE_NUM_USES_KEY = "credit_card"; - // Mapping of field name used in formautofill code to the field name // used in the telemetry. SUPPORTED_FIELDS = { @@ -369,23 +347,6 @@ class CreditCardTelemetry extends AutofillTelemetryBase { } } - recordNumberOfUse(records) { - super.recordNumberOfUse(records); - - if (!this.HISTOGRAM_NUM_USES) { - return; - } - - let histogram = Services.telemetry.getHistogramById( - this.HISTOGRAM_NUM_USES - ); - histogram.clear(); - - for (let record of records) { - histogram.add(record.timesUsed); - } - } - recordAutofillProfileCount(count) { Glean.formautofillCreditcards.autofillProfilesCount.set(count); } @@ -463,14 +424,6 @@ export class AutofillTelemetry { telemetry.recordAutofillProfileCount(count); } - /** - * Utility functions for address/credit card number of use - */ - static recordNumberOfUse(type, records) { - const telemetry = this.#getTelemetryByType(type); - telemetry.recordNumberOfUse(records); - } - static recordFormSubmissionHeuristicCount(label) { Glean.formautofill.formSubmissionHeuristic[label].add(1); } diff --git a/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs b/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs index 1281ac259f914..cd73ef59d48e1 100644 --- a/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs +++ b/firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs @@ -5,7 +5,6 @@ const IOS_DEFAULT_PREFERENCES = { "extensions.formautofill.creditCards.heuristics.mode": 1, "extensions.formautofill.creditCards.heuristics.fathom.confidenceThreshold": 0.5, - "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold": 0.95, "extensions.formautofill.creditCards.heuristics.fathom.testConfidence": 0, "extensions.formautofill.creditCards.heuristics.fathom.types": "cc-number,cc-name", @@ -32,11 +31,11 @@ const IOS_DEFAULT_PREFERENCES = { "extensions.formautofill.heuristics.captureOnPageNavigation": false, "extensions.formautofill.heuristics.detectDynamicFormChanges": false, "extensions.formautofill.heuristics.fillOnDynamicFormChanges": false, + "extensions.formautofill.heuristics.refillOnSiteClearingFields": false, "extensions.formautofill.focusOnAutofill": false, "extensions.formautofill.test.ignoreVisibilityCheck": false, "extensions.formautofill.heuristics.autofillSameOriginWithTop": false, "signon.generation.confidenceThreshold": 0.75, - "extensions.formautofill.ml.experiment.enabled": false, }; // Used Mimic the behavior of .getAutocompleteInfo() diff --git a/firefox-ios/Client/Assets/CC_Script/CreditCard.sys.mjs b/firefox-ios/Client/Assets/CC_Script/CreditCard.sys.mjs index 622c371d76ad8..0b52d96f7ed7a 100644 --- a/firefox-ios/Client/Assets/CC_Script/CreditCard.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/CreditCard.sys.mjs @@ -217,6 +217,7 @@ export class CreditCard { /** * Normalizes a credit card number. + * * @param {string} number * @return {string | null} * @memberof CreditCard @@ -346,8 +347,8 @@ export class CreditCard { } /** - * * Please use getLabelInfo above, as it allows for localization. + * * @deprecated */ static getLabel({ number, name }) { diff --git a/firefox-ios/Client/Assets/CC_Script/CreditCardRecord.sys.mjs b/firefox-ios/Client/Assets/CC_Script/CreditCardRecord.sys.mjs index 97235e8cddad7..a7436213da35a 100644 --- a/firefox-ios/Client/Assets/CC_Script/CreditCardRecord.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/CreditCardRecord.sys.mjs @@ -12,6 +12,51 @@ import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofil * for processing and consistent data representation. */ export class CreditCardRecord { + /** + * Computes derived fields from the basic fields in the CreditCard object. + * + * @param {object} creditCard The credit card object + */ + static computeFields(creditCard) { + this.#computeCCNameFields(creditCard); + this.#computeCCExpirationDateFields(creditCard); + this.#computeCCTypeField(creditCard); + } + + static #computeCCExpirationDateFields(creditCard) { + if (!("cc-exp" in creditCard)) { + if (creditCard["cc-exp-month"] && creditCard["cc-exp-year"]) { + creditCard["cc-exp"] = + String(creditCard["cc-exp-year"]) + + "-" + + String(creditCard["cc-exp-month"]).padStart(2, "0"); + } else { + creditCard["cc-exp"] = ""; + } + } + } + + static #computeCCNameFields(creditCard) { + if (!("cc-given-name" in creditCard)) { + const nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]); + creditCard["cc-given-name"] = nameParts.given; + creditCard["cc-additional-name"] = nameParts.middle; + creditCard["cc-family-name"] = nameParts.family; + } + } + + static #computeCCTypeField(creditCard) { + const type = CreditCard.getType(creditCard["cc-number"]); + if (type) { + creditCard["cc-type"] = type; + } + } + + /** + * Normalizes credit card fields by removing derived fields from the CreditCard, leaving the basic fields. + * + * @param {object} creditCard The credit card object + */ static normalizeFields(creditCard) { this.#normalizeCCNameFields(creditCard); this.#normalizeCCNumberFields(creditCard); diff --git a/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs index 7d6a87d255cce..1110070fb3c8e 100644 --- a/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs @@ -4,9 +4,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - FormAutofill: "resource://autofill/FormAutofill.sys.mjs", FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs", - MLAutofill: "resource://autofill/MLAutofill.sys.mjs", }); /** @@ -42,6 +40,10 @@ export class FieldDetail { // The possible values are "autocomplete", "fathom", and "regex-heuristic" reason = null; + // This field could be a lookup field, for example, one that could be used to + // search for an address or postal code and fill in other fields. + isLookup = false; + /* * The "section", "addressType", and "contactType" values are * used to identify the exact field when the serializable data is received @@ -96,11 +98,9 @@ export class FieldDetail { fieldName = null, { autocompleteInfo = null, - fathomLabel = null, fathomConfidence = null, isVisible = true, - mlHeaderInput = null, - mlButtonInput = null, + isLookup = false, } = {} ) { const fieldDetail = new FieldDetail(element); @@ -133,23 +133,6 @@ export class FieldDetail { } else if (fathomConfidence) { fieldDetail.reason = "fathom"; fieldDetail.confidence = fathomConfidence; - - // TODO: This should be removed once we support reference field info across iframe. - // Temporarily add an addtional "the field is the only visible input" constraint - // when determining whether a form has only a high-confidence cc-* field a valid - // credit card section. We can remove this restriction once we are confident - // about only using fathom. - fieldDetail.isOnlyVisibleFieldWithHighConfidence = false; - if ( - fieldDetail.confidence > - lazy.FormAutofillUtils.ccFathomHighConfidenceThreshold - ) { - const root = element.form || element.ownerDocument; - const inputs = root.querySelectorAll("input:not([type=hidden])"); - if (inputs.length == 1 && inputs[0] == element) { - fieldDetail.isOnlyVisibleFieldWithHighConfidence = true; - } - } } else { fieldDetail.reason = "regex-heuristic"; } @@ -168,16 +151,7 @@ export class FieldDetail { // Info required by heuristics fieldDetail.maxLength = element.maxLength; - if ( - lazy.FormAutofill.isMLExperimentEnabled && - ["input", "select"].includes(element.localName) - ) { - fieldDetail.mlinput = lazy.MLAutofill.getMLMarkup(fieldDetail.element); - fieldDetail.mlHeaderInput = mlHeaderInput; - fieldDetail.mlButtonInput = mlButtonInput; - fieldDetail.fathomLabel = fathomLabel; - fieldDetail.fathomConfidence = fathomConfidence; - } + fieldDetail.isLookup = isLookup; return fieldDetail; } @@ -251,6 +225,43 @@ export class FieldScanner { return this.#fieldDetails[index]; } + getFieldsMatching(matchFn, includeInvisible = false) { + let fields = []; + + for (let idx = 0; this.elementExisting(idx); idx++) { + let field = this.#fieldDetails[idx]; + if ((includeInvisible || field.isVisible) && matchFn(field)) { + fields.push(field); + } + } + + return fields; + } + + /** + * Return the index of the first visible field found with the given name. + * + * @param {string} fieldName + * The field name to find. + * @param {string} includeInvisible + * Whether to find non-visible fields. + * @returns {number} + * The index of the element or -1 if not found. + */ + getFieldIndexByName(fieldName, includeInvisible = false) { + for (let idx = 0; this.elementExisting(idx); idx++) { + let field = this.#fieldDetails[idx]; + if ( + field.fieldName == fieldName && + (includeInvisible || field.isVisible) + ) { + return idx; + } + } + + return -1; + } + /** * When a field detail should be changed its fieldName after parsing, use * this function to update the fieldName which is at a specific index. diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs index fb4e49a0fb84c..f7cd9fe232fc3 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs @@ -24,8 +24,8 @@ const ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.addresses.supportedCountries"; const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled"; -const AUTOFILL_CREDITCARDS_REAUTH_PREF = - "extensions.formautofill.creditCards.reauth.optout"; +const AUTOFILL_CREDITCARDS_OS_AUTH_LOCKED_PREF = + "extensions.formautofill.creditCards.os-auth.locked.enabled"; const AUTOFILL_CREDITCARDS_HIDE_UI_PREF = "extensions.formautofill.creditCards.hideui"; const FORM_AUTOFILL_SUPPORT_RTL_PREF = "extensions.formautofill.supportRTL"; @@ -45,6 +45,10 @@ const AUTOFILL_FILL_ON_DYNAMIC_FORM_CHANGES_TIMEOUT_PREF = "extensions.formautofill.heuristics.fillOnDynamicFormChanges.timeout"; const AUTOFILL_FILL_ON_DYNAMIC_FORM_CHANGES_PREF = "extensions.formautofill.heuristics.fillOnDynamicFormChanges"; +const AUTOFILL_REFILL_ON_SITE_CLEARING_VALUE_PREF = + "extensions.formautofill.heuristics.refillOnSiteClearingFields"; +const AUTOFILL_REFILL_ON_SITE_CLEARING_VALUE_TIMEOUT_PREF = + "extensions.formautofill.heuristics.refillOnSiteClearingFields.timeout"; export const FormAutofill = { ENABLED_AUTOFILL_ADDRESSES_PREF, @@ -54,11 +58,13 @@ export const FormAutofill = { ENABLED_AUTOFILL_SAME_ORIGIN_WITH_TOP, ENABLED_AUTOFILL_CREDITCARDS_PREF, ENABLED_AUTOFILL_DETECT_DYNAMIC_FORM_CHANGES_PREF, - AUTOFILL_CREDITCARDS_REAUTH_PREF, + AUTOFILL_CREDITCARDS_OS_AUTH_LOCKED_PREF, AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF, AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF, AUTOFILL_FILL_ON_DYNAMIC_FORM_CHANGES_PREF, AUTOFILL_FILL_ON_DYNAMIC_FORM_CHANGES_TIMEOUT_PREF, + AUTOFILL_REFILL_ON_SITE_CLEARING_VALUE_PREF, + AUTOFILL_REFILL_ON_SITE_CLEARING_VALUE_TIMEOUT_PREF, _region: null, @@ -212,10 +218,6 @@ export const FormAutofill = { prefix: logPrefix, }); }, - - get isMLExperimentEnabled() { - return FormAutofill._isMLEnabled && FormAutofill._isMLExperimentEnabled; - }, }; // TODO: Bug 1747284. Use Region.home instead of reading "browser.serach.region" @@ -326,43 +328,36 @@ XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, - "_isMLEnabled", - "browser.ml.enable", + "detectDynamicFormChanges", + "extensions.formautofill.heuristics.detectDynamicFormChanges", false ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, - "_isMLExperimentEnabled", - "extensions.formautofill.ml.experiment.enabled", + "fillOnDynamicFormChanges", + "extensions.formautofill.heuristics.fillOnDynamicFormChanges", false ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, - "MLModelRevision", - "extensions.formautofill.ml.experiment.modelRevision", - null -); - -XPCOMUtils.defineLazyPreferenceGetter( - FormAutofill, - "detectDynamicFormChanges", - "extensions.formautofill.heuristics.detectDynamicFormChanges", - false + "fillOnDynamicFormChangeTimeout", + "extensions.formautofill.heuristics.fillOnDynamicFormChanges.timeout", + 0 ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, - "fillOnDynamicFormChanges", - "extensions.formautofill.heuristics.fillOnDynamicFormChanges", + "refillOnSiteClearingFields", + "extensions.formautofill.heuristics.refillOnSiteClearingFields", false ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, - "fillOnDynamicFormChangeTimeout", - "extensions.formautofill.heuristics.fillOnDynamicFormChanges.timeout", + "refillOnSiteClearingFieldsTimeout", + "extensions.formautofill.heuristics.refillOnSiteClearingFields.timeout", 0 ); diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs index 4b03f37653810..6fd5b7011bb48 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs @@ -17,6 +17,9 @@ ChromeUtils.defineESModuleGetters(lazy, { FormAutofillNameUtils: "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs", LabelUtils: "resource://gre/modules/shared/LabelUtils.sys.mjs", + PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs", + clearTimeout: "resource://gre/modules/Timer.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", }); const { FIELD_STATES } = FormAutofillUtils; @@ -24,6 +27,7 @@ const { FIELD_STATES } = FormAutofillUtils; export const FORM_CHANGE_REASON = { NODES_ADDED: "nodes-added", NODES_REMOVED: "nodes-removed", + SELECT_OPTIONS_CHANGED: "select-options-changed", ELEMENT_INVISIBLE: "visible-element-became-invisible", ELEMENT_VISIBLE: "invisible-element-became-visible", }; @@ -67,13 +71,14 @@ export class FormAutofillHandler { #formMutationObserver = null; + #visibilityObserver = null; #visibilityStateObserverByElement = new WeakMap(); /** * * fillOnFormChangeData.isWithinDynamicFormChangeThreshold: * Flags if a "form-change" event is received within the timeout threshold - * (see lazy.FormAutofill.fillOnDynamicFormChangeTimeout), that we set + * (see FormAutofill.fillOnDynamicFormChangeTimeout), that we set * in order to consider newly detected fields for filling. * fillOnFormChangeData.previouslyUsedProfile * The previously used profile from the latest autocompletion. @@ -81,10 +86,16 @@ export class FormAutofillHandler { * The previously focused element id from the latest autocompletion * * This is used for any following form changes and is cleared after a time threshold - * set by lazy.FormAutofill.fillOnDynamicFormChangeTimeout. + * set by FormAutofill.fillOnDynamicFormChangeTimeout. */ #fillOnFormChangeData = new Map(); + /** + * Caching the refill timeout id to cancel it once we know that we're about to fill + * on form change, because this sets up another refill timeout. + */ + #refillTimeoutId = null; + /** * Flag to indicate whethere there is an ongoing autofilling/clearing process. */ @@ -155,6 +166,30 @@ export class FormAutofillHandler { this.#fieldDetails = fieldDetails; } + /** + * Replaces a field detail with a different element. + * + * @param {FieldDetail} fieldDetail + * Field detail to replace. + * @param {Element} element + * Element to replace. + * @returns {FieldDetail} the new field detail object. + */ + #replaceFieldDetail(fieldDetail, element) { + let idx = this.#fieldDetails.indexOf(fieldDetail); + if (idx < 0) { + return null; + } + + let newfd = lazy.FieldDetail.create( + element, + element.form, + fieldDetail.fieldName + ); + this.#fieldDetails[idx] = newfd; + return newfd; + } + /** * Determines whether 'setIdentifiedFieldDetails' has been called and the * `fieldDetails` have been initialized. @@ -212,20 +247,10 @@ export class FormAutofillHandler { return this.#filledStateByElement.get(element); } - isVisiblityStateObserverSetUpByElement(element) { - return this.#visibilityStateObserverByElement.has(element); - } - - setVisibilityStateObserverByElement(element, observer) { - this.#visibilityStateObserverByElement.set(element, observer); - } - - clearVisibilityStateObserverByElement(element) { - if (this.isVisiblityStateObserverSetUpByElement(element)) { - const observer = this.#visibilityStateObserverByElement.get(element); - observer.disconnect(); - this.#visibilityStateObserverByElement.delete(element); - } + #clearVisibilityObserver() { + this.#visibilityObserver.disconnect(); + this.#visibilityObserver = null; + this.#visibilityStateObserverByElement = new WeakMap(); } /** @@ -268,6 +293,11 @@ export class FormAutofillHandler { return false; } + updateFormByElement(element) { + const formLike = lazy.AutofillFormFactory.createFromField(element); + this._updateForm(formLike); + } + /** * Update the form with a new FormLike, and the related fields should be * updated or clear to ensure the data consistency. @@ -286,8 +316,6 @@ export class FormAutofillHandler { * * @param {formLike} formLike * The form that we collect information from. - * @param {boolean} includeIframe - * True to add