forked from mirrors/gecko-dev
Support calling cc heuristic with 3 options: 1. Old regular expression matching heuristic 2. Fathom JS implementation 3. Fathom Native implementation Depends on D137270 Differential Revision: https://phabricator.services.mozilla.com/D137274
1201 lines
40 KiB
JavaScript
1201 lines
40 KiB
JavaScript
/* 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.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
|
|
*/
|
|
|
|
const EXPORTED_SYMBOLS = ["creditCardRulesets"];
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"fathom",
|
|
"resource://gre/modules/third_party/fathom/fathom.jsm"
|
|
);
|
|
const {
|
|
element: clickedElement,
|
|
exceptions: { NoWindowError },
|
|
out,
|
|
rule,
|
|
ruleset,
|
|
score,
|
|
type,
|
|
utils: { isVisible },
|
|
} = fathom;
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"FormLikeFactory",
|
|
"resource://gre/modules/FormLikeFactory.jsm"
|
|
);
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"FormAutofillUtils",
|
|
"resource://autofill/FormAutofillUtils.jsm"
|
|
);
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
CreditCard: "resource://gre/modules/CreditCard.jsm",
|
|
NETWORK_NAMES: "resource://gre/modules/CreditCard.jsm",
|
|
LabelUtils: "resource://autofill/FormAutofillUtils.jsm",
|
|
});
|
|
|
|
/**
|
|
* RegExp copied from HeuristicsRegExp with the following modification:
|
|
* 1. Remove rulesets that are not supported by fathom
|
|
* 2. Remove rules that worsen the accuracy
|
|
*/
|
|
let FathomHeuristicsRegExp;
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
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.isFieldEligibleForAutofill(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) {
|
|
// LabelMap has to be cleared to ensure the result is not
|
|
// affected by the previous call.
|
|
LabelUtils.clearLabelMap();
|
|
|
|
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;
|
|
}
|
|
}
|
|
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 true;
|
|
}
|
|
|
|
function isNotVisible(fnode) {
|
|
try {
|
|
return !isVisible(fnode);
|
|
} catch (error) {
|
|
if (error instanceof NoWindowError) {
|
|
// This case should happen only during xpcshell tests that don't even
|
|
// care about the info emitted from Fathom. Nevertheless, this is the
|
|
// most likely truthful return value.
|
|
return false;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
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"]
|
|
),
|
|
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,
|
|
isNotVisible,
|
|
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"]
|
|
),
|
|
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,
|
|
isNotVisible,
|
|
}),
|
|
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"]
|
|
),
|
|
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"]
|
|
),
|
|
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"]
|
|
),
|
|
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"]
|
|
),
|
|
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", 6.3039140701293945],
|
|
["labelsMatchNumberRegExp", 2.8916432857513428],
|
|
["placeholderMatchesNumberRegExp", 5.505742073059082],
|
|
["ariaLabelMatchesNumberRegExp", 4.561432361602783],
|
|
["idOrNameMatchGift", -3.803224563598633],
|
|
["labelsMatchGift", -4.524861812591553],
|
|
["placeholderMatchesGift", -1.8712525367736816],
|
|
["ariaLabelMatchesGift", -3.674055576324463],
|
|
["idOrNameMatchSubscription", -0.7810876369476318],
|
|
["idOrNameMatchDwfrmAndBml", -1.095906138420105],
|
|
["hasTemplatedValue", -6.368256568908691],
|
|
["isNotVisible", -3.0330028533935547],
|
|
["inputTypeNotNumbery", -2.300889253616333],
|
|
],
|
|
"cc-name": [
|
|
["idOrNameMatchNameRegExp", 7.953818321228027],
|
|
["labelsMatchNameRegExp", 11.784907341003418],
|
|
["placeholderMatchesNameRegExp", 9.202799797058105],
|
|
["ariaLabelMatchesNameRegExp", 9.627416610717773],
|
|
["idOrNameMatchFirst", -6.200107574462891],
|
|
["labelsMatchFirst", -14.77401065826416],
|
|
["placeholderMatchesFirst", -10.258772850036621],
|
|
["ariaLabelMatchesFirst", -2.1574606895446777],
|
|
["idOrNameMatchLast", -5.508854389190674],
|
|
["labelsMatchLast", -14.563374519348145],
|
|
["placeholderMatchesLast", -8.281961441040039],
|
|
["ariaLabelMatchesLast", -7.915995121002197],
|
|
["idOrNameMatchFirstAndLast", 17.586633682250977],
|
|
["idOrNameMatchSubscription", -1.3862149715423584],
|
|
["idOrNameMatchDwfrmAndBml", -1.154863953590393],
|
|
["hasTemplatedValue", -1.3476886749267578],
|
|
["isNotVisible", -20.619457244873047],
|
|
],
|
|
"cc-type": [
|
|
["idOrNameMatchTypeRegExp", 2.815537691116333],
|
|
["labelsMatchTypeRegExp", -2.6969387531280518],
|
|
["idOrNameMatchVisaCheckout", -4.888851165771484],
|
|
["ariaLabelMatchesVisaCheckout", -5.0021514892578125],
|
|
["isSelectWithCreditCardOptions", 7.633410453796387],
|
|
["isRadioWithCreditCardText", 9.72647762298584],
|
|
["idOrNameMatchSubscription", -2.540968179702759],
|
|
["idOrNameMatchDwfrmAndBml", -2.4342823028564453],
|
|
["hasTemplatedValue", -2.134981155395508],
|
|
],
|
|
"cc-exp": [
|
|
["labelsMatchExpRegExp", 7.235990524291992],
|
|
["placeholderMatchesExpRegExp", 3.7828152179718018],
|
|
["labelsMatchExpWith2Or4DigitYear", 3.28702449798584],
|
|
["placeholderMatchesExpWith2Or4DigitYear", 0.9417413473129272],
|
|
["labelsMatchMMYY", 8.527382850646973],
|
|
["placeholderMatchesMMYY", 6.976727485656738],
|
|
["maxLengthIs7", -1.6640985012054443],
|
|
["idOrNameMatchSubscription", -1.7390238046646118],
|
|
["idOrNameMatchDwfrmAndBml", -1.8697377443313599],
|
|
["hasTemplatedValue", -2.2890148162841797],
|
|
// eslint-disable-next-line no-loss-of-precision
|
|
["isExpirationMonthLikely", -2.7287368774414062],
|
|
["isExpirationYearLikely", -2.1379034519195557],
|
|
["idOrNameMatchMonth", -2.9298980236053467],
|
|
["idOrNameMatchYear", -2.423668622970581],
|
|
["idOrNameMatchExpMonthRegExp", -2.224165916442871],
|
|
["idOrNameMatchExpYearRegExp", -2.4124796390533447],
|
|
["idOrNameMatchValidation", -6.64445686340332],
|
|
],
|
|
"cc-exp-month": [
|
|
["idOrNameMatchExpMonthRegExp", 3.1759495735168457],
|
|
["labelsMatchExpMonthRegExp", 0.6333072781562805],
|
|
["placeholderMatchesExpMonthRegExp", -1.0211261510849],
|
|
["ariaLabelMatchesExpMonthRegExp", -0.12013287842273712],
|
|
["idOrNameMatchMonth", 0.8069844245910645],
|
|
["labelsMatchMonth", 2.8041117191314697],
|
|
["placeholderMatchesMonth", -0.7963107228279114],
|
|
["ariaLabelMatchesMonth", -0.18894313275814056],
|
|
["nextFieldIdOrNameMatchExpYearRegExp", 1.3703272342681885],
|
|
["nextFieldLabelsMatchExpYearRegExp", 0.4734393060207367],
|
|
["nextFieldPlaceholderMatchExpYearRegExp", -0.9648597240447998],
|
|
["nextFieldAriaLabelMatchExpYearRegExp", 2.3334436416625977],
|
|
["nextFieldIdOrNameMatchYear", 0.7225953936576843],
|
|
["nextFieldLabelsMatchYear", 0.47795572876930237],
|
|
["nextFieldPlaceholderMatchesYear", -1.032015085220337],
|
|
["nextFieldAriaLabelMatchesYear", 2.5017199516296387],
|
|
["nextFieldMatchesExpYearAutocomplete", 1.4952502250671387],
|
|
["isExpirationMonthLikely", 5.659104347229004],
|
|
["nextFieldIsExpirationYearLikely", 2.5078020095825195],
|
|
["maxLengthIs2", -0.5410940051078796],
|
|
["placeholderMatchesMM", 7.3071208000183105],
|
|
["roleIsMenu", 5.595693111419678],
|
|
["idOrNameMatchSubscription", -5.626739978790283],
|
|
["idOrNameMatchDwfrmAndBml", -7.236949920654297],
|
|
["hasTemplatedValue", -6.055515289306641],
|
|
],
|
|
"cc-exp-year": [
|
|
["idOrNameMatchExpYearRegExp", 2.456799268722534],
|
|
["labelsMatchExpYearRegExp", 0.9488120675086975],
|
|
["placeholderMatchesExpYearRegExp", -0.6318328380584717],
|
|
["ariaLabelMatchesExpYearRegExp", -0.16433487832546234],
|
|
["idOrNameMatchYear", 2.0227997303009033],
|
|
["labelsMatchYear", 0.7777050733566284],
|
|
["placeholderMatchesYear", -0.6191908121109009],
|
|
["ariaLabelMatchesYear", -0.5337049961090088],
|
|
["previousFieldIdOrNameMatchExpMonthRegExp", 0.2529127597808838],
|
|
["previousFieldLabelsMatchExpMonthRegExp", 0.5853790044784546],
|
|
["previousFieldPlaceholderMatchExpMonthRegExp", -0.710956871509552],
|
|
["previousFieldAriaLabelMatchExpMonthRegExp", 2.2874839305877686],
|
|
["previousFieldIdOrNameMatchMonth", -1.99709153175354],
|
|
["previousFieldLabelsMatchMonth", 1.114603042602539],
|
|
["previousFieldPlaceholderMatchesMonth", -0.4987318515777588],
|
|
["previousFieldAriaLabelMatchesMonth", 2.1683783531188965],
|
|
["previousFieldMatchesExpMonthAutocomplete", 1.2016327381134033],
|
|
["isExpirationYearLikely", 5.7863616943359375],
|
|
["previousFieldIsExpirationMonthLikely", 6.4013848304748535],
|
|
["placeholderMatchesYYOrYYYY", 8.81661605834961],
|
|
["roleIsMenu", 3.7794034481048584],
|
|
["idOrNameMatchSubscription", -4.7467498779296875],
|
|
["idOrNameMatchDwfrmAndBml", -5.523425102233887],
|
|
["hasTemplatedValue", -6.14529275894165],
|
|
],
|
|
};
|
|
|
|
const biases = [
|
|
["cc-number", -4.422344207763672],
|
|
["cc-name", -5.876968860626221],
|
|
["cc-type", -5.410860061645508],
|
|
["cc-exp", -5.439330577850342],
|
|
["cc-exp-month", -5.99984073638916],
|
|
["cc-exp-year", -6.0192646980285645],
|
|
];
|
|
|
|
/**
|
|
* 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.
|
|
this.creditCardRulesets = {
|
|
init() {
|
|
for (const type of this.types) {
|
|
this[type] = makeRuleset([...coefficients[type]], biases);
|
|
}
|
|
},
|
|
|
|
get types() {
|
|
return [
|
|
"cc-name",
|
|
"cc-number",
|
|
"cc-exp-month",
|
|
"cc-exp-year",
|
|
"cc-exp",
|
|
"cc-type",
|
|
];
|
|
},
|
|
};
|
|
this.creditCardRulesets.init();
|
|
|
|
FathomHeuristicsRegExp = {
|
|
RULES: {
|
|
"cc-name": undefined,
|
|
"cc-number": undefined,
|
|
"cc-exp-month": undefined,
|
|
"cc-exp-year": undefined,
|
|
"cc-exp": undefined,
|
|
"cc-type": undefined,
|
|
},
|
|
|
|
RULE_SETS: [
|
|
//=========================================================================
|
|
// Firefox-specific rules
|
|
// TODO: Bug 1755256, consider adding `titulaire` and `kartenmarke`.
|
|
{
|
|
"cc-name": "account.*holder.*name",
|
|
"cc-number": "(cc|kk)nr", // de-DE
|
|
"cc-exp-month": "(cc|kk)month", // de-DE
|
|
"cc-exp-year": "(cc|kk)year", // de-DE
|
|
"cc-type": "type",
|
|
},
|
|
|
|
//=========================================================================
|
|
// These are the rules used by Bitwarden [0], converted into RegExp form.
|
|
// [0] https://github.com/bitwarden/browser/blob/c2b8802201fac5e292d55d5caf3f1f78088d823c/src/services/autofill.service.ts#L436
|
|
{
|
|
/* eslint-disable */
|
|
// Let us keep our consistent wrapping.
|
|
"cc-name":
|
|
"cc-?name" +
|
|
"|card-?name" +
|
|
"|cardholder-?name" +
|
|
"|(^nom$)",
|
|
/* eslint-enable */
|
|
|
|
"cc-number":
|
|
"cc-?number" +
|
|
"|cc-?num" +
|
|
"|card-?number" +
|
|
"|card-?num" +
|
|
"|cc-?no" +
|
|
"|card-?no" +
|
|
"|numero-?carte" +
|
|
"|num-?carte" +
|
|
"|cb-?num",
|
|
|
|
"cc-exp":
|
|
"(^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$)",
|
|
|
|
"cc-exp-month":
|
|
"(^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$)",
|
|
|
|
"cc-exp-year":
|
|
"(^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$)",
|
|
|
|
"cc-type":
|
|
"(^cc-?type$)" +
|
|
"|(^card-?type$)" +
|
|
"|(^card-?brand$)" +
|
|
"|(^cc-?brand$)" +
|
|
"|(^cb-?type$)",
|
|
},
|
|
|
|
//=========================================================================
|
|
// These rules are from Chromium source codes [1]. Most of them
|
|
// converted to JS format have the same meaning with the original ones except
|
|
// the first line of "address-level1".
|
|
// [1] https://source.chromium.org/chromium/chromium/src/+/master:components/autofill/core/common/autofill_regex_constants.cc
|
|
{
|
|
// ==== Credit Card Fields ====
|
|
"cc-name":
|
|
"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":
|
|
"(add)?(?:card|cc|acct).?(?:number|#|no|num)" +
|
|
"|(?<!telefon|haus|person|fødsels|zimmer)nummer" + // de-DE, sv-SE, no
|
|
"|カード番号" + // ja-JP
|
|
"|Номер.*карты" + // ru
|
|
"|信用卡号|信用卡号码" + // zh-CN
|
|
"|信用卡卡號" + // zh-TW
|
|
"|카드" + // ko-KR
|
|
// es/pt/fr
|
|
"|(numero|número|numéro)(?!.*(document|fono|phone|réservation))",
|
|
|
|
"cc-exp-month":
|
|
"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":
|
|
"(add)?year" +
|
|
"|jahr" + // de-DE
|
|
// "|fecha" + // es
|
|
// "|scadenza" + // it-IT
|
|
// "|有効期限" + // ja-JP
|
|
// "|validade" + // pt-BR, pt-PT
|
|
// "|Срок действия карты" + // ru
|
|
"|年|有效期", // zh-CN
|
|
|
|
"cc-exp":
|
|
"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
|
|
},
|
|
],
|
|
|
|
_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();
|