Skip to content

Commit

Permalink
Refactor [vXXX] auto update credential provider script
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] authored Mar 1, 2024
1 parent efa6554 commit e08fba4
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 338 deletions.
11 changes: 5 additions & 6 deletions firefox-ios/Client/Assets/CC_Script/Constants.ios.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ const IOS_DEFAULT_PREFERENCES = {
"extensions.formautofill.creditCards.heuristics.fathom.testConfidence": 0,
"extensions.formautofill.creditCards.heuristics.fathom.types":
"cc-number,cc-name",
"extensions.formautofill.addresses.capture.requiredFields":
"street-address,postal-code,address-level1,address-level2",
"extensions.formautofill.loglevel": "Warn",
"extensions.formautofill.addresses.supported": "off",
"extensions.formautofill.creditCards.supported": "detect",
"browser.search.region": "US",
"extensions.formautofill.creditCards.supportedCountries": "US,CA,GB,FR,DE",
"extensions.formautofill.addresses.enabled": false,
"extensions.formautofill.addresses.enabled": true,
"extensions.formautofill.addresses.experiments.enabled": false, // TODO(FXCM-765): fetch this value from swift
"extensions.formautofill.addresses.capture.enabled": false,
"extensions.formautofill.addresses.capture.v2.enabled": false,
"extensions.formautofill.addresses.supportedCountries": "",
"extensions.formautofill.creditCards.enabled": true,
"extensions.formautofill.reauth.enabled": true,
Expand All @@ -26,13 +28,10 @@ const IOS_DEFAULT_PREFERENCES = {
"extensions.formautofill.addresses.ignoreAutocompleteOff": true,
"extensions.formautofill.heuristics.enabled": true,
"extensions.formautofill.section.enabled": true,
// WebKit doesn't support the checkVisibility API, setting the threshold value to 0 to ensure
// `IsFieldVisible` function doesn't use it
"extensions.formautofill.heuristics.visibilityCheckThreshold": 0,
"extensions.formautofill.heuristics.interactivityCheckMode": "focusability",
"extensions.formautofill.heuristics.captureOnFormRemoval": false,
"extensions.formautofill.heuristics.captureOnPageNavigation": false,
"extensions.formautofill.focusOnAutofill": false,
"extensions.formautofill.test.ignoreVisibilityCheck": false,
};

// Used Mimic the behavior of .getAutocompleteInfo()
Expand Down
13 changes: 13 additions & 0 deletions firefox-ios/Client/Assets/CC_Script/FieldScanner.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
});

