1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 * Fathom ML model for identifying the fields of credit-card forms
8 * This is developed out-of-tree at https://github.com/mozilla-services/fathom-
9 * form-autofill, where there is also over a GB of training, validation, and
10 * testing data. To make changes, do your edits there (whether adding new
11 * training pages, adding new rules, or both), retrain and evaluate as
12 * documented at https://mozilla.github.io/fathom/training.html, paste the
13 * coefficients emitted by the trainer into the ruleset, and finally copy the
14 * ruleset's "CODE TO COPY INTO PRODUCTION" section to this file's "CODE FROM
15 * TRAINING REPOSITORY" section.
19 * CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
23 element as clickedElement,
29 } from "resource://gre/modules/third_party/fathom/fathom.mjs";
30 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
31 import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
35 } from "resource://gre/modules/CreditCard.sys.mjs";
37 import { FormLikeFactory } from "resource://gre/modules/FormLikeFactory.sys.mjs";
38 import { LabelUtils } from "resource://gre/modules/shared/LabelUtils.sys.mjs";
41 * Callthrough abstraction to allow .getAutocompleteInfo() to be mocked out
44 * @param {Element} element DOM element to get info about
45 * @returns {object} Page-author-provided autocomplete metadata
47 function getAutocompleteInfo(element) {
48 return element.getAutocompleteInfo();
52 * @param {string} selector A CSS selector that prunes away ineligible elements
53 * @returns {Lhs} An LHS yielding the element the user has clicked or, if
56 function queriedOrClickedElements(selector) {
57 return clickedElement(selector);
61 * START OF CODE PASTED FROM TRAINING REPOSITORY
64 var FathomHeuristicsRegExp = {
67 "cc-number": undefined,
68 "cc-exp-month": undefined,
69 "cc-exp-year": undefined,
77 // Let us keep our consistent wrapping.
79 // Firefox-specific rules
80 "account.*holder.*name" +
81 "|^(credit[-\\s]?card|card).*name" +
83 "|^(kredit)?(karten|konto)inhaber" +
86 "|nom.*(titulaire|détenteur)" +
87 "|(titulaire|détenteur).*(carte)" +
93 "|nombre.*(titular|tarjeta)" +
96 // Rules from Bitwarden
101 // Rules are from Chromium source codes
102 "|card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" +
103 "|(?:card|cc).?name|cc.?full.?name" +
104 "|(?:card|cc).?owner" +
105 "|nom.*carte" + // fr-FR
106 "|nome.*cart" + // it-IT
108 "|Имя.*карты" + // ru
109 "|信用卡开户名|开户名|持卡人姓名" + // zh-CN
113 // Firefox-specific rules
116 "|(kredit)?(karten)(nummer|nr)" +
120 "|(numero|número|numéro).*(carte)" +
124 "|(número|numero).*tarjeta" +
127 // Rules from Bitwarden
137 // Rules are from Chromium source codes
138 "|(add)?(?:card|cc|acct).?(?:number|#|no|num)" +
140 "|Номер.*карты" + // ru
141 "|信用卡号|信用卡号码" + // zh-CN
146 // Firefox-specific rules
147 "mm\\s*(\/|\\|-)\\s*(yy|jj|aa)" +
148 "|(month|mois)\\s*(\/|\\|-|et)\\s*(year|année)" +
151 // Rules from Bitwarden
154 "|(^cc-?expiration$)" +
155 "|(^card-?expiration$)" +
158 "|(^card-?expire$)" +
159 "|(^card-?expiry$)" +
167 "|expiration-?date" +
168 "|payment-?card-?expiration" +
169 "|(^payment-?cc-?date$)" +
170 // Rules are from Chromium source codes
171 "|expir|exp.*date|^expfield$" +
172 "|ablaufdatum|gueltig|gültig" + // de-DE
174 "|date.*exp" + // fr-FR
175 "|scadenza" + // it-IT
177 "|validade" + // pt-BR, pt-PT
178 "|Срок действия карты", // ru
181 // Firefox-specific rules
182 "(cc|kk)month" + // de-DE
183 // Rules from Bitwarden
185 "|(^cc-?exp-?month$)" +
191 "|(^card-?exp-?mo$)" +
193 "|(^card-?expiration-?month$)" +
194 "|(^expiration-?month$)" +
199 "|(^card-?exp-?mm$)" +
203 "|(^expire-?month$)" +
205 "|(^expiry-?month$)" +
207 "|(^card-?expire-?month$)" +
208 "|(^card-?expire-?mo$)" +
209 "|(^card-?expiry-?month$)" +
210 "|(^card-?expiry-?mo$)" +
211 "|(^mois-?validite$)" +
212 "|(^mois-?expiration$)" +
214 "|(^m-?expiration$)" +
215 "|(^expiry-?date-?field-?month$)" +
216 "|(^expiration-?date-?month$)" +
217 "|(^expiration-?date-?mm$)" +
219 "|(^validity-?mo$)" +
220 "|(^exp-?date-?mo$)" +
221 "|(^cb-?date-?mois$)" +
223 // Rules are from Chromium source codes
224 "|exp.*mo|ccmonth|cardmonth|addmonth" +
227 // "|date.*exp" + // fr-FR
228 // "|scadenza" + // it-IT
229 // "|有効期限" + // ja-JP
230 // "|validade" + // pt-BR, pt-PT
231 // "|Срок действия карты" + // ru
235 // Firefox-specific rules
236 "(cc|kk)year" + // de-DE
237 // Rules from Bitwarden
239 "|(^cc-?exp-?year$)" +
245 "|(^card-?exp-?yr$)" +
247 "|(^card-?expiration-?year$)" +
248 "|(^expiration-?year$)" +
253 "|(^card-?exp-?yy$)" +
259 "|(^card-?exp-?yyyy$)" +
260 "|(^cc-?exp-?yyyy$)" +
261 "|(^expire-?year$)" +
263 "|(^expiry-?year$)" +
265 "|(^card-?expire-?year$)" +
266 "|(^card-?expire-?yr$)" +
267 "|(^card-?expiry-?year$)" +
268 "|(^card-?expiry-?yr$)" +
269 "|(^an-?validite$)" +
270 "|(^an-?expiration$)" +
271 "|(^annee-?validite$)" +
272 "|(^annee-?expiration$)" +
273 "|(^expiry-?date-?field-?year$)" +
274 "|(^expiration-?date-?year$)" +
275 "|(^cb-?date-?ann$)" +
276 "|(^expiration-?date-?yy$)" +
277 "|(^expiration-?date-?yyyy$)" +
278 "|(^validity-?year$)" +
279 "|(^exp-?date-?year$)" +
281 // Rules are from Chromium source codes
285 // "|scadenza" + // it-IT
286 // "|有効期限" + // ja-JP
287 // "|validade" + // pt-BR, pt-PT
288 // "|Срок действия карты" + // ru
292 // Firefox-specific rules
296 // Rules from Bitwarden
302 // Rules are from Chromium source codes
308 this.RULE_SETS.forEach(set => {
310 rules.push(`(${set[name]})`.normalize("NFKC"));
314 const value = new RegExp(rules.join("|"), "iu");
315 Object.defineProperty(this.RULES, name, { get: undefined });
316 Object.defineProperty(this.RULES, name, { value });
321 Object.keys(this.RULES).forEach(field =>
322 Object.defineProperty(this.RULES, field, {
324 return FathomHeuristicsRegExp._getRule(field);
331 FathomHeuristicsRegExp.init();
333 const MMRegExp = /^mm$|\(mm\)/i;
334 const YYorYYYYRegExp = /^(yy|yyyy)$|\(yy\)|\(yyyy\)/i;
335 const monthRegExp = /month/i;
336 const yearRegExp = /year/i;
337 const MMYYRegExp = /mm\s*(\/|\\)\s*yy/i;
338 const VisaCheckoutRegExp = /visa(-|\s)checkout/i;
339 const CREDIT_CARD_NETWORK_REGEXP = new RegExp(
340 CreditCard.getSupportedNetworks()
341 .concat(Object.keys(NETWORK_NAMES))
345 const TwoDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yy(?:[^y]|$)/i;
346 const FourDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yyyy(?:[^y]|$)/i;
347 const dwfrmRegExp = /^dwfrm/i;
348 const bmlRegExp = /bml/i;
349 const templatedValue = /^\{\{.*\}\}$/;
350 const firstRegExp = /first/i;
351 const lastRegExp = /last/i;
352 const giftRegExp = /gift/i;
353 const subscriptionRegExp = /subscription/i;
355 function autocompleteStringMatches(element, ccString) {
356 const info = getAutocompleteInfo(element);
357 return info.fieldName === ccString;
360 function getFillableFormElements(element) {
361 const formLike = FormLikeFactory.createFromField(element);
362 return Array.from(formLike.elements).filter(el =>
363 FormAutofillUtils.isCreditCardOrAddressFieldType(el)
367 function nextFillableFormField(element) {
368 const fillableFormElements = getFillableFormElements(element);
369 const elementIndex = fillableFormElements.indexOf(element);
370 return fillableFormElements[elementIndex + 1];
373 function previousFillableFormField(element) {
374 const fillableFormElements = getFillableFormElements(element);
375 const elementIndex = fillableFormElements.indexOf(element);
376 return fillableFormElements[elementIndex - 1];
379 function nextFieldPredicateIsTrue(element, predicate) {
380 const nextField = nextFillableFormField(element);
381 return !!nextField && predicate(nextField);
384 function previousFieldPredicateIsTrue(element, predicate) {
385 const previousField = previousFillableFormField(element);
386 return !!previousField && predicate(previousField);
389 function nextFieldMatchesExpYearAutocomplete(fnode) {
390 return nextFieldPredicateIsTrue(fnode.element, nextField =>
391 autocompleteStringMatches(nextField, "cc-exp-year")
395 function previousFieldMatchesExpMonthAutocomplete(fnode) {
396 return previousFieldPredicateIsTrue(fnode.element, previousField =>
397 autocompleteStringMatches(previousField, "cc-exp-month")
401 //////////////////////////////////////////////
402 // Attribute Regular Expression Rules
403 function idOrNameMatchRegExp(element, regExp) {
404 for (const str of [element.id, element.name]) {
405 if (regExp.test(str)) {
412 function getElementLabels(element) {
414 *[Symbol.iterator]() {
415 const labels = LabelUtils.findLabelElements(element);
416 for (let label of labels) {
417 yield* LabelUtils.extractLabelStrings(label);
423 function labelsMatchRegExp(element, regExp) {
424 const elemStrings = getElementLabels(element);
425 for (const str of elemStrings) {
426 if (regExp.test(str)) {
431 const parentElement = element.parentElement;
432 // Bug 1634819: element.parentElement is null if element.parentNode is a ShadowRoot
433 if (!parentElement) {
436 // Check if the input is in a <td>, and, if so, check the textContent of the containing <tr>
437 if (parentElement.tagName === "TD" && parentElement.parentElement) {
438 // TODO: How bad is the assumption that the <tr> won't be the parent of the <td>?
439 return regExp.test(parentElement.parentElement.textContent);
442 // Check if the input is in a <dd>, and, if so, check the textContent of the preceding <dt>
444 parentElement.tagName === "DD" &&
445 // previousElementSibling can be null
446 parentElement.previousElementSibling
448 return regExp.test(parentElement.previousElementSibling.textContent);
453 function closestLabelMatchesRegExp(element, regExp) {
454 const previousElementSibling = element.previousElementSibling;
456 previousElementSibling !== null &&
457 previousElementSibling.tagName === "LABEL"
459 return regExp.test(previousElementSibling.textContent);
462 const nextElementSibling = element.nextElementSibling;
463 if (nextElementSibling !== null && nextElementSibling.tagName === "LABEL") {
464 return regExp.test(nextElementSibling.textContent);
470 function ariaLabelMatchesRegExp(element, regExp) {
471 const ariaLabel = element.getAttribute("aria-label");
472 return !!ariaLabel && regExp.test(ariaLabel);
475 function placeholderMatchesRegExp(element, regExp) {
476 const placeholder = element.getAttribute("placeholder");
477 return !!placeholder && regExp.test(placeholder);
480 function nextFieldIdOrNameMatchRegExp(element, regExp) {
481 return nextFieldPredicateIsTrue(element, nextField =>
482 idOrNameMatchRegExp(nextField, regExp)
486 function nextFieldLabelsMatchRegExp(element, regExp) {
487 return nextFieldPredicateIsTrue(element, nextField =>
488 labelsMatchRegExp(nextField, regExp)
492 function nextFieldPlaceholderMatchesRegExp(element, regExp) {
493 return nextFieldPredicateIsTrue(element, nextField =>
494 placeholderMatchesRegExp(nextField, regExp)
498 function nextFieldAriaLabelMatchesRegExp(element, regExp) {
499 return nextFieldPredicateIsTrue(element, nextField =>
500 ariaLabelMatchesRegExp(nextField, regExp)
504 function previousFieldIdOrNameMatchRegExp(element, regExp) {
505 return previousFieldPredicateIsTrue(element, previousField =>
506 idOrNameMatchRegExp(previousField, regExp)
510 function previousFieldLabelsMatchRegExp(element, regExp) {
511 return previousFieldPredicateIsTrue(element, previousField =>
512 labelsMatchRegExp(previousField, regExp)
516 function previousFieldPlaceholderMatchesRegExp(element, regExp) {
517 return previousFieldPredicateIsTrue(element, previousField =>
518 placeholderMatchesRegExp(previousField, regExp)
522 function previousFieldAriaLabelMatchesRegExp(element, regExp) {
523 return previousFieldPredicateIsTrue(element, previousField =>
524 ariaLabelMatchesRegExp(previousField, regExp)
527 //////////////////////////////////////////////
529 function isSelectWithCreditCardOptions(fnode) {
530 // Check every select for options that match credit card network names in
532 const element = fnode.element;
533 if (element.tagName === "SELECT") {
534 for (let option of element.querySelectorAll("option")) {
536 CreditCard.getNetworkFromName(option.value) ||
537 CreditCard.getNetworkFromName(option.text)
547 * If any of the regular expressions match multiple times, we assume the tested
548 * string belongs to a radio button for payment type instead of card type.
550 * @param {Fnode} fnode
553 function isRadioWithCreditCardText(fnode) {
554 const element = fnode.element;
555 const inputType = element.type;
556 if (!!inputType && inputType === "radio") {
557 const valueMatches = element.value.match(CREDIT_CARD_NETWORK_REGEXP);
559 return valueMatches.length === 1;
562 // Here we are checking that only one label matches only one entry in the regular expression.
563 const labels = getElementLabels(element);
564 let labelsMatched = 0;
565 for (const label of labels) {
566 const labelMatches = label.match(CREDIT_CARD_NETWORK_REGEXP);
568 if (labelMatches.length > 1) {
574 if (labelsMatched > 0) {
575 return labelsMatched === 1;
578 const textContentMatches = element.textContent.match(
579 CREDIT_CARD_NETWORK_REGEXP
581 if (textContentMatches) {
582 return textContentMatches.length === 1;
588 function matchContiguousSubArray(array, subArray) {
589 return array.some((elm, i) =>
590 subArray.every((sElem, j) => sElem === array[i + j])
594 function isExpirationMonthLikely(element) {
595 if (element.tagName !== "SELECT") {
599 const options = [...element.options];
600 const desiredValues = Array(12)
602 .map((v, i) => v + i);
604 // The number of month options shouldn't be less than 12 or larger than 13
605 // including the default option.
606 if (options.length < 12 || options.length > 13) {
611 matchContiguousSubArray(
612 options.map(e => +e.value),
615 matchContiguousSubArray(
616 options.map(e => +e.label),
622 function isExpirationYearLikely(element) {
623 if (element.tagName !== "SELECT") {
627 const options = [...element.options];
628 // A normal expiration year select should contain at least the last three years
630 const curYear = new Date().getFullYear();
631 const desiredValues = Array(3)
633 .map((v, i) => v + curYear + i);
636 matchContiguousSubArray(
637 options.map(e => +e.value),
640 matchContiguousSubArray(
641 options.map(e => +e.label),
647 function nextFieldIsExpirationYearLikely(fnode) {
648 return nextFieldPredicateIsTrue(fnode.element, isExpirationYearLikely);
651 function previousFieldIsExpirationMonthLikely(fnode) {
652 return previousFieldPredicateIsTrue(fnode.element, isExpirationMonthLikely);
655 function attrsMatchExpWith2Or4DigitYear(fnode, regExpMatchingFunction) {
656 const element = fnode.element;
658 regExpMatchingFunction(element, TwoDigitYearRegExp) ||
659 regExpMatchingFunction(element, FourDigitYearRegExp)
663 function maxLengthIs(fnode, maxLengthValue) {
664 return fnode.element.maxLength === maxLengthValue;
667 function roleIsMenu(fnode) {
668 const role = fnode.element.getAttribute("role");
669 return !!role && role === "menu";
672 function idOrNameMatchDwfrmAndBml(fnode) {
674 idOrNameMatchRegExp(fnode.element, dwfrmRegExp) &&
675 idOrNameMatchRegExp(fnode.element, bmlRegExp)
679 function hasTemplatedValue(fnode) {
680 const value = fnode.element.getAttribute("value");
681 return !!value && templatedValue.test(value);
684 function inputTypeNotNumbery(fnode) {
685 const inputType = fnode.element.type;
687 return !["text", "tel", "number"].includes(inputType);
692 function idOrNameMatchFirstAndLast(fnode) {
694 idOrNameMatchRegExp(fnode.element, firstRegExp) &&
695 idOrNameMatchRegExp(fnode.element, lastRegExp)
700 * Compactly generate a series of rules that all take a single LHS type with no
701 * .when() clause and have only a score() call on the right- hand side.
703 * @param {Lhs} inType The incoming fnode type that all rules take
704 * @param {object} ruleMap A simple object used as a map with rule names
705 * pointing to scoring callbacks
708 function* simpleScoringRules(inType, ruleMap) {
709 for (const [name, scoringCallback] of Object.entries(ruleMap)) {
710 yield rule(type(inType), score(scoringCallback), { name });
714 function makeRuleset(coeffs, biases) {
718 * Factor out the page scan just for a little more speed during training.
719 * This selector is good for most fields. cardType is an exception: it
720 * cannot be type=month.
723 queriedOrClickedElements(
724 "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=month], select, button"
726 type("typicalCandidates")
732 rule(type("typicalCandidates"), type("cc-number")),
733 ...simpleScoringRules("cc-number", {
734 idOrNameMatchNumberRegExp: fnode =>
737 FathomHeuristicsRegExp.RULES["cc-number"]
739 labelsMatchNumberRegExp: fnode =>
740 labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
741 closestLabelMatchesNumberRegExp: fnode =>
742 closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
743 placeholderMatchesNumberRegExp: fnode =>
744 placeholderMatchesRegExp(
746 FathomHeuristicsRegExp.RULES["cc-number"]
748 ariaLabelMatchesNumberRegExp: fnode =>
749 ariaLabelMatchesRegExp(
751 FathomHeuristicsRegExp.RULES["cc-number"]
753 idOrNameMatchGift: fnode =>
754 idOrNameMatchRegExp(fnode.element, giftRegExp),
755 labelsMatchGift: fnode => labelsMatchRegExp(fnode.element, giftRegExp),
756 placeholderMatchesGift: fnode =>
757 placeholderMatchesRegExp(fnode.element, giftRegExp),
758 ariaLabelMatchesGift: fnode =>
759 ariaLabelMatchesRegExp(fnode.element, giftRegExp),
760 idOrNameMatchSubscription: fnode =>
761 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
762 idOrNameMatchDwfrmAndBml,
766 rule(type("cc-number"), out("cc-number")),
771 rule(type("typicalCandidates"), type("cc-name")),
772 ...simpleScoringRules("cc-name", {
773 idOrNameMatchNameRegExp: fnode =>
774 idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
775 labelsMatchNameRegExp: fnode =>
776 labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
777 closestLabelMatchesNameRegExp: fnode =>
778 closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
779 placeholderMatchesNameRegExp: fnode =>
780 placeholderMatchesRegExp(
782 FathomHeuristicsRegExp.RULES["cc-name"]
784 ariaLabelMatchesNameRegExp: fnode =>
785 ariaLabelMatchesRegExp(
787 FathomHeuristicsRegExp.RULES["cc-name"]
789 idOrNameMatchFirst: fnode =>
790 idOrNameMatchRegExp(fnode.element, firstRegExp),
791 labelsMatchFirst: fnode =>
792 labelsMatchRegExp(fnode.element, firstRegExp),
793 placeholderMatchesFirst: fnode =>
794 placeholderMatchesRegExp(fnode.element, firstRegExp),
795 ariaLabelMatchesFirst: fnode =>
796 ariaLabelMatchesRegExp(fnode.element, firstRegExp),
797 idOrNameMatchLast: fnode =>
798 idOrNameMatchRegExp(fnode.element, lastRegExp),
799 labelsMatchLast: fnode => labelsMatchRegExp(fnode.element, lastRegExp),
800 placeholderMatchesLast: fnode =>
801 placeholderMatchesRegExp(fnode.element, lastRegExp),
802 ariaLabelMatchesLast: fnode =>
803 ariaLabelMatchesRegExp(fnode.element, lastRegExp),
804 idOrNameMatchSubscription: fnode =>
805 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
806 idOrNameMatchFirstAndLast,
807 idOrNameMatchDwfrmAndBml,
810 rule(type("cc-name"), out("cc-name")),
816 queriedOrClickedElements(
817 "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=radio], select, button"
821 ...simpleScoringRules("cc-type", {
822 idOrNameMatchTypeRegExp: fnode =>
823 idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
824 labelsMatchTypeRegExp: fnode =>
825 labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
826 closestLabelMatchesTypeRegExp: fnode =>
827 closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
828 idOrNameMatchVisaCheckout: fnode =>
829 idOrNameMatchRegExp(fnode.element, VisaCheckoutRegExp),
830 ariaLabelMatchesVisaCheckout: fnode =>
831 ariaLabelMatchesRegExp(fnode.element, VisaCheckoutRegExp),
832 isSelectWithCreditCardOptions,
833 isRadioWithCreditCardText,
834 idOrNameMatchSubscription: fnode =>
835 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
836 idOrNameMatchDwfrmAndBml,
839 rule(type("cc-type"), out("cc-type")),
844 rule(type("typicalCandidates"), type("cc-exp")),
845 ...simpleScoringRules("cc-exp", {
846 labelsMatchExpRegExp: fnode =>
847 labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
848 closestLabelMatchesExpRegExp: fnode =>
849 closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
850 placeholderMatchesExpRegExp: fnode =>
851 placeholderMatchesRegExp(
853 FathomHeuristicsRegExp.RULES["cc-exp"]
855 labelsMatchExpWith2Or4DigitYear: fnode =>
856 attrsMatchExpWith2Or4DigitYear(fnode, labelsMatchRegExp),
857 placeholderMatchesExpWith2Or4DigitYear: fnode =>
858 attrsMatchExpWith2Or4DigitYear(fnode, placeholderMatchesRegExp),
859 labelsMatchMMYY: fnode => labelsMatchRegExp(fnode.element, MMYYRegExp),
860 placeholderMatchesMMYY: fnode =>
861 placeholderMatchesRegExp(fnode.element, MMYYRegExp),
862 maxLengthIs7: fnode => maxLengthIs(fnode, 7),
863 idOrNameMatchSubscription: fnode =>
864 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
865 idOrNameMatchDwfrmAndBml,
867 isExpirationMonthLikely: fnode =>
868 isExpirationMonthLikely(fnode.element),
869 isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
870 idOrNameMatchMonth: fnode =>
871 idOrNameMatchRegExp(fnode.element, monthRegExp),
872 idOrNameMatchYear: fnode =>
873 idOrNameMatchRegExp(fnode.element, yearRegExp),
874 idOrNameMatchExpMonthRegExp: fnode =>
877 FathomHeuristicsRegExp.RULES["cc-exp-month"]
879 idOrNameMatchExpYearRegExp: fnode =>
882 FathomHeuristicsRegExp.RULES["cc-exp-year"]
884 idOrNameMatchValidation: fnode =>
885 idOrNameMatchRegExp(fnode.element, /validate|validation/i),
887 rule(type("cc-exp"), out("cc-exp")),
890 * expirationMonth rules
892 rule(type("typicalCandidates"), type("cc-exp-month")),
893 ...simpleScoringRules("cc-exp-month", {
894 idOrNameMatchExpMonthRegExp: fnode =>
897 FathomHeuristicsRegExp.RULES["cc-exp-month"]
899 labelsMatchExpMonthRegExp: fnode =>
902 FathomHeuristicsRegExp.RULES["cc-exp-month"]
904 closestLabelMatchesExpMonthRegExp: fnode =>
905 closestLabelMatchesRegExp(
907 FathomHeuristicsRegExp.RULES["cc-exp-month"]
909 placeholderMatchesExpMonthRegExp: fnode =>
910 placeholderMatchesRegExp(
912 FathomHeuristicsRegExp.RULES["cc-exp-month"]
914 ariaLabelMatchesExpMonthRegExp: fnode =>
915 ariaLabelMatchesRegExp(
917 FathomHeuristicsRegExp.RULES["cc-exp-month"]
919 idOrNameMatchMonth: fnode =>
920 idOrNameMatchRegExp(fnode.element, monthRegExp),
921 labelsMatchMonth: fnode =>
922 labelsMatchRegExp(fnode.element, monthRegExp),
923 placeholderMatchesMonth: fnode =>
924 placeholderMatchesRegExp(fnode.element, monthRegExp),
925 ariaLabelMatchesMonth: fnode =>
926 ariaLabelMatchesRegExp(fnode.element, monthRegExp),
927 nextFieldIdOrNameMatchExpYearRegExp: fnode =>
928 nextFieldIdOrNameMatchRegExp(
930 FathomHeuristicsRegExp.RULES["cc-exp-year"]
932 nextFieldLabelsMatchExpYearRegExp: fnode =>
933 nextFieldLabelsMatchRegExp(
935 FathomHeuristicsRegExp.RULES["cc-exp-year"]
937 nextFieldPlaceholderMatchExpYearRegExp: fnode =>
938 nextFieldPlaceholderMatchesRegExp(
940 FathomHeuristicsRegExp.RULES["cc-exp-year"]
942 nextFieldAriaLabelMatchExpYearRegExp: fnode =>
943 nextFieldAriaLabelMatchesRegExp(
945 FathomHeuristicsRegExp.RULES["cc-exp-year"]
947 nextFieldIdOrNameMatchYear: fnode =>
948 nextFieldIdOrNameMatchRegExp(fnode.element, yearRegExp),
949 nextFieldLabelsMatchYear: fnode =>
950 nextFieldLabelsMatchRegExp(fnode.element, yearRegExp),
951 nextFieldPlaceholderMatchesYear: fnode =>
952 nextFieldPlaceholderMatchesRegExp(fnode.element, yearRegExp),
953 nextFieldAriaLabelMatchesYear: fnode =>
954 nextFieldAriaLabelMatchesRegExp(fnode.element, yearRegExp),
955 nextFieldMatchesExpYearAutocomplete,
956 isExpirationMonthLikely: fnode =>
957 isExpirationMonthLikely(fnode.element),
958 nextFieldIsExpirationYearLikely,
959 maxLengthIs2: fnode => maxLengthIs(fnode, 2),
960 placeholderMatchesMM: fnode =>
961 placeholderMatchesRegExp(fnode.element, MMRegExp),
963 idOrNameMatchSubscription: fnode =>
964 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
965 idOrNameMatchDwfrmAndBml,
968 rule(type("cc-exp-month"), out("cc-exp-month")),
971 * expirationYear rules
973 rule(type("typicalCandidates"), type("cc-exp-year")),
974 ...simpleScoringRules("cc-exp-year", {
975 idOrNameMatchExpYearRegExp: fnode =>
978 FathomHeuristicsRegExp.RULES["cc-exp-year"]
980 labelsMatchExpYearRegExp: fnode =>
983 FathomHeuristicsRegExp.RULES["cc-exp-year"]
985 closestLabelMatchesExpYearRegExp: fnode =>
986 closestLabelMatchesRegExp(
988 FathomHeuristicsRegExp.RULES["cc-exp-year"]
990 placeholderMatchesExpYearRegExp: fnode =>
991 placeholderMatchesRegExp(
993 FathomHeuristicsRegExp.RULES["cc-exp-year"]
995 ariaLabelMatchesExpYearRegExp: fnode =>
996 ariaLabelMatchesRegExp(
998 FathomHeuristicsRegExp.RULES["cc-exp-year"]
1000 idOrNameMatchYear: fnode =>
1001 idOrNameMatchRegExp(fnode.element, yearRegExp),
1002 labelsMatchYear: fnode => labelsMatchRegExp(fnode.element, yearRegExp),
1003 placeholderMatchesYear: fnode =>
1004 placeholderMatchesRegExp(fnode.element, yearRegExp),
1005 ariaLabelMatchesYear: fnode =>
1006 ariaLabelMatchesRegExp(fnode.element, yearRegExp),
1007 previousFieldIdOrNameMatchExpMonthRegExp: fnode =>
1008 previousFieldIdOrNameMatchRegExp(
1010 FathomHeuristicsRegExp.RULES["cc-exp-month"]
1012 previousFieldLabelsMatchExpMonthRegExp: fnode =>
1013 previousFieldLabelsMatchRegExp(
1015 FathomHeuristicsRegExp.RULES["cc-exp-month"]
1017 previousFieldPlaceholderMatchExpMonthRegExp: fnode =>
1018 previousFieldPlaceholderMatchesRegExp(
1020 FathomHeuristicsRegExp.RULES["cc-exp-month"]
1022 previousFieldAriaLabelMatchExpMonthRegExp: fnode =>
1023 previousFieldAriaLabelMatchesRegExp(
1025 FathomHeuristicsRegExp.RULES["cc-exp-month"]
1027 previousFieldIdOrNameMatchMonth: fnode =>
1028 previousFieldIdOrNameMatchRegExp(fnode.element, monthRegExp),
1029 previousFieldLabelsMatchMonth: fnode =>
1030 previousFieldLabelsMatchRegExp(fnode.element, monthRegExp),
1031 previousFieldPlaceholderMatchesMonth: fnode =>
1032 previousFieldPlaceholderMatchesRegExp(fnode.element, monthRegExp),
1033 previousFieldAriaLabelMatchesMonth: fnode =>
1034 previousFieldAriaLabelMatchesRegExp(fnode.element, monthRegExp),
1035 previousFieldMatchesExpMonthAutocomplete,
1036 isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
1037 previousFieldIsExpirationMonthLikely,
1038 placeholderMatchesYYOrYYYY: fnode =>
1039 placeholderMatchesRegExp(fnode.element, YYorYYYYRegExp),
1041 idOrNameMatchSubscription: fnode =>
1042 idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
1043 idOrNameMatchDwfrmAndBml,
1046 rule(type("cc-exp-year"), out("cc-exp-year")),
1053 const coefficients = {
1055 ["idOrNameMatchNumberRegExp", 7.679469585418701],
1056 ["labelsMatchNumberRegExp", 5.122580051422119],
1057 ["closestLabelMatchesNumberRegExp", 2.1256935596466064],
1058 ["placeholderMatchesNumberRegExp", 9.471800804138184],
1059 ["ariaLabelMatchesNumberRegExp", 6.067715644836426],
1060 ["idOrNameMatchGift", -22.946273803710938],
1061 ["labelsMatchGift", -7.852959632873535],
1062 ["placeholderMatchesGift", -2.355496406555176],
1063 ["ariaLabelMatchesGift", -2.940307855606079],
1064 ["idOrNameMatchSubscription", 0.11255314946174622],
1065 ["idOrNameMatchDwfrmAndBml", -0.0006645023822784424],
1066 ["hasTemplatedValue", -0.11370040476322174],
1067 ["inputTypeNotNumbery", -3.750155210494995]
1070 ["idOrNameMatchNameRegExp", 7.496212959289551],
1071 ["labelsMatchNameRegExp", 6.081472873687744],
1072 ["closestLabelMatchesNameRegExp", 2.600574254989624],
1073 ["placeholderMatchesNameRegExp", 5.750874042510986],
1074 ["ariaLabelMatchesNameRegExp", 5.162227153778076],
1075 ["idOrNameMatchFirst", -6.742659091949463],
1076 ["labelsMatchFirst", -0.5234538912773132],
1077 ["placeholderMatchesFirst", -3.4615235328674316],
1078 ["ariaLabelMatchesFirst", -1.3145145177841187],
1079 ["idOrNameMatchLast", -12.561869621276855],
1080 ["labelsMatchLast", -0.27417105436325073],
1081 ["placeholderMatchesLast", -1.434966802597046],
1082 ["ariaLabelMatchesLast", -2.9319725036621094],
1083 ["idOrNameMatchFirstAndLast", 24.123435974121094],
1084 ["idOrNameMatchSubscription", 0.08349418640136719],
1085 ["idOrNameMatchDwfrmAndBml", 0.01882520318031311],
1086 ["hasTemplatedValue", 0.182317852973938]
1089 ["idOrNameMatchTypeRegExp", 2.0581533908843994],
1090 ["labelsMatchTypeRegExp", 1.0784518718719482],
1091 ["closestLabelMatchesTypeRegExp", 0.6995877623558044],
1092 ["idOrNameMatchVisaCheckout", -3.320356845855713],
1093 ["ariaLabelMatchesVisaCheckout", -3.4196767807006836],
1094 ["isSelectWithCreditCardOptions", 10.337477684020996],
1095 ["isRadioWithCreditCardText", 4.530318737030029],
1096 ["idOrNameMatchSubscription", -3.7206356525421143],
1097 ["idOrNameMatchDwfrmAndBml", -0.08782318234443665],
1098 ["hasTemplatedValue", 0.1772511601448059]
1101 ["labelsMatchExpRegExp", 7.588159561157227],
1102 ["closestLabelMatchesExpRegExp", 1.41484534740448],
1103 ["placeholderMatchesExpRegExp", 8.759064674377441],
1104 ["labelsMatchExpWith2Or4DigitYear", -3.876218795776367],
1105 ["placeholderMatchesExpWith2Or4DigitYear", 2.8364884853363037],
1106 ["labelsMatchMMYY", 8.836017608642578],
1107 ["placeholderMatchesMMYY", -0.5231751799583435],
1108 ["maxLengthIs7", 1.3565447330474854],
1109 ["idOrNameMatchSubscription", 0.1779913753271103],
1110 ["idOrNameMatchDwfrmAndBml", 0.21037884056568146],
1111 ["hasTemplatedValue", 0.14900512993335724],
1112 ["isExpirationMonthLikely", -3.223409652709961],
1113 ["isExpirationYearLikely", -2.536919593811035],
1114 ["idOrNameMatchMonth", -3.6893014907836914],
1115 ["idOrNameMatchYear", -3.108184337615967],
1116 ["idOrNameMatchExpMonthRegExp", -2.264357089996338],
1117 ["idOrNameMatchExpYearRegExp", -2.7957723140716553],
1118 ["idOrNameMatchValidation", -2.29402756690979]
1121 ["idOrNameMatchExpMonthRegExp", 0.2787344455718994],
1122 ["labelsMatchExpMonthRegExp", 1.298413634300232],
1123 ["closestLabelMatchesExpMonthRegExp", -11.206244468688965],
1124 ["placeholderMatchesExpMonthRegExp", 1.2605619430541992],
1125 ["ariaLabelMatchesExpMonthRegExp", 1.1330018043518066],
1126 ["idOrNameMatchMonth", 6.1464314460754395],
1127 ["labelsMatchMonth", 0.7051732540130615],
1128 ["placeholderMatchesMonth", 0.7463492751121521],
1129 ["ariaLabelMatchesMonth", 1.8244760036468506],
1130 ["nextFieldIdOrNameMatchExpYearRegExp", 0.06347066164016724],
1131 ["nextFieldLabelsMatchExpYearRegExp", -0.1692247837781906],
1132 ["nextFieldPlaceholderMatchExpYearRegExp", 1.0434566736221313],
1133 ["nextFieldAriaLabelMatchExpYearRegExp", 1.751156210899353],
1134 ["nextFieldIdOrNameMatchYear", -0.532447338104248],
1135 ["nextFieldLabelsMatchYear", 1.3248541355133057],
1136 ["nextFieldPlaceholderMatchesYear", 0.604235827922821],
1137 ["nextFieldAriaLabelMatchesYear", 1.5364223718643188],
1138 ["nextFieldMatchesExpYearAutocomplete", 6.285938262939453],
1139 ["isExpirationMonthLikely", 13.117807388305664],
1140 ["nextFieldIsExpirationYearLikely", 7.182341575622559],
1141 ["maxLengthIs2", 4.477289199829102],
1142 ["placeholderMatchesMM", 14.403288841247559],
1143 ["roleIsMenu", 5.770959854125977],
1144 ["idOrNameMatchSubscription", -0.043085768818855286],
1145 ["idOrNameMatchDwfrmAndBml", 0.02823038399219513],
1146 ["hasTemplatedValue", 0.07234494388103485]
1149 ["idOrNameMatchExpYearRegExp", 5.426016807556152],
1150 ["labelsMatchExpYearRegExp", 1.3240209817886353],
1151 ["closestLabelMatchesExpYearRegExp", -8.702284812927246],
1152 ["placeholderMatchesExpYearRegExp", 0.9059725999832153],
1153 ["ariaLabelMatchesExpYearRegExp", 0.5550334453582764],
1154 ["idOrNameMatchYear", 5.362994194030762],
1155 ["labelsMatchYear", 2.7185044288635254],
1156 ["placeholderMatchesYear", 0.7883157134056091],
1157 ["ariaLabelMatchesYear", 0.311492383480072],
1158 ["previousFieldIdOrNameMatchExpMonthRegExp", 1.8155208826065063],
1159 ["previousFieldLabelsMatchExpMonthRegExp", -0.46133187413215637],
1160 ["previousFieldPlaceholderMatchExpMonthRegExp", 1.0374903678894043],
1161 ["previousFieldAriaLabelMatchExpMonthRegExp", -0.5901495814323425],
1162 ["previousFieldIdOrNameMatchMonth", -5.960310935974121],
1163 ["previousFieldLabelsMatchMonth", 0.6495584845542908],
1164 ["previousFieldPlaceholderMatchesMonth", 0.7198042273521423],
1165 ["previousFieldAriaLabelMatchesMonth", 3.4590985774993896],
1166 ["previousFieldMatchesExpMonthAutocomplete", 2.986003875732422],
1167 ["isExpirationYearLikely", 4.021566390991211],
1168 ["previousFieldIsExpirationMonthLikely", 9.298635482788086],
1169 ["placeholderMatchesYYOrYYYY", 10.457176208496094],
1170 ["roleIsMenu", 1.1051956415176392],
1171 ["idOrNameMatchSubscription", 0.000688597559928894],
1172 ["idOrNameMatchDwfrmAndBml", 0.15687309205532074],
1173 ["hasTemplatedValue", -0.19141331315040588]
1178 ["cc-number", -4.948795795440674],
1179 ["cc-name", -5.3578081130981445],
1180 ["cc-type", -5.979659557342529],
1181 ["cc-exp", -5.849575996398926],
1182 ["cc-exp-month", -8.844199180603027],
1183 ["cc-exp-year", -6.499860763549805],
1187 * END OF CODE PASTED FROM TRAINING REPOSITORY
1191 * MORE CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
1193 // Currently there is a bug when a ruleset has multple types (ex, cc-name, cc-number)
1194 // and those types also has the same rules (ex. rule `hasTemplatedValue` is used in
1195 // all the tyoes). When the above case exists, the coefficient of the rule will be
1196 // overwritten, which means, we can't have different coefficient for the same rule on
1197 // different types. To workaround this issue, we create a new ruleset for each type.
1198 export var CreditCardRulesets = {
1200 XPCOMUtils.defineLazyPreferenceGetter(
1203 "extensions.formautofill.creditCards.heuristics.fathom.types",
1206 val => val.split(",")
1209 for (const type of this.types) {
1211 this[type] = makeRuleset([...coefficients[type]], biases);
1217 return this.supportedTypes;
1221 CreditCardRulesets.init();
1223 export default CreditCardRulesets;