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 Oct 12, 2024
1 parent efa6554 commit 19311fa
Show file tree
Hide file tree
Showing 16 changed files with 2,296 additions and 1,984 deletions.
13 changes: 7 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": true,
"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,12 @@ 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,
"extensions.formautofill.heuristics.autofillSameOriginWithTop": false,
"signon.generation.confidenceThreshold": 0.75,
};

// Used Mimic the behavior of .getAutocompleteInfo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1207,7 +1207,9 @@ export var CreditCardRulesets = {
);

for (const type of this.types) {
this[type] = makeRuleset([...coefficients[type]], biases);
if (type) {
this[type] = makeRuleset([...coefficients[type]], biases);
}
}
},

Expand Down
203 changes: 124 additions & 79 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 All @@ -10,10 +15,25 @@ export class FieldDetail {
// Reference to the elemenet
elementWeakRef = null;

// id/name. This is only used for debugging
// The identifier generated via ContentDOMReference for the associated DOM element
// of this field
elementId = null;

// The identifier generated via ContentDOMReference for the root element of
// this field
rootElementId = null;

// If the element is an iframe, it is the id of the BrowsingContext of the iframe,
// Otherwise, it is the id of the BrowsingContext the element is in
browsingContextId = null;

// string with `${element.id}/{element.name}`. This is only used for debugging.
identifier = "";

// The inferred field name for this element
// tag name attribute of the element
localName = null;

// The inferred field name for this element.
fieldName = null;

// The approach we use to infer the information for this element
Expand Down Expand Up @@ -43,35 +63,103 @@ export class FieldDetail {
// Confidence value when the field name is inferred by "fathom"
confidence = null;

constructor(
constructor(element) {
this.elementWeakRef = new WeakRef(element);
}

get element() {
return this.elementWeakRef.deref();
}

/**
* Convert FieldDetail class to an object that is suitable for
* sending over IPC. Avoid using this in other case.
*/
toVanillaObject() {
const json = { ...this };
delete json.elementWeakRef;
return json;
}

static fromVanillaObject(obj) {
const element = lazy.FormAutofillUtils.getElementByIdentifier(
obj.elementId
);
return element ? Object.assign(new FieldDetail(element), obj) : null;
}

static create(
element,
form,
fieldName = null,
{ autocompleteInfo = {}, confidence = null } = {}
) {
this.elementWeakRef = new WeakRef(element);
this.identifier = `${element.id}/${element.name}`;
this.fieldName = fieldName;

if (autocompleteInfo) {
this.reason = "autocomplete";
this.section = autocompleteInfo.section;
this.addressType = autocompleteInfo.addressType;
this.contactType = autocompleteInfo.contactType;
this.credentialType = autocompleteInfo.credentialType;
const fieldDetail = new FieldDetail(element);

fieldDetail.elementId =
lazy.FormAutofillUtils.getElementIdentifier(element);
fieldDetail.rootElementId = lazy.FormAutofillUtils.getElementIdentifier(
form.rootElement
);
fieldDetail.identifier = `${element.id}/${element.name}`;
fieldDetail.localName = element.localName;

if (Array.isArray(fieldName)) {
fieldDetail.fieldName = fieldName[0] ?? "";
fieldDetail.alternativeFieldName = fieldName[1] ?? "";
} else {
fieldDetail.fieldName = fieldName;
}

if (!fieldDetail.fieldName) {
fieldDetail.reason = "unknown";
} else if (autocompleteInfo) {
fieldDetail.reason = "autocomplete";
fieldDetail.section = autocompleteInfo.section;
fieldDetail.addressType = autocompleteInfo.addressType;
fieldDetail.contactType = autocompleteInfo.contactType;
fieldDetail.credentialType = autocompleteInfo.credentialType;
fieldDetail.sectionName =
autocompleteInfo.section || autocompleteInfo.addressType;
} else if (confidence) {
this.reason = "fathom";
this.confidence = confidence;
fieldDetail.reason = "fathom";
fieldDetail.confidence = confidence;

// 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 {
this.reason = "regex-heuristic";
fieldDetail.reason = "regex-heuristic";
}
}

get element() {
return this.elementWeakRef.deref();
}
try {
fieldDetail.browsingContextId =
element.localName == "iframe"
? element.browsingContext.id
: BrowsingContext.getFromWindow(element.ownerGlobal).id;
} catch {
/* unit test doesn't have ownerGlobal */
}

get sectionName() {
return this.section || this.addressType;
fieldDetail.isVisible = lazy.FormAutofillUtils.isFieldVisible(element);

// Info required by heuristics
fieldDetail.maxLength = element.maxLength;

return fieldDetail;
}
}

Expand All @@ -83,29 +171,19 @@ export class FieldDetail {
* `inferFieldInfo` function.
*/
export class FieldScanner {
#elementsWeakRef = null;
#inferFieldInfoFn = null;

#parsingIndex = 0;

fieldDetails = [];
#fieldDetails = [];

/**
* Create a FieldScanner based on form elements with the existing
* fieldDetails.
*
* @param {Array.DOMElement} elements
* The elements from a form for each parser.
* @param {Funcion} inferFieldInfoFn
* The callback function that is used to infer the field info of a given element
* @param {Array<FieldDetails>} fieldDetails
* An array of fieldDetail object to be scanned.
*/
constructor(elements, inferFieldInfoFn) {
this.#elementsWeakRef = new WeakRef(elements);
this.#inferFieldInfoFn = inferFieldInfoFn;
}

get #elements() {
return this.#elementsWeakRef.deref();
constructor(fieldDetails) {
this.#fieldDetails = fieldDetails;
}

/**
Expand All @@ -119,7 +197,7 @@ export class FieldScanner {
}

get parsingFinished() {
return this.parsingIndex >= this.#elements.length;
return this.parsingIndex >= this.#fieldDetails.length;
}

/**
Expand All @@ -130,7 +208,7 @@ export class FieldScanner {
* The latest index of elements waiting for parsing.
*/
set parsingIndex(index) {
if (index > this.#elements.length) {
if (index > this.#fieldDetails.length) {
throw new Error("The parsing index is out of range.");
}
this.#parsingIndex = index;
Expand All @@ -146,44 +224,11 @@ export class FieldScanner {
* The field detail at the specific index.
*/
getFieldDetailByIndex(index) {
if (index >= this.#elements.length) {
if (index >= this.#fieldDetails.length) {
return null;
}

if (index < this.fieldDetails.length) {
return this.fieldDetails[index];
}

for (let i = this.fieldDetails.length; i < index + 1; i++) {
this.pushDetail();
}

return this.fieldDetails[index];
}

/**
* This function retrieves the first unparsed element and obtains its
* information by invoking the `inferFieldInfoFn` callback function.
* The field information is then stored in a FieldDetail object and
* appended to the `fieldDetails` array.
*
* Any element without the related detail will be used for adding the detail
* to the end of field details.
*/
pushDetail() {
const elementIndex = this.fieldDetails.length;
if (elementIndex >= this.#elements.length) {
throw new Error("Try to push the non-existing element info.");
}
const element = this.#elements[elementIndex];
const [fieldName, autocompleteInfo, confidence] =
this.#inferFieldInfoFn(element);
const fieldDetail = new FieldDetail(element, fieldName, {
autocompleteInfo,
confidence,
});

this.fieldDetails.push(fieldDetail);
return this.#fieldDetails[index];
}

/**
Expand All @@ -199,11 +244,11 @@ export class FieldScanner {
* autocomplete attribute
*/
updateFieldName(index, fieldName, ignoreAutocomplete = false) {
if (index >= this.fieldDetails.length) {
if (index >= this.#fieldDetails.length) {
throw new Error("Try to update the non-existing field detail.");
}

const fieldDetail = this.fieldDetails[index];
const fieldDetail = this.#fieldDetails[index];
if (fieldDetail.fieldName == fieldName) {
return;
}
Expand All @@ -212,12 +257,12 @@ export class FieldScanner {
return;
}

this.fieldDetails[index].fieldName = fieldName;
this.fieldDetails[index].reason = "update-heuristic";
fieldDetail.fieldName = fieldName;
fieldDetail.reason = "update-heuristic";
}

elementExisting(index) {
return index < this.#elements.length;
return index < this.#fieldDetails.length;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";

FormAutofill.defineLogGetter = (scope, logPrefix) => ({
FormAutofill.defineLogGetter = (_scope, _logPrefix) => ({
// TODO: Bug 1828405. Explore how logging should be handled.
// Maybe it makes more sense to do it on swift side and have JS just send messages.
info: () => {},
Expand Down
Loading

0 comments on commit 19311fa

Please sign in to comment.