From 0381bb90853ad6bf2bf86957f17982648d506303 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:26:10 +0000 Subject: [PATCH] Refactor - auto update credential provider script --- .../Assets/CC_Script/FieldScanner.sys.mjs | 7 +- .../Assets/CC_Script/FormAutofill.sys.mjs | 25 ++++- .../CC_Script/FormAutofillChild.ios.sys.mjs | 2 +- .../CC_Script/FormAutofillHandler.sys.mjs | 43 ++++++-- .../CC_Script/FormAutofillHeuristics.sys.mjs | 97 ++++++++++++++++--- .../CC_Script/FormAutofillSection.sys.mjs | 94 ++++++++++++------ .../Assets/CC_Script/HeuristicsRegExp.sys.mjs | 7 +- 7 files changed, 216 insertions(+), 59 deletions(-) diff --git a/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs index 85f771dd0c01..7d6a87d255cc 100644 --- a/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs @@ -6,6 +6,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", }); /** @@ -98,6 +99,8 @@ export class FieldDetail { fathomLabel = null, fathomConfidence = null, isVisible = true, + mlHeaderInput = null, + mlButtonInput = null, } = {} ) { const fieldDetail = new FieldDetail(element); @@ -169,7 +172,9 @@ export class FieldDetail { lazy.FormAutofill.isMLExperimentEnabled && ["input", "select"].includes(element.localName) ) { - fieldDetail.htmlMarkup = element.outerHTML.substring(0, 1024); + fieldDetail.mlinput = lazy.MLAutofill.getMLMarkup(fieldDetail.element); + fieldDetail.mlHeaderInput = mlHeaderInput; + fieldDetail.mlButtonInput = mlButtonInput; fieldDetail.fathomLabel = fathomLabel; fieldDetail.fathomConfidence = fathomConfidence; } diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs index 82889760649a..64e20cdbb317 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs @@ -83,10 +83,22 @@ export const FormAutofill = { } return false; }, + + /** + * Return true if address autofill is available for a specific country. + */ isAutofillAddressesAvailableInCountry(country) { - return FormAutofill._addressAutofillSupportedCountries.includes( - country.toUpperCase() - ); + if (FormAutofill._isAutofillAddressesAvailableInExperiment) { + return true; + } + + let available = FormAutofill._isAutofillAddressesAvailable; + if (country && available == "detect") { + return FormAutofill._addressAutofillSupportedCountries.includes( + country.toUpperCase() + ); + } + return available == "on"; }, get isAutofillEnabled() { return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled; @@ -317,6 +329,13 @@ XPCOMUtils.defineLazyPreferenceGetter( false ); +XPCOMUtils.defineLazyPreferenceGetter( + FormAutofill, + "MLModelRevision", + "extensions.formautofill.ml.experiment.modelRevision", + null +); + ChromeUtils.defineLazyGetter(FormAutofill, "countries", () => AddressMetaDataLoader.getCountries() ); diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofillChild.ios.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofillChild.ios.sys.mjs index b0a956a19304..f094b0fe96a1 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofillChild.ios.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofillChild.ios.sys.mjs @@ -71,7 +71,7 @@ export class FormAutofillChild { if (!handler.hasIdentifiedFields() || handler.updateFormIfNeeded(element)) { // If we found newly identified fields, run section classification heuristic - const detectedFields = FormAutofillHandler.collectFormFields( + const detectedFields = FormAutofillHandler.collectFormFieldDetails( handler.form ); diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs index a7aec3ac1f64..1989b41a94ff 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs @@ -217,20 +217,38 @@ export class FormAutofillHandler { * Collect , fields. * * @returns {Array} * An array containing eliglble fields for autofill, also * including iframe. */ - static collectFormFields(form) { - const fieldDetails = lazy.FormAutofillHeuristics.getFormInfo(form) ?? []; + static collectFormFieldDetails( + formLike, + includeIframe, + ignoreInvisibleInput = true + ) { + const fieldDetails = + lazy.FormAutofillHeuristics.getFormInfo(formLike, ignoreInvisibleInput) ?? + []; + + // 'FormLike' only contains & fields. * @returns {Array} * all sections within its field details in the form. */ - getFormInfo(form) { - const elements = Array.from(form.elements).filter(element => + getFormInfo(formLike, ignoreInvisibleInput) { + const elements = Array.from(formLike.elements).filter(element => lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element) ); + let closestHeaders; + let closestButtons; + if (FormAutofill.isMLExperimentEnabled && elements.length) { + closestHeaders = lazy.MLAutofill.closestHeaderAbove(elements); + closestButtons = lazy.MLAutofill.closestButtonBelow(elements); + } + const fieldDetails = []; - for (const element of elements) { + for (let idx = 0; idx < elements.length; idx++) { + const element = elements[idx]; // Ignore invisible , we still keep invisible // to store the value. const isVisible = lazy.FormAutofillUtils.isFieldVisible(element); - if (!HTMLSelectElement.isInstance(element) && !isVisible) { + if ( + !HTMLSelectElement.isInstance(element) && + !isVisible && + ignoreInvisibleInput + ) { continue; } @@ -742,11 +814,13 @@ export const FormAutofillHeuristics = { } fieldDetails.push( - lazy.FieldDetail.create(element, form, fieldName, { + lazy.FieldDetail.create(element, formLike, fieldName, { autocompleteInfo: inferInfo.autocompleteInfo, fathomLabel: inferInfo.fathomLabel, fathomConfidence: inferInfo.fathomConfidence, isVisible, + mlHeaderInput: closestHeaders?.[idx] ?? null, + mlButtonInput: closestButtons?.[idx] ?? null, }) ); } @@ -812,6 +886,7 @@ export const FormAutofillHeuristics = { // Attempt to parse the field using different parsers. if ( this._parseNameFields(scanner, fieldDetail) || + this._parseHouseNumberFields(scanner, fieldDetail) || this._parseStreetAddressFields(scanner, fieldDetail) || this._parseAddressFields(scanner, fieldDetail) || this._parseCreditCardExpiryFields(scanner, fieldDetail) || diff --git a/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs b/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs index c6aa40a773f7..42ff0ae01c7d 100644 --- a/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/FormAutofillSection.sys.mjs @@ -25,14 +25,19 @@ class FormSection { fieldDetails.forEach(field => this.addField(field)); - const fieldName = fieldDetails[0].fieldName; - if (lazy.FormAutofillUtils.isAddressField(fieldName)) { - this.type = FormSection.ADDRESS; - } else if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) { - this.type = FormSection.CREDIT_CARD; - } else { - throw new Error("Unknown field type to create a section."); + for (const fieldDetail of fieldDetails) { + if (lazy.FormAutofillUtils.isAddressField(fieldDetail.fieldName)) { + this.type = FormSection.ADDRESS; + break; + } else if ( + lazy.FormAutofillUtils.isCreditCardField(fieldDetail.fieldName) + ) { + this.type = FormSection.CREDIT_CARD; + break; + } } + + this.type ||= FormSection.ADDRESS; } get fieldDetails() { @@ -140,21 +145,40 @@ export class FormAutofillSection { * The result is an array contains the sections with its belonging field details. * * @param {Array} fieldDetails field detail array to be classified - * @param {boolean} ignoreInvalid - * True to keep invalid section in the return array. Only used by tests now. + * @param {object} options + * @param {boolean} [options.ignoreInvalidSection = false] + * True to keep invalid section in the return array. Only used by tests now + * @param {boolean} [options.ignoreUnknownField = true] + * False to keep unknown field in a section. Only used by developer tools now * @returns {Array} The array with the sections. */ - static classifySections(fieldDetails, ignoreInvalid = false) { - const addressSections = FormAutofillSection.groupFields( - fieldDetails.filter(f => - lazy.FormAutofillUtils.isAddressField(f.fieldName) - ) - ); - const creditCardSections = FormAutofillSection.groupFields( - fieldDetails.filter(f => - lazy.FormAutofillUtils.isCreditCardField(f.fieldName) - ) - ); + static classifySections( + fieldDetails, + { ignoreInvalidSection = false, ignoreUnknownField = true } = {} + ) { + const addressFields = []; + const creditCardFields = []; + + // 'current' refers to the last list where an field was added to. + // It helps determine the appropriate list for unknown fields, defaulting to the address + // field list for simplicity + let current = addressFields; + for (const fieldDetail of fieldDetails) { + if (lazy.FormAutofillUtils.isAddressField(fieldDetail.fieldName)) { + current = addressFields; + } else if ( + lazy.FormAutofillUtils.isCreditCardField(fieldDetail.fieldName) + ) { + current = creditCardFields; + } else if (ignoreUnknownField) { + continue; + } + current.push(fieldDetail); + } + + const addressSections = FormAutofillSection.groupFields(addressFields); + const creditCardSections = + FormAutofillSection.groupFields(creditCardFields); const sections = [...addressSections, ...creditCardSections].sort( (a, b) => @@ -173,7 +197,7 @@ export class FormAutofillSection { ? new FormAutofillAddressSection(section.fieldDetails) : new FormAutofillCreditCardSection(section.fieldDetails); - if (ignoreInvalid && !autofillableSection.isValidSection()) { + if (ignoreInvalidSection && !autofillableSection.isValidSection()) { continue; } @@ -420,10 +444,7 @@ export class FormAutofillAddressSection extends FormAutofillSection { const country = lazy.FormAutofillUtils.identifyCountryCode( record.country || record["country-name"] ); - if ( - country && - !lazy.FormAutofill.isAutofillAddressesAvailableInCountry(country) - ) { + if (!lazy.FormAutofill.isAutofillAddressesAvailableInCountry(country)) { // We don't want to save data in the wrong fields due to not having proper // heuristic regexes in countries we don't yet support. this.log.warn( @@ -585,16 +606,27 @@ export class FormAutofillCreditCardSection extends FormAutofillSection { "autofill-use-payment-method-os-prompt-windows", "autofill-use-payment-method-os-prompt-other" ); - const decrypted = await this.getDecryptedString( - profile["cc-number-encrypted"], - promptMessage - ); - + let decrypted; + let result; + try { + decrypted = await this.getDecryptedString( + profile["cc-number-encrypted"], + promptMessage + ); + result = decrypted ? "success" : "fail_user_canceled"; + } catch (ex) { + result = "fail_error"; + throw ex; + } finally { + Glean.formautofill.promptShownOsReauth.record({ + trigger: "autofill", + result, + }); + } if (!decrypted) { // Early return if the decrypted is empty or undefined return false; } - profile["cc-number"] = decrypted; } return true; diff --git a/firefox-ios/Client/Assets/CC_Script/HeuristicsRegExp.sys.mjs b/firefox-ios/Client/Assets/CC_Script/HeuristicsRegExp.sys.mjs index de52591353f4..4ef488aa6109 100644 --- a/firefox-ios/Client/Assets/CC_Script/HeuristicsRegExp.sys.mjs +++ b/firefox-ios/Client/Assets/CC_Script/HeuristicsRegExp.sys.mjs @@ -8,9 +8,9 @@ export const HeuristicsRegExp = { RULES: { email: undefined, tel: undefined, + "address-housenumber": undefined, "street-address": undefined, "address-line1": undefined, - "address-housenumber": undefined, "address-line2": undefined, "address-line3": undefined, "address-level2": undefined, @@ -52,6 +52,8 @@ export const HeuristicsRegExp = { "addrline2|address_2|addl2" + "|landmark", // common in IN "address-line3": "addrline3|address_3|addl3", + "address-housenumber": + "house\\s*number|hausnummer|haus|house[a-z\-]*n(r|o)", "postal-code": "^PLZ(\\b|\\*)", // de-DE "additional-name": "apellido.?materno|lastlastname", "cc-name": @@ -468,9 +470,6 @@ export const HeuristicsRegExp = { "|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]l(imiz)?|kent)(\\b|_|\\*))" + // tr "|^시[·・]?도", // ko-KR - "address-housenumber": - "housenumber|hausnummer|haus|house[a-z\-]*n(r|o)", - "postal-code": "zip|postal|post.*code|pcode" + "|pin.?code" + // en-IN