/* 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" + "|^(credit[-\\s]?card|card).*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" + // es-ES "|nombre.*(titular|tarjeta)" + // nl-NL "|naam.*op.*kaart" + // 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" + "|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" + // es-ES "|(número|numero).*tarjeta" + // nl-NL "|kaartnummer" + // 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 , and, if so, check the textContent of the containing if (parentElement.tagName === "TD" && parentElement.parentElement) { // TODO: How bad is the assumption that the won't be the parent of the ? return regExp.test(parentElement.parentElement.textContent); } // Check if the input is in a
, and, if so, check the textContent of the preceding
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;