summaryrefslogtreecommitdiffstats
path: root/toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs')
-rw-r--r--toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs1212
1 files changed, 1212 insertions, 0 deletions
diff --git a/toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs b/toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs
new file mode 100644
index 0000000000..ed72d26018
--- /dev/null
+++ b/toolkit/components/formautofill/shared/CreditCardRuleset.sys.mjs
@@ -0,0 +1,1212 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+/**
+ * Fathom ML model for identifying the fields of credit-card forms
+ *
+ * This is developed out-of-tree at https://github.com/mozilla-services/fathom-
+ * form-autofill, where there is also over a GB of training, validation, and
+ * testing data. To make changes, do your edits there (whether adding new
+ * training pages, adding new rules, or both), retrain and evaluate as
+ * documented at https://mozilla.github.io/fathom/training.html, paste the
+ * coefficients emitted by the trainer into the ruleset, and finally copy the
+ * ruleset's "CODE TO COPY INTO PRODUCTION" section to this file's "CODE FROM
+ * TRAINING REPOSITORY" section.
+ */
+
+/**
+ * CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
+ */
+
+import {
+ element as clickedElement,
+ out,
+ rule,
+ ruleset,
+ score,
+ type,
+} from "resource://gre/modules/third_party/fathom/fathom.mjs";
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
+import {
+ CreditCard,
+ NETWORK_NAMES,
+} from "resource://gre/modules/CreditCard.sys.mjs";
+
+import { FormLikeFactory } from "resource://gre/modules/FormLikeFactory.sys.mjs";
+import { LabelUtils } from "resource://gre/modules/shared/LabelUtils.sys.mjs";
+
+/**
+ * Callthrough abstraction to allow .getAutocompleteInfo() to be mocked out
+ * during training
+ *
+ * @param {Element} element DOM element to get info about
+ * @returns {object} Page-author-provided autocomplete metadata
+ */
+function getAutocompleteInfo(element) {
+ return element.getAutocompleteInfo();
+}
+
+/**
+ * @param {string} selector A CSS selector that prunes away ineligible elements
+ * @returns {Lhs} An LHS yielding the element the user has clicked or, if
+ * pruned, none
+ */
+function queriedOrClickedElements(selector) {
+ return clickedElement(selector);
+}
+
+/**
+ * START OF CODE PASTED FROM TRAINING REPOSITORY
+ */
+var FathomHeuristicsRegExp = {
+ RULES: {
+ "cc-name": undefined,
+ "cc-number": undefined,
+ "cc-exp-month": undefined,
+ "cc-exp-year": undefined,
+ "cc-exp": undefined,
+ "cc-type": undefined,
+ },
+
+ RULE_SETS: [
+ {
+ /* eslint-disable */
+ // Let us keep our consistent wrapping.
+ "cc-name":
+ // Firefox-specific rules
+ "account.*holder.*name" +
+ // de-DE
+ "|^(kredit)?(karten|konto)inhaber" +
+ "|^(name).*karte" +
+ // fr-FR
+ "|nom.*(titulaire|détenteur)" +
+ "|(titulaire|détenteur).*(carte)" +
+ // it-IT
+ "|titolare.*carta" +
+ // pl-PL
+ "|posiadacz.*karty" +
+ // Rules from Bitwarden
+ "|cc-?name" +
+ "|card-?name" +
+ "|cardholder-?name" +
+ "|(^nom$)" +
+ // Rules are from Chromium source codes
+ "|card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" +
+ "|(?:card|cc).?name|cc.?full.?name" +
+ "|(?:card|cc).?owner" +
+ "|nombre.*tarjeta" + // es
+ "|nom.*carte" + // fr-FR
+ "|nome.*cart" + // it-IT
+ "|名前" + // ja-JP
+ "|Имя.*карты" + // ru
+ "|信用卡开户名|开户名|持卡人姓名" + // zh-CN
+ "|持卡人姓名", // zh-TW
+
+ "cc-number":
+ // Firefox-specific rules
+ // de-DE
+ "(cc|kk)nr" +
+ "|(kredit)?(karten)(nummer|nr)" +
+ // it-IT
+ "|numero.*carta" +
+ // fr-FR
+ "|(numero|número|numéro).*(carte)" +
+ // pl-PL
+ "|numer.*karty" +
+ // Rules from Bitwarden
+ "|cc-?number" +
+ "|cc-?num" +
+ "|card-?number" +
+ "|card-?num" +
+ "|cc-?no" +
+ "|card-?no" +
+ "|numero-?carte" +
+ "|num-?carte" +
+ "|cb-?num" +
+ // Rules are from Chromium source codes
+ "|(add)?(?:card|cc|acct).?(?:number|#|no|num)" +
+ "|カード番号" + // ja-JP
+ "|Номер.*карты" + // ru
+ "|信用卡号|信用卡号码" + // zh-CN
+ "|信用卡卡號" + // zh-TW
+ "|카드", // ko-KR
+
+ "cc-exp":
+ // Firefox-specific rules
+ "mm\\s*(\/|\\|-)\\s*(yy|jj|aa)" +
+ "|(month|mois)\\s*(\/|\\|-|et)\\s*(year|année)" +
+ // de-DE
+ // fr-FR
+ // Rules from Bitwarden
+ "|(^cc-?exp$)" +
+ "|(^card-?exp$)" +
+ "|(^cc-?expiration$)" +
+ "|(^card-?expiration$)" +
+ "|(^cc-?ex$)" +
+ "|(^card-?ex$)" +
+ "|(^card-?expire$)" +
+ "|(^card-?expiry$)" +
+ "|(^validite$)" +
+ "|(^expiration$)" +
+ "|(^expiry$)" +
+ "|mm-?yy" +
+ "|mm-?yyyy" +
+ "|yy-?mm" +
+ "|yyyy-?mm" +
+ "|expiration-?date" +
+ "|payment-?card-?expiration" +
+ "|(^payment-?cc-?date$)" +
+ // Rules are from Chromium source codes
+ "|expir|exp.*date|^expfield$" +
+ "|ablaufdatum|gueltig|gültig" + // de-DE
+ "|fecha" + // es
+ "|date.*exp" + // fr-FR
+ "|scadenza" + // it-IT
+ "|有効期限" + // ja-JP
+ "|validade" + // pt-BR, pt-PT
+ "|Срок действия карты", // ru
+
+ "cc-exp-month":
+ // Firefox-specific rules
+ "(cc|kk)month" + // de-DE
+ // Rules from Bitwarden
+ "|(^exp-?month$)" +
+ "|(^cc-?exp-?month$)" +
+ "|(^cc-?month$)" +
+ "|(^card-?month$)" +
+ "|(^cc-?mo$)" +
+ "|(^card-?mo$)" +
+ "|(^exp-?mo$)" +
+ "|(^card-?exp-?mo$)" +
+ "|(^cc-?exp-?mo$)" +
+ "|(^card-?expiration-?month$)" +
+ "|(^expiration-?month$)" +
+ "|(^cc-?mm$)" +
+ "|(^cc-?m$)" +
+ "|(^card-?mm$)" +
+ "|(^card-?m$)" +
+ "|(^card-?exp-?mm$)" +
+ "|(^cc-?exp-?mm$)" +
+ "|(^exp-?mm$)" +
+ "|(^exp-?m$)" +
+ "|(^expire-?month$)" +
+ "|(^expire-?mo$)" +
+ "|(^expiry-?month$)" +
+ "|(^expiry-?mo$)" +
+ "|(^card-?expire-?month$)" +
+ "|(^card-?expire-?mo$)" +
+ "|(^card-?expiry-?month$)" +
+ "|(^card-?expiry-?mo$)" +
+ "|(^mois-?validite$)" +
+ "|(^mois-?expiration$)" +
+ "|(^m-?validite$)" +
+ "|(^m-?expiration$)" +
+ "|(^expiry-?date-?field-?month$)" +
+ "|(^expiration-?date-?month$)" +
+ "|(^expiration-?date-?mm$)" +
+ "|(^exp-?mon$)" +
+ "|(^validity-?mo$)" +
+ "|(^exp-?date-?mo$)" +
+ "|(^cb-?date-?mois$)" +
+ "|(^date-?m$)" +
+ // Rules are from Chromium source codes
+ "|exp.*mo|ccmonth|cardmonth|addmonth" +
+ "|monat" + // de-DE
+ // "|fecha" + // es
+ // "|date.*exp" + // fr-FR
+ // "|scadenza" + // it-IT
+ // "|有効期限" + // ja-JP
+ // "|validade" + // pt-BR, pt-PT
+ // "|Срок действия карты" + // ru
+ "|月", // zh-CN
+
+ "cc-exp-year":
+ // Firefox-specific rules
+ "(cc|kk)year" + // de-DE
+ // Rules from Bitwarden
+ "|(^exp-?year$)" +
+ "|(^cc-?exp-?year$)" +
+ "|(^cc-?year$)" +
+ "|(^card-?year$)" +
+ "|(^cc-?yr$)" +
+ "|(^card-?yr$)" +
+ "|(^exp-?yr$)" +
+ "|(^card-?exp-?yr$)" +
+ "|(^cc-?exp-?yr$)" +
+ "|(^card-?expiration-?year$)" +
+ "|(^expiration-?year$)" +
+ "|(^cc-?yy$)" +
+ "|(^cc-?y$)" +
+ "|(^card-?yy$)" +
+ "|(^card-?y$)" +
+ "|(^card-?exp-?yy$)" +
+ "|(^cc-?exp-?yy$)" +
+ "|(^exp-?yy$)" +
+ "|(^exp-?y$)" +
+ "|(^cc-?yyyy$)" +
+ "|(^card-?yyyy$)" +
+ "|(^card-?exp-?yyyy$)" +
+ "|(^cc-?exp-?yyyy$)" +
+ "|(^expire-?year$)" +
+ "|(^expire-?yr$)" +
+ "|(^expiry-?year$)" +
+ "|(^expiry-?yr$)" +
+ "|(^card-?expire-?year$)" +
+ "|(^card-?expire-?yr$)" +
+ "|(^card-?expiry-?year$)" +
+ "|(^card-?expiry-?yr$)" +
+ "|(^an-?validite$)" +
+ "|(^an-?expiration$)" +
+ "|(^annee-?validite$)" +
+ "|(^annee-?expiration$)" +
+ "|(^expiry-?date-?field-?year$)" +
+ "|(^expiration-?date-?year$)" +
+ "|(^cb-?date-?ann$)" +
+ "|(^expiration-?date-?yy$)" +
+ "|(^expiration-?date-?yyyy$)" +
+ "|(^validity-?year$)" +
+ "|(^exp-?date-?year$)" +
+ "|(^date-?y$)" +
+ // Rules are from Chromium source codes
+ "|(add)?year" +
+ "|jahr" + // de-DE
+ // "|fecha" + // es
+ // "|scadenza" + // it-IT
+ // "|有効期限" + // ja-JP
+ // "|validade" + // pt-BR, pt-PT
+ // "|Срок действия карты" + // ru
+ "|年|有效期", // zh-CN
+
+ "cc-type":
+ // Firefox-specific rules
+ "type" +
+ // de-DE
+ "|Kartenmarke" +
+ // Rules from Bitwarden
+ "|(^cc-?type$)" +
+ "|(^card-?type$)" +
+ "|(^card-?brand$)" +
+ "|(^cc-?brand$)" +
+ "|(^cb-?type$)",
+ // Rules are from Chromium source codes
+ },
+ ],
+
+ _getRule(name) {
+ let rules = [];
+ this.RULE_SETS.forEach(set => {
+ if (set[name]) {
+ rules.push(`(${set[name]})`.normalize("NFKC"));
+ }
+ });
+
+ const value = new RegExp(rules.join("|"), "iu");
+ Object.defineProperty(this.RULES, name, { get: undefined });
+ Object.defineProperty(this.RULES, name, { value });
+ return value;
+ },
+
+ init() {
+ Object.keys(this.RULES).forEach(field =>
+ Object.defineProperty(this.RULES, field, {
+ get() {
+ return FathomHeuristicsRegExp._getRule(field);
+ },
+ })
+ );
+ },
+};
+
+FathomHeuristicsRegExp.init();
+
+const MMRegExp = /^mm$|\(mm\)/i;
+const YYorYYYYRegExp = /^(yy|yyyy)$|\(yy\)|\(yyyy\)/i;
+const monthRegExp = /month/i;
+const yearRegExp = /year/i;
+const MMYYRegExp = /mm\s*(\/|\\)\s*yy/i;
+const VisaCheckoutRegExp = /visa(-|\s)checkout/i;
+const CREDIT_CARD_NETWORK_REGEXP = new RegExp(
+ CreditCard.getSupportedNetworks()
+ .concat(Object.keys(NETWORK_NAMES))
+ .join("|"),
+ "gui"
+ );
+const TwoDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yy(?:[^y]|$)/i;
+const FourDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yyyy(?:[^y]|$)/i;
+const dwfrmRegExp = /^dwfrm/i;
+const bmlRegExp = /bml/i;
+const templatedValue = /^\{\{.*\}\}$/;
+const firstRegExp = /first/i;
+const lastRegExp = /last/i;
+const giftRegExp = /gift/i;
+const subscriptionRegExp = /subscription/i;
+
+function autocompleteStringMatches(element, ccString) {
+ const info = getAutocompleteInfo(element);
+ return info.fieldName === ccString;
+}
+
+function getFillableFormElements(element) {
+ const formLike = FormLikeFactory.createFromField(element);
+ return Array.from(formLike.elements).filter(el =>
+ FormAutofillUtils.isCreditCardOrAddressFieldType(el)
+ );
+}
+
+function nextFillableFormField(element) {
+ const fillableFormElements = getFillableFormElements(element);
+ const elementIndex = fillableFormElements.indexOf(element);
+ return fillableFormElements[elementIndex + 1];
+}
+
+function previousFillableFormField(element) {
+ const fillableFormElements = getFillableFormElements(element);
+ const elementIndex = fillableFormElements.indexOf(element);
+ return fillableFormElements[elementIndex - 1];
+}
+
+function nextFieldPredicateIsTrue(element, predicate) {
+ const nextField = nextFillableFormField(element);
+ return !!nextField && predicate(nextField);
+}
+
+function previousFieldPredicateIsTrue(element, predicate) {
+ const previousField = previousFillableFormField(element);
+ return !!previousField && predicate(previousField);
+}
+
+function nextFieldMatchesExpYearAutocomplete(fnode) {
+ return nextFieldPredicateIsTrue(fnode.element, nextField =>
+ autocompleteStringMatches(nextField, "cc-exp-year")
+ );
+}
+
+function previousFieldMatchesExpMonthAutocomplete(fnode) {
+ return previousFieldPredicateIsTrue(fnode.element, previousField =>
+ autocompleteStringMatches(previousField, "cc-exp-month")
+ );
+}
+
+//////////////////////////////////////////////
+// Attribute Regular Expression Rules
+function idOrNameMatchRegExp(element, regExp) {
+ for (const str of [element.id, element.name]) {
+ if (regExp.test(str)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function getElementLabels(element) {
+ return {
+ *[Symbol.iterator]() {
+ const labels = LabelUtils.findLabelElements(element);
+ for (let label of labels) {
+ yield* LabelUtils.extractLabelStrings(label);
+ }
+ },
+ };
+}
+
+function labelsMatchRegExp(element, regExp) {
+ const elemStrings = getElementLabels(element);
+ for (const str of elemStrings) {
+ if (regExp.test(str)) {
+ return true;
+ }
+ }
+
+ const parentElement = element.parentElement;
+ // Bug 1634819: element.parentElement is null if element.parentNode is a ShadowRoot
+ if (!parentElement) {
+ return false;
+ }
+ // Check if the input is in a <td>, and, if so, check the textContent of the containing <tr>
+ if (parentElement.tagName === "TD" && parentElement.parentElement) {
+ // TODO: How bad is the assumption that the <tr> won't be the parent of the <td>?
+ return regExp.test(parentElement.parentElement.textContent);
+ }
+
+ // Check if the input is in a <dd>, and, if so, check the textContent of the preceding <dt>
+ if (
+ parentElement.tagName === "DD" &&
+ // previousElementSibling can be null
+ parentElement.previousElementSibling
+ ) {
+ return regExp.test(parentElement.previousElementSibling.textContent);
+ }
+ return false;
+}
+
+function closestLabelMatchesRegExp(element, regExp) {
+ const previousElementSibling = element.previousElementSibling;
+ if (
+ previousElementSibling !== null &&
+ previousElementSibling.tagName === "LABEL"
+ ) {
+ return regExp.test(previousElementSibling.textContent);
+ }
+
+ const nextElementSibling = element.nextElementSibling;
+ if (nextElementSibling !== null && nextElementSibling.tagName === "LABEL") {
+ return regExp.test(nextElementSibling.textContent);
+ }
+
+ return false;
+}
+
+function ariaLabelMatchesRegExp(element, regExp) {
+ const ariaLabel = element.getAttribute("aria-label");
+ return !!ariaLabel && regExp.test(ariaLabel);
+}
+
+function placeholderMatchesRegExp(element, regExp) {
+ const placeholder = element.getAttribute("placeholder");
+ return !!placeholder && regExp.test(placeholder);
+}
+
+function nextFieldIdOrNameMatchRegExp(element, regExp) {
+ return nextFieldPredicateIsTrue(element, nextField =>
+ idOrNameMatchRegExp(nextField, regExp)
+ );
+}
+
+function nextFieldLabelsMatchRegExp(element, regExp) {
+ return nextFieldPredicateIsTrue(element, nextField =>
+ labelsMatchRegExp(nextField, regExp)
+ );
+}
+
+function nextFieldPlaceholderMatchesRegExp(element, regExp) {
+ return nextFieldPredicateIsTrue(element, nextField =>
+ placeholderMatchesRegExp(nextField, regExp)
+ );
+}
+
+function nextFieldAriaLabelMatchesRegExp(element, regExp) {
+ return nextFieldPredicateIsTrue(element, nextField =>
+ ariaLabelMatchesRegExp(nextField, regExp)
+ );
+}
+
+function previousFieldIdOrNameMatchRegExp(element, regExp) {
+ return previousFieldPredicateIsTrue(element, previousField =>
+ idOrNameMatchRegExp(previousField, regExp)
+ );
+}
+
+function previousFieldLabelsMatchRegExp(element, regExp) {
+ return previousFieldPredicateIsTrue(element, previousField =>
+ labelsMatchRegExp(previousField, regExp)
+ );
+}
+
+function previousFieldPlaceholderMatchesRegExp(element, regExp) {
+ return previousFieldPredicateIsTrue(element, previousField =>
+ placeholderMatchesRegExp(previousField, regExp)
+ );
+}
+
+function previousFieldAriaLabelMatchesRegExp(element, regExp) {
+ return previousFieldPredicateIsTrue(element, previousField =>
+ ariaLabelMatchesRegExp(previousField, regExp)
+ );
+}
+//////////////////////////////////////////////
+
+function isSelectWithCreditCardOptions(fnode) {
+ // Check every select for options that match credit card network names in
+ // value or label.
+ const element = fnode.element;
+ if (element.tagName === "SELECT") {
+ for (let option of element.querySelectorAll("option")) {
+ if (
+ CreditCard.getNetworkFromName(option.value) ||
+ CreditCard.getNetworkFromName(option.text)
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * If any of the regular expressions match multiple times, we assume the tested
+ * string belongs to a radio button for payment type instead of card type.
+ *
+ * @param {Fnode} fnode
+ * @returns {boolean}
+ */
+function isRadioWithCreditCardText(fnode) {
+ const element = fnode.element;
+ const inputType = element.type;
+ if (!!inputType && inputType === "radio") {
+ const valueMatches = element.value.match(CREDIT_CARD_NETWORK_REGEXP);
+ if (valueMatches) {
+ return valueMatches.length === 1;
+ }
+
+ // Here we are checking that only one label matches only one entry in the regular expression.
+ const labels = getElementLabels(element);
+ let labelsMatched = 0;
+ for (const label of labels) {
+ const labelMatches = label.match(CREDIT_CARD_NETWORK_REGEXP);
+ if (labelMatches) {
+ if (labelMatches.length > 1) {
+ return false;
+ }
+ labelsMatched++;
+ }
+ }
+ if (labelsMatched > 0) {
+ return labelsMatched === 1;
+ }
+
+ const textContentMatches = element.textContent.match(
+ CREDIT_CARD_NETWORK_REGEXP
+ );
+ if (textContentMatches) {
+ return textContentMatches.length === 1;
+ }
+ }
+ return false;
+}
+
+function matchContiguousSubArray(array, subArray) {
+ return array.some((elm, i) =>
+ subArray.every((sElem, j) => sElem === array[i + j])
+ );
+}
+
+function isExpirationMonthLikely(element) {
+ if (element.tagName !== "SELECT") {
+ return false;
+ }
+
+ const options = [...element.options];
+ const desiredValues = Array(12)
+ .fill(1)
+ .map((v, i) => v + i);
+
+ // The number of month options shouldn't be less than 12 or larger than 13
+ // including the default option.
+ if (options.length < 12 || options.length > 13) {
+ return false;
+ }
+
+ return (
+ matchContiguousSubArray(
+ options.map(e => +e.value),
+ desiredValues
+ ) ||
+ matchContiguousSubArray(
+ options.map(e => +e.label),
+ desiredValues
+ )
+ );
+}
+
+function isExpirationYearLikely(element) {
+ if (element.tagName !== "SELECT") {
+ return false;
+ }
+
+ const options = [...element.options];
+ // A normal expiration year select should contain at least the last three years
+ // in the list.
+ const curYear = new Date().getFullYear();
+ const desiredValues = Array(3)
+ .fill(0)
+ .map((v, i) => v + curYear + i);
+
+ return (
+ matchContiguousSubArray(
+ options.map(e => +e.value),
+ desiredValues
+ ) ||
+ matchContiguousSubArray(
+ options.map(e => +e.label),
+ desiredValues
+ )
+ );
+}
+
+function nextFieldIsExpirationYearLikely(fnode) {
+ return nextFieldPredicateIsTrue(fnode.element, isExpirationYearLikely);
+}
+
+function previousFieldIsExpirationMonthLikely(fnode) {
+ return previousFieldPredicateIsTrue(fnode.element, isExpirationMonthLikely);
+}
+
+function attrsMatchExpWith2Or4DigitYear(fnode, regExpMatchingFunction) {
+ const element = fnode.element;
+ return (
+ regExpMatchingFunction(element, TwoDigitYearRegExp) ||
+ regExpMatchingFunction(element, FourDigitYearRegExp)
+ );
+}
+
+function maxLengthIs(fnode, maxLengthValue) {
+ return fnode.element.maxLength === maxLengthValue;
+}
+
+function roleIsMenu(fnode) {
+ const role = fnode.element.getAttribute("role");
+ return !!role && role === "menu";
+}
+
+function idOrNameMatchDwfrmAndBml(fnode) {
+ return (
+ idOrNameMatchRegExp(fnode.element, dwfrmRegExp) &&
+ idOrNameMatchRegExp(fnode.element, bmlRegExp)
+ );
+}
+
+function hasTemplatedValue(fnode) {
+ const value = fnode.element.getAttribute("value");
+ return !!value && templatedValue.test(value);
+}
+
+function inputTypeNotNumbery(fnode) {
+ const inputType = fnode.element.type;
+ if (inputType) {
+ return !["text", "tel", "number"].includes(inputType);
+ }
+ return false;
+}
+
+function idOrNameMatchFirstAndLast(fnode) {
+ return (
+ idOrNameMatchRegExp(fnode.element, firstRegExp) &&
+ idOrNameMatchRegExp(fnode.element, lastRegExp)
+ );
+}
+
+/**
+ * Compactly generate a series of rules that all take a single LHS type with no
+ * .when() clause and have only a score() call on the right- hand side.
+ *
+ * @param {Lhs} inType The incoming fnode type that all rules take
+ * @param {object} ruleMap A simple object used as a map with rule names
+ * pointing to scoring callbacks
+ * @yields {Rule}
+ */
+function* simpleScoringRules(inType, ruleMap) {
+ for (const [name, scoringCallback] of Object.entries(ruleMap)) {
+ yield rule(type(inType), score(scoringCallback), { name });
+ }
+}
+
+function makeRuleset(coeffs, biases) {
+ return ruleset(
+ [
+ /**
+ * Factor out the page scan just for a little more speed during training.
+ * This selector is good for most fields. cardType is an exception: it
+ * cannot be type=month.
+ */
+ rule(
+ queriedOrClickedElements(
+ "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=month], select, button"
+ ),
+ type("typicalCandidates")
+ ),
+
+ /**
+ * number rules
+ */
+ rule(type("typicalCandidates"), type("cc-number")),
+ ...simpleScoringRules("cc-number", {
+ idOrNameMatchNumberRegExp: fnode =>
+ idOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-number"]
+ ),
+ labelsMatchNumberRegExp: fnode =>
+ labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
+ closestLabelMatchesNumberRegExp: fnode =>
+ closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
+ placeholderMatchesNumberRegExp: fnode =>
+ placeholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-number"]
+ ),
+ ariaLabelMatchesNumberRegExp: fnode =>
+ ariaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-number"]
+ ),
+ idOrNameMatchGift: fnode =>
+ idOrNameMatchRegExp(fnode.element, giftRegExp),
+ labelsMatchGift: fnode => labelsMatchRegExp(fnode.element, giftRegExp),
+ placeholderMatchesGift: fnode =>
+ placeholderMatchesRegExp(fnode.element, giftRegExp),
+ ariaLabelMatchesGift: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, giftRegExp),
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ inputTypeNotNumbery,
+ }),
+ rule(type("cc-number"), out("cc-number")),
+
+ /**
+ * name rules
+ */
+ rule(type("typicalCandidates"), type("cc-name")),
+ ...simpleScoringRules("cc-name", {
+ idOrNameMatchNameRegExp: fnode =>
+ idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
+ labelsMatchNameRegExp: fnode =>
+ labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
+ closestLabelMatchesNameRegExp: fnode =>
+ closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
+ placeholderMatchesNameRegExp: fnode =>
+ placeholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-name"]
+ ),
+ ariaLabelMatchesNameRegExp: fnode =>
+ ariaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-name"]
+ ),
+ idOrNameMatchFirst: fnode =>
+ idOrNameMatchRegExp(fnode.element, firstRegExp),
+ labelsMatchFirst: fnode =>
+ labelsMatchRegExp(fnode.element, firstRegExp),
+ placeholderMatchesFirst: fnode =>
+ placeholderMatchesRegExp(fnode.element, firstRegExp),
+ ariaLabelMatchesFirst: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, firstRegExp),
+ idOrNameMatchLast: fnode =>
+ idOrNameMatchRegExp(fnode.element, lastRegExp),
+ labelsMatchLast: fnode => labelsMatchRegExp(fnode.element, lastRegExp),
+ placeholderMatchesLast: fnode =>
+ placeholderMatchesRegExp(fnode.element, lastRegExp),
+ ariaLabelMatchesLast: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, lastRegExp),
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchFirstAndLast,
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ }),
+ rule(type("cc-name"), out("cc-name")),
+
+ /**
+ * cardType rules
+ */
+ rule(
+ queriedOrClickedElements(
+ "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=radio], select, button"
+ ),
+ type("cc-type")
+ ),
+ ...simpleScoringRules("cc-type", {
+ idOrNameMatchTypeRegExp: fnode =>
+ idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
+ labelsMatchTypeRegExp: fnode =>
+ labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
+ closestLabelMatchesTypeRegExp: fnode =>
+ closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
+ idOrNameMatchVisaCheckout: fnode =>
+ idOrNameMatchRegExp(fnode.element, VisaCheckoutRegExp),
+ ariaLabelMatchesVisaCheckout: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, VisaCheckoutRegExp),
+ isSelectWithCreditCardOptions,
+ isRadioWithCreditCardText,
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ }),
+ rule(type("cc-type"), out("cc-type")),
+
+ /**
+ * expiration rules
+ */
+ rule(type("typicalCandidates"), type("cc-exp")),
+ ...simpleScoringRules("cc-exp", {
+ labelsMatchExpRegExp: fnode =>
+ labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
+ closestLabelMatchesExpRegExp: fnode =>
+ closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
+ placeholderMatchesExpRegExp: fnode =>
+ placeholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp"]
+ ),
+ labelsMatchExpWith2Or4DigitYear: fnode =>
+ attrsMatchExpWith2Or4DigitYear(fnode, labelsMatchRegExp),
+ placeholderMatchesExpWith2Or4DigitYear: fnode =>
+ attrsMatchExpWith2Or4DigitYear(fnode, placeholderMatchesRegExp),
+ labelsMatchMMYY: fnode => labelsMatchRegExp(fnode.element, MMYYRegExp),
+ placeholderMatchesMMYY: fnode =>
+ placeholderMatchesRegExp(fnode.element, MMYYRegExp),
+ maxLengthIs7: fnode => maxLengthIs(fnode, 7),
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ isExpirationMonthLikely: fnode =>
+ isExpirationMonthLikely(fnode.element),
+ isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
+ idOrNameMatchMonth: fnode =>
+ idOrNameMatchRegExp(fnode.element, monthRegExp),
+ idOrNameMatchYear: fnode =>
+ idOrNameMatchRegExp(fnode.element, yearRegExp),
+ idOrNameMatchExpMonthRegExp: fnode =>
+ idOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ idOrNameMatchExpYearRegExp: fnode =>
+ idOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ idOrNameMatchValidation: fnode =>
+ idOrNameMatchRegExp(fnode.element, /validate|validation/i),
+ }),
+ rule(type("cc-exp"), out("cc-exp")),
+
+ /**
+ * expirationMonth rules
+ */
+ rule(type("typicalCandidates"), type("cc-exp-month")),
+ ...simpleScoringRules("cc-exp-month", {
+ idOrNameMatchExpMonthRegExp: fnode =>
+ idOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ labelsMatchExpMonthRegExp: fnode =>
+ labelsMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ closestLabelMatchesExpMonthRegExp: fnode =>
+ closestLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ placeholderMatchesExpMonthRegExp: fnode =>
+ placeholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ ariaLabelMatchesExpMonthRegExp: fnode =>
+ ariaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ idOrNameMatchMonth: fnode =>
+ idOrNameMatchRegExp(fnode.element, monthRegExp),
+ labelsMatchMonth: fnode =>
+ labelsMatchRegExp(fnode.element, monthRegExp),
+ placeholderMatchesMonth: fnode =>
+ placeholderMatchesRegExp(fnode.element, monthRegExp),
+ ariaLabelMatchesMonth: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, monthRegExp),
+ nextFieldIdOrNameMatchExpYearRegExp: fnode =>
+ nextFieldIdOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ nextFieldLabelsMatchExpYearRegExp: fnode =>
+ nextFieldLabelsMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ nextFieldPlaceholderMatchExpYearRegExp: fnode =>
+ nextFieldPlaceholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ nextFieldAriaLabelMatchExpYearRegExp: fnode =>
+ nextFieldAriaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ nextFieldIdOrNameMatchYear: fnode =>
+ nextFieldIdOrNameMatchRegExp(fnode.element, yearRegExp),
+ nextFieldLabelsMatchYear: fnode =>
+ nextFieldLabelsMatchRegExp(fnode.element, yearRegExp),
+ nextFieldPlaceholderMatchesYear: fnode =>
+ nextFieldPlaceholderMatchesRegExp(fnode.element, yearRegExp),
+ nextFieldAriaLabelMatchesYear: fnode =>
+ nextFieldAriaLabelMatchesRegExp(fnode.element, yearRegExp),
+ nextFieldMatchesExpYearAutocomplete,
+ isExpirationMonthLikely: fnode =>
+ isExpirationMonthLikely(fnode.element),
+ nextFieldIsExpirationYearLikely,
+ maxLengthIs2: fnode => maxLengthIs(fnode, 2),
+ placeholderMatchesMM: fnode =>
+ placeholderMatchesRegExp(fnode.element, MMRegExp),
+ roleIsMenu,
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ }),
+ rule(type("cc-exp-month"), out("cc-exp-month")),
+
+ /**
+ * expirationYear rules
+ */
+ rule(type("typicalCandidates"), type("cc-exp-year")),
+ ...simpleScoringRules("cc-exp-year", {
+ idOrNameMatchExpYearRegExp: fnode =>
+ idOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ labelsMatchExpYearRegExp: fnode =>
+ labelsMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ closestLabelMatchesExpYearRegExp: fnode =>
+ closestLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ placeholderMatchesExpYearRegExp: fnode =>
+ placeholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ ariaLabelMatchesExpYearRegExp: fnode =>
+ ariaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-year"]
+ ),
+ idOrNameMatchYear: fnode =>
+ idOrNameMatchRegExp(fnode.element, yearRegExp),
+ labelsMatchYear: fnode => labelsMatchRegExp(fnode.element, yearRegExp),
+ placeholderMatchesYear: fnode =>
+ placeholderMatchesRegExp(fnode.element, yearRegExp),
+ ariaLabelMatchesYear: fnode =>
+ ariaLabelMatchesRegExp(fnode.element, yearRegExp),
+ previousFieldIdOrNameMatchExpMonthRegExp: fnode =>
+ previousFieldIdOrNameMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ previousFieldLabelsMatchExpMonthRegExp: fnode =>
+ previousFieldLabelsMatchRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ previousFieldPlaceholderMatchExpMonthRegExp: fnode =>
+ previousFieldPlaceholderMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ previousFieldAriaLabelMatchExpMonthRegExp: fnode =>
+ previousFieldAriaLabelMatchesRegExp(
+ fnode.element,
+ FathomHeuristicsRegExp.RULES["cc-exp-month"]
+ ),
+ previousFieldIdOrNameMatchMonth: fnode =>
+ previousFieldIdOrNameMatchRegExp(fnode.element, monthRegExp),
+ previousFieldLabelsMatchMonth: fnode =>
+ previousFieldLabelsMatchRegExp(fnode.element, monthRegExp),
+ previousFieldPlaceholderMatchesMonth: fnode =>
+ previousFieldPlaceholderMatchesRegExp(fnode.element, monthRegExp),
+ previousFieldAriaLabelMatchesMonth: fnode =>
+ previousFieldAriaLabelMatchesRegExp(fnode.element, monthRegExp),
+ previousFieldMatchesExpMonthAutocomplete,
+ isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
+ previousFieldIsExpirationMonthLikely,
+ placeholderMatchesYYOrYYYY: fnode =>
+ placeholderMatchesRegExp(fnode.element, YYorYYYYRegExp),
+ roleIsMenu,
+ idOrNameMatchSubscription: fnode =>
+ idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
+ idOrNameMatchDwfrmAndBml,
+ hasTemplatedValue,
+ }),
+ rule(type("cc-exp-year"), out("cc-exp-year")),
+ ],
+ coeffs,
+ biases
+ );
+}
+
+const coefficients = {
+ "cc-number": [
+ ["idOrNameMatchNumberRegExp", 7.679469585418701],
+ ["labelsMatchNumberRegExp", 5.122580051422119],
+ ["closestLabelMatchesNumberRegExp", 2.1256935596466064],
+ ["placeholderMatchesNumberRegExp", 9.471800804138184],
+ ["ariaLabelMatchesNumberRegExp", 6.067715644836426],
+ ["idOrNameMatchGift", -22.946273803710938],
+ ["labelsMatchGift", -7.852959632873535],
+ ["placeholderMatchesGift", -2.355496406555176],
+ ["ariaLabelMatchesGift", -2.940307855606079],
+ ["idOrNameMatchSubscription", 0.11255314946174622],
+ ["idOrNameMatchDwfrmAndBml", -0.0006645023822784424],
+ ["hasTemplatedValue", -0.11370040476322174],
+ ["inputTypeNotNumbery", -3.750155210494995]
+ ],
+ "cc-name": [
+ ["idOrNameMatchNameRegExp", 7.496212959289551],
+ ["labelsMatchNameRegExp", 6.081472873687744],
+ ["closestLabelMatchesNameRegExp", 2.600574254989624],
+ ["placeholderMatchesNameRegExp", 5.750874042510986],
+ ["ariaLabelMatchesNameRegExp", 5.162227153778076],
+ ["idOrNameMatchFirst", -6.742659091949463],
+ ["labelsMatchFirst", -0.5234538912773132],
+ ["placeholderMatchesFirst", -3.4615235328674316],
+ ["ariaLabelMatchesFirst", -1.3145145177841187],
+ ["idOrNameMatchLast", -12.561869621276855],
+ ["labelsMatchLast", -0.27417105436325073],
+ ["placeholderMatchesLast", -1.434966802597046],
+ ["ariaLabelMatchesLast", -2.9319725036621094],
+ ["idOrNameMatchFirstAndLast", 24.123435974121094],
+ ["idOrNameMatchSubscription", 0.08349418640136719],
+ ["idOrNameMatchDwfrmAndBml", 0.01882520318031311],
+ ["hasTemplatedValue", 0.182317852973938]
+ ],
+ "cc-type": [
+ ["idOrNameMatchTypeRegExp", 2.0581533908843994],
+ ["labelsMatchTypeRegExp", 1.0784518718719482],
+ ["closestLabelMatchesTypeRegExp", 0.6995877623558044],
+ ["idOrNameMatchVisaCheckout", -3.320356845855713],
+ ["ariaLabelMatchesVisaCheckout", -3.4196767807006836],
+ ["isSelectWithCreditCardOptions", 10.337477684020996],
+ ["isRadioWithCreditCardText", 4.530318737030029],
+ ["idOrNameMatchSubscription", -3.7206356525421143],
+ ["idOrNameMatchDwfrmAndBml", -0.08782318234443665],
+ ["hasTemplatedValue", 0.1772511601448059]
+ ],
+ "cc-exp": [
+ ["labelsMatchExpRegExp", 7.588159561157227],
+ ["closestLabelMatchesExpRegExp", 1.41484534740448],
+ ["placeholderMatchesExpRegExp", 8.759064674377441],
+ ["labelsMatchExpWith2Or4DigitYear", -3.876218795776367],
+ ["placeholderMatchesExpWith2Or4DigitYear", 2.8364884853363037],
+ ["labelsMatchMMYY", 8.836017608642578],
+ ["placeholderMatchesMMYY", -0.5231751799583435],
+ ["maxLengthIs7", 1.3565447330474854],
+ ["idOrNameMatchSubscription", 0.1779913753271103],
+ ["idOrNameMatchDwfrmAndBml", 0.21037884056568146],
+ ["hasTemplatedValue", 0.14900512993335724],
+ ["isExpirationMonthLikely", -3.223409652709961],
+ ["isExpirationYearLikely", -2.536919593811035],
+ ["idOrNameMatchMonth", -3.6893014907836914],
+ ["idOrNameMatchYear", -3.108184337615967],
+ ["idOrNameMatchExpMonthRegExp", -2.264357089996338],
+ ["idOrNameMatchExpYearRegExp", -2.7957723140716553],
+ ["idOrNameMatchValidation", -2.29402756690979]
+ ],
+ "cc-exp-month": [
+ ["idOrNameMatchExpMonthRegExp", 0.2787344455718994],
+ ["labelsMatchExpMonthRegExp", 1.298413634300232],
+ ["closestLabelMatchesExpMonthRegExp", -11.206244468688965],
+ ["placeholderMatchesExpMonthRegExp", 1.2605619430541992],
+ ["ariaLabelMatchesExpMonthRegExp", 1.1330018043518066],
+ ["idOrNameMatchMonth", 6.1464314460754395],
+ ["labelsMatchMonth", 0.7051732540130615],
+ ["placeholderMatchesMonth", 0.7463492751121521],
+ ["ariaLabelMatchesMonth", 1.8244760036468506],
+ ["nextFieldIdOrNameMatchExpYearRegExp", 0.06347066164016724],
+ ["nextFieldLabelsMatchExpYearRegExp", -0.1692247837781906],
+ ["nextFieldPlaceholderMatchExpYearRegExp", 1.0434566736221313],
+ ["nextFieldAriaLabelMatchExpYearRegExp", 1.751156210899353],
+ ["nextFieldIdOrNameMatchYear", -0.532447338104248],
+ ["nextFieldLabelsMatchYear", 1.3248541355133057],
+ ["nextFieldPlaceholderMatchesYear", 0.604235827922821],
+ ["nextFieldAriaLabelMatchesYear", 1.5364223718643188],
+ ["nextFieldMatchesExpYearAutocomplete", 6.285938262939453],
+ ["isExpirationMonthLikely", 13.117807388305664],
+ ["nextFieldIsExpirationYearLikely", 7.182341575622559],
+ ["maxLengthIs2", 4.477289199829102],
+ ["placeholderMatchesMM", 14.403288841247559],
+ ["roleIsMenu", 5.770959854125977],
+ ["idOrNameMatchSubscription", -0.043085768818855286],
+ ["idOrNameMatchDwfrmAndBml", 0.02823038399219513],
+ ["hasTemplatedValue", 0.07234494388103485]
+ ],
+ "cc-exp-year": [
+ ["idOrNameMatchExpYearRegExp", 5.426016807556152],
+ ["labelsMatchExpYearRegExp", 1.3240209817886353],
+ ["closestLabelMatchesExpYearRegExp", -8.702284812927246],
+ ["placeholderMatchesExpYearRegExp", 0.9059725999832153],
+ ["ariaLabelMatchesExpYearRegExp", 0.5550334453582764],
+ ["idOrNameMatchYear", 5.362994194030762],
+ ["labelsMatchYear", 2.7185044288635254],
+ ["placeholderMatchesYear", 0.7883157134056091],
+ ["ariaLabelMatchesYear", 0.311492383480072],
+ ["previousFieldIdOrNameMatchExpMonthRegExp", 1.8155208826065063],
+ ["previousFieldLabelsMatchExpMonthRegExp", -0.46133187413215637],
+ ["previousFieldPlaceholderMatchExpMonthRegExp", 1.0374903678894043],
+ ["previousFieldAriaLabelMatchExpMonthRegExp", -0.5901495814323425],
+ ["previousFieldIdOrNameMatchMonth", -5.960310935974121],
+ ["previousFieldLabelsMatchMonth", 0.6495584845542908],
+ ["previousFieldPlaceholderMatchesMonth", 0.7198042273521423],
+ ["previousFieldAriaLabelMatchesMonth", 3.4590985774993896],
+ ["previousFieldMatchesExpMonthAutocomplete", 2.986003875732422],
+ ["isExpirationYearLikely", 4.021566390991211],
+ ["previousFieldIsExpirationMonthLikely", 9.298635482788086],
+ ["placeholderMatchesYYOrYYYY", 10.457176208496094],
+ ["roleIsMenu", 1.1051956415176392],
+ ["idOrNameMatchSubscription", 0.000688597559928894],
+ ["idOrNameMatchDwfrmAndBml", 0.15687309205532074],
+ ["hasTemplatedValue", -0.19141331315040588]
+ ],
+};
+
+const biases = [
+ ["cc-number", -4.948795795440674],
+ ["cc-name", -5.3578081130981445],
+ ["cc-type", -5.979659557342529],
+ ["cc-exp", -5.849575996398926],
+ ["cc-exp-month", -8.844199180603027],
+ ["cc-exp-year", -6.499860763549805],
+];
+
+/**
+ * END OF CODE PASTED FROM TRAINING REPOSITORY
+ */
+
+/**
+ * MORE CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
+ */
+// Currently there is a bug when a ruleset has multple types (ex, cc-name, cc-number)
+// and those types also has the same rules (ex. rule `hasTemplatedValue` is used in
+// all the tyoes). When the above case exists, the coefficient of the rule will be
+// overwritten, which means, we can't have different coefficient for the same rule on
+// different types. To workaround this issue, we create a new ruleset for each type.
+export var CreditCardRulesets = {
+ init() {
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "supportedTypes",
+ "extensions.formautofill.creditCards.heuristics.fathom.types",
+ null,
+ null,
+ val => val.split(",")
+ );
+
+ for (const type of this.types) {
+ this[type] = makeRuleset([...coefficients[type]], biases);
+ }
+ },
+
+ get types() {
+ return this.supportedTypes;
+ },
+};
+
+CreditCardRulesets.init();
+
+export default CreditCardRulesets;