/**
* Represents the detailed information about a form field, including
* the inferred field name, the approach used for inferring, and additional metadata.
Expand Down Expand Up @@ -73,6 +78,14 @@ export class FieldDetail {
get sectionName() {
return this.section || this.addressType;
}

#isVisible = null;
get isVisible() {
if (this.#isVisible == null) {
this.#isVisible = lazy.FormAutofillUtils.isFieldVisible(this.element);
}
return this.#isVisible;
}
}

/**
Expand Down
67 changes: 38 additions & 29 deletions firefox-ios/Client/Assets/CC_Script/FormAutofill.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { Region } from "resource://gre/modules/Region.sys.mjs";
import { AddressMetaDataLoader } from "resource://gre/modules/shared/AddressMetaDataLoader.sys.mjs";

const AUTOFILL_ADDRESSES_AVAILABLE_PREF =
"extensions.formautofill.addresses.supported";
Expand All @@ -17,8 +18,8 @@ const ENABLED_AUTOFILL_ADDRESSES_PREF =
"extensions.formautofill.addresses.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF =
"extensions.formautofill.addresses.capture.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_V2_PREF =
"extensions.formautofill.addresses.capture.v2.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_REQUIRED_FIELDS_PREF =
"extensions.formautofill.addresses.capture.requiredFields";
const ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF =
"extensions.formautofill.addresses.supportedCountries";
const ENABLED_AUTOFILL_CREDITCARDS_PREF =
Expand All @@ -32,17 +33,16 @@ const AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF =
"extensions.formautofill.creditCards.ignoreAutocompleteOff";
const AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF =
"extensions.formautofill.addresses.ignoreAutocompleteOff";
const ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL =
const ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF =
"extensions.formautofill.heuristics.captureOnFormRemoval";
const ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION =
const ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF =
"extensions.formautofill.heuristics.captureOnPageNavigation";

export const FormAutofill = {
ENABLED_AUTOFILL_ADDRESSES_PREF,
ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF,
ENABLED_AUTOFILL_ADDRESSES_CAPTURE_V2_PREF,
ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL,
ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION,
ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF,
ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF,
ENABLED_AUTOFILL_CREDITCARDS_PREF,
ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF,
AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF,
Expand Down Expand Up @@ -101,14 +101,25 @@ export const FormAutofill = {
/**
* Determines if the address autofill feature is available to use in the browser.
* If the feature is not available, then there are no user facing ways to enable it.
* Two conditions must be met for the autofill feature to be considered available:
* 1. Address autofill support is confirmed when:
* - `extensions.formautofill.addresses.supported` is set to `on`.
* - The user is located in a region supported by the feature
* (`extensions.formautofill.creditCards.supportedCountries`).
* 2. Address autofill is enabled through a Nimbus experiment:
* - The experiment pref `extensions.formautofill.addresses.experiments.enabled` is set to true.
*
* @returns {boolean} `true` if address autofill is available
*/
get isAutofillAddressesAvailable() {
return this._isSupportedRegion(
const isUserInSupportedRegion = this._isSupportedRegion(
FormAutofill._isAutofillAddressesAvailable,
FormAutofill._addressAutofillSupportedCountries
);
return (
isUserInSupportedRegion ||
FormAutofill._isAutofillAddressesAvailableInExperiment
);
},
/**
* Determines if the user has enabled or disabled credit card autofill.
Expand Down Expand Up @@ -208,11 +219,6 @@ XPCOMUtils.defineLazyPreferenceGetter(
"isAutofillAddressesCaptureEnabled",
ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"isAutofillAddressesCaptureV2Enabled",
ENABLED_AUTOFILL_ADDRESSES_CAPTURE_V2_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"_isAutofillCreditCardsAvailable",
Expand Down Expand Up @@ -261,25 +267,28 @@ XPCOMUtils.defineLazyPreferenceGetter(
XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"captureOnFormRemoval",
ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL
ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"captureOnPageNavigation",
ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION
ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"addressCaptureRequiredFields",
ENABLED_AUTOFILL_ADDRESSES_CAPTURE_REQUIRED_FIELDS_PREF,
null,
null,
val => val?.split(",").filter(v => !!v)
);

XPCOMUtils.defineLazyPreferenceGetter(
FormAutofill,
"_isAutofillAddressesAvailableInExperiment",
"extensions.formautofill.addresses.experiments.enabled"
);

// XXX: This should be invalidated on intl:app-locales-changed.
ChromeUtils.defineLazyGetter(FormAutofill, "countries", () => {
let availableRegionCodes =
Services.intl.getAvailableLocaleDisplayNames("region");
let displayNames = Services.intl.getRegionDisplayNames(
undefined,
availableRegionCodes
);
let result = new Map();
for (let i = 0; i < availableRegionCodes.length; i++) {
result.set(availableRegionCodes[i].toUpperCase(), displayNames[i]);
}
return result;
});
ChromeUtils.defineLazyGetter(FormAutofill, "countries", () =>
AddressMetaDataLoader.getCountries()
);
18 changes: 12 additions & 6 deletions firefox-ios/Client/Assets/CC_Script/FormAutofillChild.ios.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,21 @@ export class FormAutofillChild {

_doIdentifyAutofillFields(element) {
this.fieldDetailsManager.updateActiveInput(element);
const validDetails =
this.fieldDetailsManager.identifyAutofillFields(element);
this.fieldDetailsManager.identifyAutofillFields(element);

const activeFieldName =
this.fieldDetailsManager.activeFieldDetail?.fieldName;

const activeFieldDetails =
this.fieldDetailsManager.activeSection?.fieldDetails;

// Only ping swift if current field is either a cc or address field
if (!validDetails?.find(field => field.element === element)) {
if (!activeFieldDetails?.find(field => field.element === element)) {
return;
}

const fieldNamesWithValues =
this.transformToFieldNamesWithValues(validDetails);

this.transformToFieldNamesWithValues(activeFieldDetails);
if (FormAutofillUtils.isAddressField(activeFieldName)) {
this.callbacks.address.autofill(fieldNamesWithValues);
} else if (FormAutofillUtils.isCreditCardField(activeFieldName)) {
Expand Down Expand Up @@ -77,9 +78,14 @@ export class FormAutofillChild {
}

onSubmit(evt) {
if (!this.fieldDetailsManager.activeHandler) {
return;
}

this.fieldDetailsManager.activeHandler.onFormSubmitted();
const records = this.fieldDetailsManager.activeHandler.createRecords();
if (records.creditCard) {

if (records.creditCard.length) {
// Normalize record format so we always get a consistent
// credit card record format: {cc-number, cc-name, cc-exp-month, cc-exp-year}
const creditCardRecords = records.creditCard.map(entry => {
Expand Down
94 changes: 31 additions & 63 deletions firefox-ios/Client/Assets/CC_Script/FormAutofillHeuristics.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
LabelUtils: "resource://gre/modules/shared/LabelUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () =>
FormAutofill.defineLogGetter(lazy, "FormAutofillHeuristics")
);

/**
* To help us classify sections, we want to know what fields can appear
* multiple times in a row.
Expand Down Expand Up @@ -551,7 +547,9 @@ export const FormAutofillHeuristics = {
* all sections within its field details in the form.
*/
getFormInfo(form) {
let elements = this.getFormElements(form);
const elements = Array.from(form.elements).filter(element =>
lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
);

const scanner = new lazy.FieldScanner(elements, element =>
this.inferFieldInfo(element, elements)
Expand Down Expand Up @@ -600,44 +598,6 @@ export const FormAutofillHeuristics = {
);
},

/**
* Get form elements that are of credit card or address type and filtered by either
* visibility or focusability - depending on the interactivity mode (default = focusability)
* This distinction is only temporary as we want to test switching from visibility mode
* to focusability mode. The visibility mode is then removed.
*
* @param {HTMLElement} form
* @returns {Array<HTMLElement>} elements filtered by interactivity mode (visibility or focusability)
*/
getFormElements(form) {
let elements = Array.from(form.elements).filter(element =>
lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
);
const interactivityMode = lazy.FormAutofillUtils.interactivityCheckMode;

if (interactivityMode == "focusability") {
elements = elements.filter(element =>
lazy.FormAutofillUtils.isFieldFocusable(element)
);
} else if (interactivityMode == "visibility") {
// Due to potential performance impact while running visibility check on
// a large amount of elements, a comprehensive visibility check
// (considering opacity and CSS visibility) is only applied when the number
// of eligible elements is below a certain threshold.
const runVisiblityCheck =
elements.length < lazy.FormAutofillUtils.visibilityCheckThreshold;
if (!runVisiblityCheck) {
lazy.log.debug(
`Skip running visibility check, because of too many elements (${elements.length})`
);
}
elements = elements.filter(element =>
lazy.FormAutofillUtils.isFieldVisible(element, runVisiblityCheck)
);
}
return elements;
},

/**
* The result is an array contains the sections with its belonging field details.
*
Expand All @@ -647,46 +607,54 @@ export const FormAutofillHeuristics = {
_classifySections(fieldDetails) {
let sections = [];
for (let i = 0; i < fieldDetails.length; i++) {
const fieldName = fieldDetails[i].fieldName;
const sectionName = fieldDetails[i].sectionName;

const cur = fieldDetails[i];
const [currentSection] = sections.slice(-1);

// The section this field might belong to
// The section this field might be placed into.
let candidateSection = null;

// If the field doesn't have a section name, MAYBE put it to the previous
// section if exists. If the field has a section name, maybe put it to the
// nearest section that either has the same name or it doesn't has a name.
// Otherwise, create a new section.
if (!currentSection || !sectionName) {
// Use name group from autocomplete attribute (ex, section-xxx) to look for the section
// we might place this field into.
// If the field doesn't have a section name, the candidate section is the previous section.
if (!currentSection || !cur.sectionName) {
candidateSection = currentSection;
} else if (sectionName) {
} else if (cur.sectionName) {
// If the field has a section name, the candidate section is the nearest section that
// either shares the same name or lacks a name.
for (let idx = sections.length - 1; idx >= 0; idx--) {
if (!sections[idx].name || sections[idx].name == sectionName) {
if (!sections[idx].name || sections[idx].name == cur.sectionName) {
candidateSection = sections[idx];
break;
}
}
}

// We got an candidate section to put the field to, check whether the section
// already has a field with the same field name. If yes, only add the field to when
// the type of the field might appear multiple times in a row.
if (candidateSection) {
let createNewSection = true;
if (candidateSection.fieldDetails.find(f => f.fieldName == fieldName)) {

// We might create a new section instead of placing the field in the candiate section if
// the section already has a field with the same field name.
// We also check visibility for both the fields with the same field name because we don't
// wanht to create a new section for an invisible field.
if (
candidateSection.fieldDetails.find(
f => f.fieldName == cur.fieldName && f.isVisible && cur.isVisible
)
) {
// For some field type, it is common to have multiple fields in one section, for example,
// email. In that case, we will not create a new section even when the candidate section
// already has a field with the same field name.
const [lastFieldDetail] = candidateSection.fieldDetails.slice(-1);
if (lastFieldDetail.fieldName == fieldName) {
if (MULTI_FIELD_NAMES.includes(fieldName)) {
if (lastFieldDetail.fieldName == cur.fieldName) {
if (MULTI_FIELD_NAMES.includes(cur.fieldName)) {
createNewSection = false;
} else if (fieldName in MULTI_N_FIELD_NAMES) {
} else if (cur.fieldName in MULTI_N_FIELD_NAMES) {
// This is the heuristic to handle special cases where we can have multiple
// fields in one section, but only if the field has appeared N times in a row.
// For example, websites can use 4 consecutive 4-digit `cc-number` fields
// instead of one 16-digit `cc-number` field.

const N = MULTI_N_FIELD_NAMES[fieldName];
const N = MULTI_N_FIELD_NAMES[cur.fieldName];
if (lastFieldDetail.part) {
// If `part` is set, we have already identified this field can be
// merged previously
Expand All @@ -699,7 +667,7 @@ export const FormAutofillHeuristics = {
N == 2 ||
fieldDetails
.slice(i + 1, i + N - 1)
.every(f => f.fieldName == fieldName)
.every(f => f.fieldName == cur.fieldName)
) {
lastFieldDetail.part = 1;
fieldDetails[i].part = 2;
Expand Down
Loading

0 comments on commit e08fba4

Please sign in to comment.