mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-04 02:57:38 +00:00
Bug 1642060 - Handle Credit Card Type select. r=abr
Differential Revision: https://phabricator.services.mozilla.com/D81015
This commit is contained in:
parent
608d46df7a
commit
ea5b8a7d68
@ -52,6 +52,10 @@ XPCOMUtils.defineLazyGetter(this, "reauthPasswordPromptMessage", () => {
|
||||
);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
CreditCard: "resource://gre/modules/CreditCard.jsm",
|
||||
});
|
||||
|
||||
this.log = null;
|
||||
FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
|
||||
|
||||
@ -956,6 +960,35 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
|
||||
this.adaptFieldMaxLength(profile);
|
||||
}
|
||||
|
||||
computeFillingValue(value, fieldDetail, element) {
|
||||
if (
|
||||
fieldDetail.fieldName != "cc-type" ||
|
||||
ChromeUtils.getClassName(element) !== "HTMLSelectElement"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (CreditCard.isValidNetwork(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Don't save the record when the option value is empty *OR* there
|
||||
// are multiple options being selected. The empty option is usually
|
||||
// assumed to be default along with a meaningless text to users.
|
||||
if (value && element.selectedOptions.length == 1) {
|
||||
let selectedOption = element.selectedOptions[0];
|
||||
let networkType =
|
||||
CreditCard.getNetworkFromName(selectedOption.text) ??
|
||||
CreditCard.getNetworkFromName(selectedOption.value);
|
||||
if (networkType) {
|
||||
return networkType;
|
||||
}
|
||||
}
|
||||
// If we couldn't match the value to any network, we'll
|
||||
// strip this field when submitting.
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customize for previewing prorifle.
|
||||
*
|
||||
|
@ -23,6 +23,10 @@ ChromeUtils.defineModuleGetter(
|
||||
"resource://formautofill/FormAutofillUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
CreditCard: "resource://gre/modules/CreditCard.jsm",
|
||||
});
|
||||
|
||||
this.log = null;
|
||||
FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
|
||||
|
||||
@ -709,27 +713,48 @@ this.FormAutofillHeuristics = {
|
||||
* Return true if there is any field can be recognized in the parser,
|
||||
* otherwise false.
|
||||
*/
|
||||
_parseCreditCardExpirationDateFields(fieldScanner) {
|
||||
_parseCreditCardFields(fieldScanner) {
|
||||
if (fieldScanner.parsingFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const savedIndex = fieldScanner.parsingIndex;
|
||||
const monthAndYearFieldNames = ["cc-exp-month", "cc-exp-year"];
|
||||
const detail = fieldScanner.getFieldDetailByIndex(
|
||||
fieldScanner.parsingIndex
|
||||
);
|
||||
const element = detail.elementWeakRef.get();
|
||||
|
||||
// Respect to autocomplete attr and skip the uninteresting fields
|
||||
// Respect to autocomplete attr
|
||||
if (!detail || (detail._reason && detail._reason == "autocomplete")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const monthAndYearFieldNames = ["cc-exp-month", "cc-exp-year"];
|
||||
// Skip the uninteresting fields
|
||||
if (
|
||||
!detail ||
|
||||
(detail._reason && detail._reason == "autocomplete") ||
|
||||
!["cc-exp", ...monthAndYearFieldNames].includes(detail.fieldName)
|
||||
!["cc-exp", "cc-type", ...monthAndYearFieldNames].includes(
|
||||
detail.fieldName
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const element = detail.elementWeakRef.get();
|
||||
|
||||
// If we didn't auto-discover type field, check every select for options that
|
||||
// match credit card network names in value or label.
|
||||
if (ChromeUtils.getClassName(element) == "HTMLSelectElement") {
|
||||
for (let option of element.querySelectorAll("option")) {
|
||||
if (
|
||||
CreditCard.getNetworkFromName(option.value) ||
|
||||
CreditCard.getNetworkFromName(option.text)
|
||||
) {
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-type");
|
||||
fieldScanner.parsingIndex++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the input type is a month picker, then assume it's cc-exp.
|
||||
if (element.type == "month") {
|
||||
fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp");
|
||||
@ -881,7 +906,7 @@ this.FormAutofillHeuristics = {
|
||||
while (!fieldScanner.parsingFinished) {
|
||||
let parsedPhoneFields = this._parsePhoneFields(fieldScanner);
|
||||
let parsedAddressFields = this._parseAddressFields(fieldScanner);
|
||||
let parsedExpirationDateFields = this._parseCreditCardExpirationDateFields(
|
||||
let parsedExpirationDateFields = this._parseCreditCardFields(
|
||||
fieldScanner
|
||||
);
|
||||
|
||||
@ -935,6 +960,7 @@ this.FormAutofillHeuristics = {
|
||||
"cc-exp-month",
|
||||
"cc-exp-year",
|
||||
"cc-exp",
|
||||
"cc-type",
|
||||
];
|
||||
let regexps = isAutoCompleteOff
|
||||
? FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF
|
||||
@ -954,6 +980,7 @@ this.FormAutofillHeuristics = {
|
||||
"cc-exp-month",
|
||||
"cc-exp-year",
|
||||
"cc-exp",
|
||||
"cc-type",
|
||||
];
|
||||
regexps = regexps.filter(name =>
|
||||
FIELDNAMES_FOR_SELECT_ELEMENT.includes(name)
|
||||
|
@ -588,7 +588,9 @@ class FormAutofillParent extends JSWindowActorParent {
|
||||
creditCard.record["cc-type"] &&
|
||||
!CreditCard.isValidNetwork(creditCard.record["cc-type"])
|
||||
) {
|
||||
delete creditCard.record["cc-type"];
|
||||
// Let's reset the credit card to empty, and then network auto-detect will
|
||||
// pick it up.
|
||||
creditCard.record["cc-type"] = "";
|
||||
}
|
||||
|
||||
// We'll show the credit card doorhanger if:
|
||||
|
@ -35,6 +35,7 @@ var HeuristicsRegExp = {
|
||||
"cc-exp-month": undefined,
|
||||
"cc-exp-year": undefined,
|
||||
"cc-exp": undefined,
|
||||
"cc-type": undefined,
|
||||
},
|
||||
|
||||
RULE_SETS: [
|
||||
@ -49,6 +50,7 @@ var HeuristicsRegExp = {
|
||||
"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",
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
@ -15,6 +15,12 @@ runHeuristicsTest(
|
||||
contactType: "",
|
||||
fieldName: "cc-name",
|
||||
},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
|
@ -130,7 +130,12 @@ runHeuristicsTest(
|
||||
},
|
||||
],
|
||||
[
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
}, // ac-off
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
|
@ -170,7 +170,12 @@ runHeuristicsTest(
|
||||
expectedResult: [
|
||||
[
|
||||
[
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
}, // ac-off
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
|
@ -113,7 +113,12 @@ runHeuristicsTest(
|
||||
},
|
||||
],
|
||||
[
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // ac-off
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
}, // ac-off
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
|
@ -21,7 +21,12 @@ runHeuristicsTest(
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"},
|
||||
],
|
||||
[
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
},
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
@ -74,7 +79,12 @@ runHeuristicsTest(
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "bday-year"}, // select
|
||||
],
|
||||
[
|
||||
// {"section": "", "addressType": "", "contactType": "", "fieldName": "cc-type"}, // select
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
fieldName: "cc-type",
|
||||
}, // ac-off
|
||||
{
|
||||
section: "",
|
||||
addressType: "",
|
||||
|
@ -248,16 +248,18 @@ const TESTCASES = [
|
||||
},
|
||||
{
|
||||
description:
|
||||
"A credit card form with the value of cc-number, cc-exp, and cc-name.",
|
||||
"A credit card form with the value of cc-number, cc-exp, cc-name and cc-type.",
|
||||
document: `<form>
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<input id="cc-name" autocomplete="cc-name">
|
||||
<input id="cc-exp" autocomplete="cc-exp">
|
||||
<input id="cc-type" autocomplete="cc-type">
|
||||
</form>`,
|
||||
formValue: {
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-name": "Foo Bar",
|
||||
"cc-exp": "2022-06",
|
||||
"cc-type": "Visa",
|
||||
},
|
||||
expectedRecord: {
|
||||
address: [],
|
||||
@ -266,6 +268,7 @@ const TESTCASES = [
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-name": "Foo Bar",
|
||||
"cc-exp": "2022-06",
|
||||
"cc-type": "Visa",
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -395,6 +398,52 @@ const TESTCASES = [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "A credit card form with a cc-type select.",
|
||||
document: `<form>
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<label for="field1">Card Type:</label>
|
||||
<select id="field1">
|
||||
<option value="visa" selected>Visa</option>
|
||||
</select>
|
||||
</form>`,
|
||||
formValue: {
|
||||
"cc-number": "5105105105105100",
|
||||
},
|
||||
expectedRecord: {
|
||||
address: [],
|
||||
creditCard: [
|
||||
{
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-type": "visa",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "A credit card form with a cc-type select from label.",
|
||||
document: `<form>
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<label for="cc-type">Card Type:</label>
|
||||
<select id="cc-type">
|
||||
<option value="V" selected>Visa</option>
|
||||
<option value="A">American Express</option>
|
||||
</select>
|
||||
</form>`,
|
||||
formValue: {
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-type": "A",
|
||||
},
|
||||
expectedRecord: {
|
||||
address: [],
|
||||
creditCard: [
|
||||
{
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-type": "amex",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
for (let testcase of TESTCASES) {
|
||||
|
@ -239,6 +239,20 @@ const TESTCASES = [
|
||||
contactType: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Identify credit card type field",
|
||||
document: `<form>
|
||||
<label for="targetElement">Card Type</label>
|
||||
<input id="targetElement" type="text">
|
||||
</form>`,
|
||||
elementId: "targetElement",
|
||||
expectedReturnValue: {
|
||||
fieldName: "cc-type",
|
||||
section: "",
|
||||
addressType: "",
|
||||
contactType: "",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
TESTCASES.forEach(testcase => {
|
||||
|
@ -32,6 +32,12 @@ const MOCK_DOC = MockDocument.createTestDocument(
|
||||
<input id="cc-number" autocomplete="cc-number">
|
||||
<input id="cc-exp-month" autocomplete="cc-exp-month">
|
||||
<input id="cc-exp-year" autocomplete="cc-exp-year">
|
||||
<select id="cc-type">
|
||||
<option value="">Select</option>
|
||||
<option value="visa">Visa</option>
|
||||
<option value="mastercard">Master Card</option>
|
||||
<option value="amex">American Express</option>
|
||||
</select>
|
||||
<input id="submit" type="submit">
|
||||
</form>`
|
||||
);
|
||||
@ -95,6 +101,7 @@ const TESTCASES = [
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": 2000,
|
||||
"cc-type": "amex",
|
||||
},
|
||||
expectedResult: {
|
||||
formSubmission: true,
|
||||
@ -108,6 +115,7 @@ const TESTCASES = [
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": 2000,
|
||||
"cc-type": "amex",
|
||||
},
|
||||
untouchedFields: [],
|
||||
},
|
||||
@ -125,6 +133,7 @@ const TESTCASES = [
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": 2000,
|
||||
"cc-type": "visa",
|
||||
},
|
||||
expectedResult: {
|
||||
formSubmission: true,
|
||||
@ -151,6 +160,7 @@ const TESTCASES = [
|
||||
"cc-number": "5105105105105100",
|
||||
"cc-exp-month": 12,
|
||||
"cc-exp-year": 2000,
|
||||
"cc-type": "visa",
|
||||
},
|
||||
untouchedFields: [],
|
||||
},
|
||||
|
@ -21,6 +21,14 @@ const SUPPORTED_NETWORKS = Object.freeze([
|
||||
"visa",
|
||||
]);
|
||||
|
||||
// This lists stores lower cased variations of popular credit card network
|
||||
// names for matching against strings.
|
||||
const NETWORK_NAMES = {
|
||||
"american express": "amex",
|
||||
"master card": "mastercard",
|
||||
"union pay": "unionpay",
|
||||
};
|
||||
|
||||
// Based on https://en.wikipedia.org/wiki/Payment_card_number
|
||||
//
|
||||
// Notice:
|
||||
@ -231,6 +239,33 @@ class CreditCard {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to retrieve a card network identifier based
|
||||
* on a name.
|
||||
*
|
||||
* @param {string|undefined|null} name
|
||||
*
|
||||
* @returns {string|null}
|
||||
*/
|
||||
static getNetworkFromName(name) {
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
let lcName = name
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.normalize("NFKC");
|
||||
if (SUPPORTED_NETWORKS.includes(lcName)) {
|
||||
return lcName;
|
||||
}
|
||||
for (let term in NETWORK_NAMES) {
|
||||
if (lcName.includes(term)) {
|
||||
return NETWORK_NAMES[term];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the card number is valid and the
|
||||
* expiration date has not passed. Otherwise false.
|
||||
|
@ -528,3 +528,29 @@ add_task(async function test_getType() {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_getNetworkFromName() {
|
||||
const RECOGNIZED_NAMES = [
|
||||
["amex", "amex"],
|
||||
["American Express", "amex"],
|
||||
["american express", "amex"],
|
||||
["mastercard", "mastercard"],
|
||||
["master card", "mastercard"],
|
||||
["MasterCard", "mastercard"],
|
||||
["Master Card", "mastercard"],
|
||||
["Union Pay", "unionpay"],
|
||||
["UnionPay", "unionpay"],
|
||||
["Unionpay", "unionpay"],
|
||||
["unionpay", "unionpay"],
|
||||
|
||||
["Unknown", null],
|
||||
["", null],
|
||||
];
|
||||
for (let [value, type] of RECOGNIZED_NAMES) {
|
||||
Assert.equal(
|
||||
CreditCard.getNetworkFromName(value),
|
||||
type,
|
||||
`Expected ${value} to be recognized as ${type}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user