Bug 1941046 - Part 4: Send a callback request for impression and clicks of MARS Top...
[gecko.git] / toolkit / components / formautofill / shared / CreditCardRuleset.sys.mjs
blob72a75cf27aa01df7dea7c73ea924ec286e87463c
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/. */
5 /**
6  * Fathom ML model for identifying the fields of credit-card forms
7  *
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.
16  */
18 /**
19  * CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
20  */
22 import {
23   element as clickedElement,
24   out,
25   rule,
26   ruleset,
27   score,
28   type,
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";
32 import {
33   CreditCard,
34   NETWORK_NAMES,
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";
40 /**
41  * Callthrough abstraction to allow .getAutocompleteInfo() to be mocked out
42  * during training
43  *
44  * @param {Element} element DOM element to get info about
45  * @returns {object} Page-author-provided autocomplete metadata
46  */
47 function getAutocompleteInfo(element) {
48   return element.getAutocompleteInfo();
51 /**
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
54  *  pruned, none
55  */
56 function queriedOrClickedElements(selector) {
57   return clickedElement(selector);
60 /**
61  * START OF CODE PASTED FROM TRAINING REPOSITORY
62  */
64 var FathomHeuristicsRegExp = {
65   RULES: {
66     "cc-name": undefined,
67     "cc-number": undefined,
68     "cc-exp-month": undefined,
69     "cc-exp-year": undefined,
70     "cc-exp": undefined,
71     "cc-type": undefined,
72   },
74   RULE_SETS: [
75     {
76       /* eslint-disable */
77       // Let us keep our consistent wrapping.
78       "cc-name":
79         // Firefox-specific rules
80         "account.*holder.*name" +
81         "|^(credit[-\\s]?card|card).*name" +
82         // de-DE
83         "|^(kredit)?(karten|konto)inhaber" +
84         "|^(name).*karte" +
85         // fr-FR
86         "|nom.*(titulaire|détenteur)" +
87         "|(titulaire|détenteur).*(carte)" +
88         // it-IT
89         "|titolare.*carta" +
90         // pl-PL
91         "|posiadacz.*karty" +
92         // es-ES
93         "|nombre.*(titular|tarjeta)" +
94         // nl-NL
95         "|naam.*op.*kaart" +
96         // Rules from Bitwarden
97         "|cc-?name" +
98         "|card-?name" +
99         "|cardholder-?name" +
100         "|(^nom$)" +
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
107         "|名前" + // ja-JP
108         "|Имя.*карты" + // ru
109         "|信用卡开户名|开户名|持卡人姓名" + // zh-CN
110         "|持卡人姓名", // zh-TW
112       "cc-number":
113         // Firefox-specific rules
114         // de-DE
115         "(cc|kk)nr" +
116         "|(kredit)?(karten)(nummer|nr)" +
117         // it-IT
118         "|numero.*carta" +
119         // fr-FR
120         "|(numero|número|numéro).*(carte)" +
121         // pl-PL
122         "|numer.*karty" +
123         // es-ES
124         "|(número|numero).*tarjeta" +
125         // nl-NL
126         "|kaartnummer" +
127         // Rules from Bitwarden
128         "|cc-?number" +
129         "|cc-?num" +
130         "|card-?number" +
131         "|card-?num" +
132         "|cc-?no" +
133         "|card-?no" +
134         "|numero-?carte" +
135         "|num-?carte" +
136         "|cb-?num" +
137         // Rules are from Chromium source codes
138         "|(add)?(?:card|cc|acct).?(?:number|#|no|num)" +
139         "|カード番号" + // ja-JP
140         "|Номер.*карты" + // ru
141         "|信用卡号|信用卡号码" + // zh-CN
142         "|信用卡卡號" + // zh-TW
143         "|카드", // ko-KR
145       "cc-exp":
146         // Firefox-specific rules
147         "mm\\s*(\/|\\|-)\\s*(yy|jj|aa)" +
148         "|(month|mois)\\s*(\/|\\|-|et)\\s*(year|année)" +
149         // de-DE
150         // fr-FR
151         // Rules from Bitwarden
152         "|(^cc-?exp$)" +
153         "|(^card-?exp$)" +
154         "|(^cc-?expiration$)" +
155         "|(^card-?expiration$)" +
156         "|(^cc-?ex$)" +
157         "|(^card-?ex$)" +
158         "|(^card-?expire$)" +
159         "|(^card-?expiry$)" +
160         "|(^validite$)" +
161         "|(^expiration$)" +
162         "|(^expiry$)" +
163         "|mm-?yy" +
164         "|mm-?yyyy" +
165         "|yy-?mm" +
166         "|yyyy-?mm" +
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
173         "|fecha" + // es
174         "|date.*exp" + // fr-FR
175         "|scadenza" + // it-IT
176         "|有効期限" + // ja-JP
177         "|validade" + // pt-BR, pt-PT
178         "|Срок действия карты", // ru
180       "cc-exp-month":
181         // Firefox-specific rules
182         "(cc|kk)month" + // de-DE
183         // Rules from Bitwarden
184         "|(^exp-?month$)" +
185         "|(^cc-?exp-?month$)" +
186         "|(^cc-?month$)" +
187         "|(^card-?month$)" +
188         "|(^cc-?mo$)" +
189         "|(^card-?mo$)" +
190         "|(^exp-?mo$)" +
191         "|(^card-?exp-?mo$)" +
192         "|(^cc-?exp-?mo$)" +
193         "|(^card-?expiration-?month$)" +
194         "|(^expiration-?month$)" +
195         "|(^cc-?mm$)" +
196         "|(^cc-?m$)" +
197         "|(^card-?mm$)" +
198         "|(^card-?m$)" +
199         "|(^card-?exp-?mm$)" +
200         "|(^cc-?exp-?mm$)" +
201         "|(^exp-?mm$)" +
202         "|(^exp-?m$)" +
203         "|(^expire-?month$)" +
204         "|(^expire-?mo$)" +
205         "|(^expiry-?month$)" +
206         "|(^expiry-?mo$)" +
207         "|(^card-?expire-?month$)" +
208         "|(^card-?expire-?mo$)" +
209         "|(^card-?expiry-?month$)" +
210         "|(^card-?expiry-?mo$)" +
211         "|(^mois-?validite$)" +
212         "|(^mois-?expiration$)" +
213         "|(^m-?validite$)" +
214         "|(^m-?expiration$)" +
215         "|(^expiry-?date-?field-?month$)" +
216         "|(^expiration-?date-?month$)" +
217         "|(^expiration-?date-?mm$)" +
218         "|(^exp-?mon$)" +
219         "|(^validity-?mo$)" +
220         "|(^exp-?date-?mo$)" +
221         "|(^cb-?date-?mois$)" +
222         "|(^date-?m$)" +
223         // Rules are from Chromium source codes
224         "|exp.*mo|ccmonth|cardmonth|addmonth" +
225         "|monat" + // de-DE
226         // "|fecha" + // es
227         // "|date.*exp" + // fr-FR
228         // "|scadenza" + // it-IT
229         // "|有効期限" + // ja-JP
230         // "|validade" + // pt-BR, pt-PT
231         // "|Срок действия карты" + // ru
232         "|月", // zh-CN
234       "cc-exp-year":
235         // Firefox-specific rules
236         "(cc|kk)year" + // de-DE
237         // Rules from Bitwarden
238         "|(^exp-?year$)" +
239         "|(^cc-?exp-?year$)" +
240         "|(^cc-?year$)" +
241         "|(^card-?year$)" +
242         "|(^cc-?yr$)" +
243         "|(^card-?yr$)" +
244         "|(^exp-?yr$)" +
245         "|(^card-?exp-?yr$)" +
246         "|(^cc-?exp-?yr$)" +
247         "|(^card-?expiration-?year$)" +
248         "|(^expiration-?year$)" +
249         "|(^cc-?yy$)" +
250         "|(^cc-?y$)" +
251         "|(^card-?yy$)" +
252         "|(^card-?y$)" +
253         "|(^card-?exp-?yy$)" +
254         "|(^cc-?exp-?yy$)" +
255         "|(^exp-?yy$)" +
256         "|(^exp-?y$)" +
257         "|(^cc-?yyyy$)" +
258         "|(^card-?yyyy$)" +
259         "|(^card-?exp-?yyyy$)" +
260         "|(^cc-?exp-?yyyy$)" +
261         "|(^expire-?year$)" +
262         "|(^expire-?yr$)" +
263         "|(^expiry-?year$)" +
264         "|(^expiry-?yr$)" +
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$)" +
280         "|(^date-?y$)" +
281         // Rules are from Chromium source codes
282         "|(add)?year" +
283         "|jahr" + // de-DE
284         // "|fecha" + // es
285         // "|scadenza" + // it-IT
286         // "|有効期限" + // ja-JP
287         // "|validade" + // pt-BR, pt-PT
288         // "|Срок действия карты" + // ru
289         "|年|有效期", // zh-CN
291       "cc-type":
292         // Firefox-specific rules
293         "type" +
294         // de-DE
295         "|Kartenmarke" +
296         // Rules from Bitwarden
297         "|(^cc-?type$)" +
298         "|(^card-?type$)" +
299         "|(^card-?brand$)" +
300         "|(^cc-?brand$)" +
301         "|(^cb-?type$)",
302         // Rules are from Chromium source codes
303     },
304   ],
306   _getRule(name) {
307     let rules = [];
308     this.RULE_SETS.forEach(set => {
309       if (set[name]) {
310         rules.push(`(${set[name]})`.normalize("NFKC"));
311       }
312     });
314     const value = new RegExp(rules.join("|"), "iu");
315     Object.defineProperty(this.RULES, name, { get: undefined });
316     Object.defineProperty(this.RULES, name, { value });
317     return value;
318   },
320   init() {
321     Object.keys(this.RULES).forEach(field =>
322       Object.defineProperty(this.RULES, field, {
323         get() {
324           return FathomHeuristicsRegExp._getRule(field);
325         },
326       })
327     );
328   },
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))
342     .join("|"),
343   "gui"
344   );
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)
364   );
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")
392   );
395 function previousFieldMatchesExpMonthAutocomplete(fnode) {
396   return previousFieldPredicateIsTrue(fnode.element, previousField =>
397     autocompleteStringMatches(previousField, "cc-exp-month")
398   );
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)) {
406       return true;
407     }
408   }
409   return false;
412 function getElementLabels(element) {
413   return {
414     *[Symbol.iterator]() {
415       const labels = LabelUtils.findLabelElements(element);
416       for (let label of labels) {
417         yield* LabelUtils.extractLabelStrings(label);
418       }
419     },
420   };
423 function labelsMatchRegExp(element, regExp) {
424   const elemStrings = getElementLabels(element);
425   for (const str of elemStrings) {
426     if (regExp.test(str)) {
427       return true;
428     }
429   }
431   const parentElement = element.parentElement;
432   // Bug 1634819: element.parentElement is null if element.parentNode is a ShadowRoot
433   if (!parentElement) {
434     return false;
435   }
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);
440   }
442   // Check if the input is in a <dd>, and, if so, check the textContent of the preceding <dt>
443   if (
444     parentElement.tagName === "DD" &&
445     // previousElementSibling can be null
446     parentElement.previousElementSibling
447   ) {
448     return regExp.test(parentElement.previousElementSibling.textContent);
449   }
450   return false;
453 function closestLabelMatchesRegExp(element, regExp) {
454   const previousElementSibling = element.previousElementSibling;
455   if (
456     previousElementSibling !== null &&
457     previousElementSibling.tagName === "LABEL"
458   ) {
459     return regExp.test(previousElementSibling.textContent);
460   }
462   const nextElementSibling = element.nextElementSibling;
463   if (nextElementSibling !== null && nextElementSibling.tagName === "LABEL") {
464     return regExp.test(nextElementSibling.textContent);
465   }
467   return false;
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)
483   );
486 function nextFieldLabelsMatchRegExp(element, regExp) {
487   return nextFieldPredicateIsTrue(element, nextField =>
488     labelsMatchRegExp(nextField, regExp)
489   );
492 function nextFieldPlaceholderMatchesRegExp(element, regExp) {
493   return nextFieldPredicateIsTrue(element, nextField =>
494     placeholderMatchesRegExp(nextField, regExp)
495   );
498 function nextFieldAriaLabelMatchesRegExp(element, regExp) {
499   return nextFieldPredicateIsTrue(element, nextField =>
500     ariaLabelMatchesRegExp(nextField, regExp)
501   );
504 function previousFieldIdOrNameMatchRegExp(element, regExp) {
505   return previousFieldPredicateIsTrue(element, previousField =>
506     idOrNameMatchRegExp(previousField, regExp)
507   );
510 function previousFieldLabelsMatchRegExp(element, regExp) {
511   return previousFieldPredicateIsTrue(element, previousField =>
512     labelsMatchRegExp(previousField, regExp)
513   );
516 function previousFieldPlaceholderMatchesRegExp(element, regExp) {
517   return previousFieldPredicateIsTrue(element, previousField =>
518     placeholderMatchesRegExp(previousField, regExp)
519   );
522 function previousFieldAriaLabelMatchesRegExp(element, regExp) {
523   return previousFieldPredicateIsTrue(element, previousField =>
524     ariaLabelMatchesRegExp(previousField, regExp)
525   );
527 //////////////////////////////////////////////
529 function isSelectWithCreditCardOptions(fnode) {
530   // Check every select for options that match credit card network names in
531   // value or label.
532   const element = fnode.element;
533   if (element.tagName === "SELECT") {
534     for (let option of element.querySelectorAll("option")) {
535       if (
536         CreditCard.getNetworkFromName(option.value) ||
537         CreditCard.getNetworkFromName(option.text)
538       ) {
539         return true;
540       }
541     }
542   }
543   return false;
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
551  * @returns {boolean}
552  */
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);
558     if (valueMatches) {
559       return valueMatches.length === 1;
560     }
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);
567       if (labelMatches) {
568         if (labelMatches.length > 1) {
569           return false;
570         }
571         labelsMatched++;
572       }
573     }
574     if (labelsMatched > 0) {
575       return labelsMatched === 1;
576     }
578     const textContentMatches = element.textContent.match(
579       CREDIT_CARD_NETWORK_REGEXP
580     );
581     if (textContentMatches) {
582       return textContentMatches.length === 1;
583     }
584   }
585   return false;
588 function matchContiguousSubArray(array, subArray) {
589   return array.some((elm, i) =>
590     subArray.every((sElem, j) => sElem === array[i + j])
591   );
594 function isExpirationMonthLikely(element) {
595   if (element.tagName !== "SELECT") {
596     return false;
597   }
599   const options = [...element.options];
600   const desiredValues = Array(12)
601     .fill(1)
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) {
607     return false;
608   }
610   return (
611     matchContiguousSubArray(
612       options.map(e => +e.value),
613       desiredValues
614     ) ||
615     matchContiguousSubArray(
616       options.map(e => +e.label),
617       desiredValues
618     )
619   );
622 function isExpirationYearLikely(element) {
623   if (element.tagName !== "SELECT") {
624     return false;
625   }
627   const options = [...element.options];
628   // A normal expiration year select should contain at least the last three years
629   // in the list.
630   const curYear = new Date().getFullYear();
631   const desiredValues = Array(3)
632     .fill(0)
633     .map((v, i) => v + curYear + i);
635   return (
636     matchContiguousSubArray(
637       options.map(e => +e.value),
638       desiredValues
639     ) ||
640     matchContiguousSubArray(
641       options.map(e => +e.label),
642       desiredValues
643     )
644   );
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;
657   return (
658     regExpMatchingFunction(element, TwoDigitYearRegExp) ||
659     regExpMatchingFunction(element, FourDigitYearRegExp)
660   );
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) {
673   return (
674     idOrNameMatchRegExp(fnode.element, dwfrmRegExp) &&
675     idOrNameMatchRegExp(fnode.element, bmlRegExp)
676   );
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;
686   if (inputType) {
687     return !["text", "tel", "number"].includes(inputType);
688   }
689   return false;
692 function idOrNameMatchFirstAndLast(fnode) {
693   return (
694     idOrNameMatchRegExp(fnode.element, firstRegExp) &&
695     idOrNameMatchRegExp(fnode.element, lastRegExp)
696   );
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
706  * @yields {Rule}
707  */
708 function* simpleScoringRules(inType, ruleMap) {
709   for (const [name, scoringCallback] of Object.entries(ruleMap)) {
710     yield rule(type(inType), score(scoringCallback), { name });
711   }
714 function makeRuleset(coeffs, biases) {
715   return ruleset(
716     [
717       /**
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.
721        */
722       rule(
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"
725         ),
726         type("typicalCandidates")
727       ),
729       /**
730        * number rules
731        */
732       rule(type("typicalCandidates"), type("cc-number")),
733       ...simpleScoringRules("cc-number", {
734         idOrNameMatchNumberRegExp: fnode =>
735           idOrNameMatchRegExp(
736             fnode.element,
737             FathomHeuristicsRegExp.RULES["cc-number"]
738           ),
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(
745             fnode.element,
746             FathomHeuristicsRegExp.RULES["cc-number"]
747           ),
748         ariaLabelMatchesNumberRegExp: fnode =>
749           ariaLabelMatchesRegExp(
750             fnode.element,
751             FathomHeuristicsRegExp.RULES["cc-number"]
752           ),
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,
763         hasTemplatedValue,
764         inputTypeNotNumbery,
765       }),
766       rule(type("cc-number"), out("cc-number")),
768       /**
769        * name rules
770        */
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(
781             fnode.element,
782             FathomHeuristicsRegExp.RULES["cc-name"]
783           ),
784         ariaLabelMatchesNameRegExp: fnode =>
785           ariaLabelMatchesRegExp(
786             fnode.element,
787             FathomHeuristicsRegExp.RULES["cc-name"]
788           ),
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,
808         hasTemplatedValue,
809       }),
810       rule(type("cc-name"), out("cc-name")),
812       /**
813        * cardType rules
814        */
815       rule(
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"
818         ),
819         type("cc-type")
820       ),
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,
837         hasTemplatedValue,
838       }),
839       rule(type("cc-type"), out("cc-type")),
841       /**
842        * expiration rules
843        */
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(
852             fnode.element,
853             FathomHeuristicsRegExp.RULES["cc-exp"]
854           ),
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,
866         hasTemplatedValue,
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 =>
875           idOrNameMatchRegExp(
876             fnode.element,
877             FathomHeuristicsRegExp.RULES["cc-exp-month"]
878           ),
879         idOrNameMatchExpYearRegExp: fnode =>
880           idOrNameMatchRegExp(
881             fnode.element,
882             FathomHeuristicsRegExp.RULES["cc-exp-year"]
883           ),
884         idOrNameMatchValidation: fnode =>
885           idOrNameMatchRegExp(fnode.element, /validate|validation/i),
886       }),
887       rule(type("cc-exp"), out("cc-exp")),
889       /**
890        * expirationMonth rules
891        */
892       rule(type("typicalCandidates"), type("cc-exp-month")),
893       ...simpleScoringRules("cc-exp-month", {
894         idOrNameMatchExpMonthRegExp: fnode =>
895           idOrNameMatchRegExp(
896             fnode.element,
897             FathomHeuristicsRegExp.RULES["cc-exp-month"]
898           ),
899         labelsMatchExpMonthRegExp: fnode =>
900           labelsMatchRegExp(
901             fnode.element,
902             FathomHeuristicsRegExp.RULES["cc-exp-month"]
903           ),
904         closestLabelMatchesExpMonthRegExp: fnode =>
905           closestLabelMatchesRegExp(
906             fnode.element,
907             FathomHeuristicsRegExp.RULES["cc-exp-month"]
908           ),
909         placeholderMatchesExpMonthRegExp: fnode =>
910           placeholderMatchesRegExp(
911             fnode.element,
912             FathomHeuristicsRegExp.RULES["cc-exp-month"]
913           ),
914         ariaLabelMatchesExpMonthRegExp: fnode =>
915           ariaLabelMatchesRegExp(
916             fnode.element,
917             FathomHeuristicsRegExp.RULES["cc-exp-month"]
918           ),
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(
929             fnode.element,
930             FathomHeuristicsRegExp.RULES["cc-exp-year"]
931           ),
932         nextFieldLabelsMatchExpYearRegExp: fnode =>
933           nextFieldLabelsMatchRegExp(
934             fnode.element,
935             FathomHeuristicsRegExp.RULES["cc-exp-year"]
936           ),
937         nextFieldPlaceholderMatchExpYearRegExp: fnode =>
938           nextFieldPlaceholderMatchesRegExp(
939             fnode.element,
940             FathomHeuristicsRegExp.RULES["cc-exp-year"]
941           ),
942         nextFieldAriaLabelMatchExpYearRegExp: fnode =>
943           nextFieldAriaLabelMatchesRegExp(
944             fnode.element,
945             FathomHeuristicsRegExp.RULES["cc-exp-year"]
946           ),
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),
962         roleIsMenu,
963         idOrNameMatchSubscription: fnode =>
964           idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
965         idOrNameMatchDwfrmAndBml,
966         hasTemplatedValue,
967       }),
968       rule(type("cc-exp-month"), out("cc-exp-month")),
970       /**
971        * expirationYear rules
972        */
973       rule(type("typicalCandidates"), type("cc-exp-year")),
974       ...simpleScoringRules("cc-exp-year", {
975         idOrNameMatchExpYearRegExp: fnode =>
976           idOrNameMatchRegExp(
977             fnode.element,
978             FathomHeuristicsRegExp.RULES["cc-exp-year"]
979           ),
980         labelsMatchExpYearRegExp: fnode =>
981           labelsMatchRegExp(
982             fnode.element,
983             FathomHeuristicsRegExp.RULES["cc-exp-year"]
984           ),
985         closestLabelMatchesExpYearRegExp: fnode =>
986           closestLabelMatchesRegExp(
987             fnode.element,
988             FathomHeuristicsRegExp.RULES["cc-exp-year"]
989           ),
990         placeholderMatchesExpYearRegExp: fnode =>
991           placeholderMatchesRegExp(
992             fnode.element,
993             FathomHeuristicsRegExp.RULES["cc-exp-year"]
994           ),
995         ariaLabelMatchesExpYearRegExp: fnode =>
996           ariaLabelMatchesRegExp(
997             fnode.element,
998             FathomHeuristicsRegExp.RULES["cc-exp-year"]
999           ),
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(
1009             fnode.element,
1010             FathomHeuristicsRegExp.RULES["cc-exp-month"]
1011           ),
1012         previousFieldLabelsMatchExpMonthRegExp: fnode =>
1013           previousFieldLabelsMatchRegExp(
1014             fnode.element,
1015             FathomHeuristicsRegExp.RULES["cc-exp-month"]
1016           ),
1017         previousFieldPlaceholderMatchExpMonthRegExp: fnode =>
1018           previousFieldPlaceholderMatchesRegExp(
1019             fnode.element,
1020             FathomHeuristicsRegExp.RULES["cc-exp-month"]
1021           ),
1022         previousFieldAriaLabelMatchExpMonthRegExp: fnode =>
1023           previousFieldAriaLabelMatchesRegExp(
1024             fnode.element,
1025             FathomHeuristicsRegExp.RULES["cc-exp-month"]
1026           ),
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),
1040         roleIsMenu,
1041         idOrNameMatchSubscription: fnode =>
1042           idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
1043         idOrNameMatchDwfrmAndBml,
1044         hasTemplatedValue,
1045       }),
1046       rule(type("cc-exp-year"), out("cc-exp-year")),
1047     ],
1048     coeffs,
1049     biases
1050   );
1053 const coefficients = {
1054   "cc-number": [
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]
1068   ],
1069   "cc-name": [
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]
1087   ],
1088   "cc-type": [
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]
1099   ],
1100   "cc-exp": [
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]
1119   ],
1120   "cc-exp-month": [
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]
1147   ],
1148   "cc-exp-year": [
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]
1174   ],
1177 const biases = [
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
1188  */
1191  * MORE CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
1192  */
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 = {
1199   init() {
1200     XPCOMUtils.defineLazyPreferenceGetter(
1201       this,
1202       "supportedTypes",
1203       "extensions.formautofill.creditCards.heuristics.fathom.types",
1204       null,
1205       null,
1206       val => val.split(",")
1207     );
1209     for (const type of this.types) {
1210       if (type) {
1211         this[type] = makeRuleset([...coefficients[type]], biases);
1212       }
1213     }
1214   },
1216   get types() {
1217     return this.supportedTypes;
1218   },
1221 CreditCardRulesets.init();
1223 export default CreditCardRulesets;