Bug 1688607 - Enable autofill and capturing when there are multiple credit card number fields. r=sgalich

Enable the feature only in Nightly.

Add functionality for previewing and autofilling into multiple cc-number fields.

Fix test_Lush.js case.

Add logic to handle credit card saving/update when multiple cc-number fields are present.

Add heuristic test for Lufthansa site.

Add test for filling cc-number when there are multiple cc-number fields in a form.

Add test for collecting multiple cc-number fields via collectFormFields.

Add multiple cc-number fields case to onFormSubmitted test.

Add multiple cc-number fields with maxlength=4 case to markAsAutofillField.

Add implementation of the preview and fill test cases for multiple cc-number fields.

Differential Revision: https://phabricator.services.mozilla.com/D127987
This commit is contained in:
Tim Giles 2021-10-14 14:03:38 +00:00
Родитель 709bdad4ad
Коммит 6fe1099c7b
12 изменённых файлов: 682 добавлений и 22 удалений

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -18,5 +18,7 @@ scheme=https
scheme=https
[test_creditcard_autocomplete_off.html]
scheme=https
[test_preview_highlight_with_multiple_cc_number_fields.html]
scheme=https
[test_preview_highlight_with_site_prefill.html]
scheme=https

Просмотреть файл

@ -0,0 +1,162 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test form autofill - preview and highlight with multiple cc number fields</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="../formautofill_common.js"></script>
<script type="text/javascript" src="../../../../../../toolkit/components/satchel/test/satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Form autofill test: preview and highlight multiple cc number fields
<script>
"use strict";
const MOCK_STORAGE = [{
"cc-name": "Test Name",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
}, {
"cc-name": "Timothy Berners-Lee",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
}];
const MOCK_STORAGE_PREVIEW = [{
"cc-name": "Test Name",
"cc-number": "************1045",
"cc-exp-month": "4",
"cc-exp-year": "2017",
}, {
"cc-name": "Timothy Berners-Lee",
"cc-number": "************7870",
"cc-exp-month": "12",
"cc-exp-year": "2022",
}];
/*
This function is similar to checkFormFieldsStyle in formautofill_common.js, but deals with the case
when one value is spread across multiple fields.
This function is needed because of the multiple cc-number filling behavior introduced in Bug 1688607.
Since the cc-number is stored as a whole value in the profile,
there has to be specific handling in the test to assert the correct fillable value.
Otherwise, we would try to grab a value out of profile["cc-number1"] which doesn't exist.
*/
async function checkMultipleCCNumberFormStyle(profile, isPreviewing = true) {
const elements = document.querySelectorAll("input, select");
for (const element of elements) {
let fillableValue;
if (element.id.includes("cc-number") && isPreviewing) {
fillableValue = profile["cc-number"].slice(-8);
} else if (element.id.includes("cc-number")) {
fillableValue = profile["cc-number"];
} else {
fillableValue = profile[element.id];
}
let previewValue = (isPreviewing && fillableValue) || "";
await checkFieldHighlighted(element, !!fillableValue);
await checkFieldPreview(element, previewValue);
}
}
/*
This function sets up 'change' event listeners so that we can safely
assert an element's value after autofilling has occurred.
This is essentially a barebones copy of triggerAutofillAndCheckProfile
that exists in formautofill_common.js.
We can't use triggerAutofillAndCheckProfile because "cc-number1" through "cc-number4"
do not exist in the profile.
Again, we store the whole cc-number in the profile, not its subsections.
So if we tried to grab the element by ID using "cc-number", this element would not exist in the doc,
causing triggerAutofillAndCheckProfile to throw an exception.
*/
async function setupListeners(elements, profile) {
for (const element of elements) {
let id = element.id;
element.addEventListener("change", () => {
let filledValue;
if (id == "cc-number1") {
filledValue = profile["cc-number"].slice(0, 4);
} else if (id == "cc-number2") {
filledValue = profile["cc-number"].slice(4, 8);
} else if (id == "cc-number3") {
filledValue = profile["cc-number"].slice(8, 12);
} else if (id == "cc-number4") {
filledValue = profile["cc-number"].slice(12, 16);
} else {
filledValue = profile[element.id];
}
checkFieldValue(element, filledValue);
}, {once: true})
}
}
initPopupListener();
add_task(async function setup_storage() {
await addCreditCard(MOCK_STORAGE[0]);
await addCreditCard(MOCK_STORAGE[1]);
});
add_task(async function check_preview() {
let canTest = await canTestOSKeyStoreLogin();
if (!canTest) {
todo(canTest, "Cannot test OS key store login on official builds.");
return;
}
let popup = expectPopup();
const focusedInput = await setInput("#cc-name", "");
await popup;
for (let i = 0; i < MOCK_STORAGE_PREVIEW.length; i++) {
synthesizeKey("KEY_ArrowDown");
await notifySelectedIndex(i);
await checkMultipleCCNumberFormStyle(MOCK_STORAGE_PREVIEW[i]);
}
focusedInput.blur();
});
add_task(async function check_filled_highlight() {
let canTest = await canTestOSKeyStoreLogin();
if (!canTest) {
todo(canTest, "Cannot test OS key store login on official builds.");
return;
}
await triggerPopupAndHoverItem("#cc-name", 0);
let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
// filled 1st credit card option
synthesizeKey("KEY_Enter");
await osKeyStoreLoginShown;
let elements = document.querySelectorAll("input, select");
let profile = MOCK_STORAGE[0];
await setupListeners(elements, profile);
await checkMultipleCCNumberFormStyle(profile, false);
});
</script>
<p id="display"></p>
<div id="content">
<form id="form1">
<p>This is a basic credit card form.</p>
<p>card number subsection 1: <input id="cc-number1" maxlength="4"></p>
<p>card number subsection 2: <input id="cc-number2" maxlength="4"></p>
<p>card number subsection 3: <input id="cc-number3" maxlength="4"></p>
<p>card number subsection 4: <input id="cc-number4" maxlength="4"></p>
<p>cardholder name: <input id="cc-name" autocomplete="cc-name"></p>
<p>expiration month: <input id="cc-exp-month" autocomplete="cc-exp-month"></p>
<p>expiration year: <input id="cc-exp-year" autocomplete="cc-exp-year"></p>
</form>
</div>
<pre id="test"></pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,64 @@
/* global runHeuristicsTest */
"use strict";
runHeuristicsTest(
[
{
fixturePath: "Checkout_Payment.html",
expectedResult: [
[
[
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-type",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
transform: "fullCCNumber => fullCCNumber.slice(0, 4)",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
transform: "fullCCNumber => fullCCNumber.slice(4, 8)",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
transform: "fullCCNumber => fullCCNumber.slice(8, 12)",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
transform: "fullCCNumber => fullCCNumber.slice(12, 16)",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-month",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-year",
},
],
],
],
},
],
"../../../fixtures/third_party/Lufthansa/"
);

Просмотреть файл

@ -14,6 +14,7 @@ skip-if = (os == "linux") && ccov # bug 1614100
skip-if = (os == "linux") && ccov # bug 1614100
[test_HomeDepot.js]
skip-if = (os == "linux") && ccov # bug 1614100
[test_Lufthansa.js]
[test_Macys.js]
skip-if = (os == "linux") && ccov # bug 1614100
[test_NewEgg.js]

Просмотреть файл

@ -386,6 +386,34 @@ const TESTCASES = [
"cc-exp-year": "25",
},
},
{
description:
"Fill credit card number fields in a form with multiple cc-number inputs",
document: `<form>
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-number4" maxlength="4">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
</form>`,
focusedInputId: "cc-number1",
profileData: {
guid: "123",
"cc-number": "371449635398431",
"cc-exp-month": 6,
"cc-exp-year": 25,
},
expectedResult: {
guid: "123",
"cc-number1": "3714",
"cc-number2": "4963",
"cc-number3": "5398",
"cc-number4": "431",
"cc-exp-month": "6",
"cc-exp-year": "25",
},
},
];
const TESTCASES_INPUT_UNCHANGED = [

Просмотреть файл

@ -608,6 +608,83 @@ const TESTCASES = [
],
ids: ["cc-number", "cc-exp-month", "cc-exp-year"],
},
{
description: "A valid credit card form with multiple cc-number fields",
document: `<form>
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-number4" maxlength="4">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
</form>`,
sections: [
[
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-month",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-year",
},
],
],
validFieldDetails: [
{ section: "", addressType: "", contactType: "", fieldName: "cc-number" },
{ section: "", addressType: "", contactType: "", fieldName: "cc-number" },
{ section: "", addressType: "", contactType: "", fieldName: "cc-number" },
{ section: "", addressType: "", contactType: "", fieldName: "cc-number" },
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-month",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp-year",
},
],
ids: [
"cc-number1",
"cc-number2",
"cc-number3",
"cc-number4",
"cc-exp-month",
"cc-exp-year",
],
},
{
description: "Three sets of adjacent phone number fields",
document: `<form>

Просмотреть файл

@ -61,6 +61,29 @@ const TESTCASES = [
targetElementId: "cc-number",
expectedResult: ["cc-number", "cc-name", "cc-exp-month", "cc-exp-year"],
},
{
description:
"Form containing multiple cc-number fields without autocomplete attributes.",
document: `<form>
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-number4" maxlength="4">
<input id="cc-name">
<input id="cc-exp-month">
<input id="cc-exp-year">
</form>`,
targetElementId: "cc-number1",
expectedResult: [
"cc-number1",
"cc-number2",
"cc-number3",
"cc-number4",
"cc-name",
"cc-exp-month",
"cc-exp-year",
],
},
];
let markedFieldId = [];

Просмотреть файл

@ -7,9 +7,7 @@ add_task(async function setup() {
));
});
const MOCK_DOC = MockDocument.createTestDocument(
"http://localhost:8080/test/",
`<form id="form1">
const DEFAULT_TEST_DOC = `<form id="form1">
<input id="street-addr" autocomplete="street-address">
<select id="address-level1" autocomplete="address-level1">
<option value=""></option>
@ -39,14 +37,15 @@ const MOCK_DOC = MockDocument.createTestDocument(
<option value="amex">American Express</option>
</select>
<input id="submit" type="submit">
</form>`
);
</form>`;
const TARGET_ELEMENT_ID = "street-addr";
const TESTCASES = [
{
description:
"Should not trigger address saving if the number of fields is less than 3",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
tel: "1-650-903-0800",
@ -57,6 +56,8 @@ const TESTCASES = [
},
{
description: "Should not trigger credit card saving if number is empty",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"cc-name": "John Doe",
"cc-exp-month": 12,
@ -66,8 +67,69 @@ const TESTCASES = [
formSubmission: false,
},
},
{
description:
"Should not trigger credit card saving if there are more than four cc-number fields",
document: `<form id="form1">
<input id="cc-type" autocomplete="cc-type">
<input id="cc-name" autocomplete="cc-name">
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-number4" maxlength="4">
<input id="cc-number5" maxlength="4">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
<input id="submit" type="submit">
</form>
`,
targetElementId: "cc-name",
formValue: {
"cc-name": "John Doe",
"cc-number1": "3714",
"cc-number2": "4963",
"cc-number3": "5398",
"cc-number4": "431",
"cc-exp-month": 12,
"cc-exp-year": 2000,
"cc-type": "amex",
},
expectedResult: {
formSubmission: false,
},
},
{
description:
"Should not trigger credit card saving if there is more than one cc-number field but less than four fields",
document: `<form id="form1">
<input id="cc-type" autocomplete="cc-type">
<input id="cc-name" autocomplete="cc-name">
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
<input id="submit" type="submit">
</form>
`,
targetElementId: "cc-name",
formValue: {
"cc-name": "John Doe",
"cc-number1": "3714",
"cc-number2": "4963",
"cc-number3": "5398",
"cc-exp-month": 12,
"cc-exp-year": 2000,
"cc-type": "amex",
},
expectedResult: {
formSubmission: false,
},
},
{
description: "Trigger address saving",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
country: "US",
@ -96,6 +158,8 @@ const TESTCASES = [
},
{
description: "Trigger credit card saving",
document: DEFAULT_TEST_DOC,
targetElementId: "cc-type",
formValue: {
"cc-name": "John Doe",
"cc-number": "5105105105105100",
@ -123,8 +187,54 @@ const TESTCASES = [
},
},
},
{
description: "Trigger credit card saving using multiple cc-number fields",
document: `<form id="form1">
<input id="cc-type" autocomplete="cc-type">
<input id="cc-name" autocomplete="cc-name">
<input id="cc-number1" maxlength="4">
<input id="cc-number2" maxlength="4">
<input id="cc-number3" maxlength="4">
<input id="cc-number4" maxlength="4">
<input id="cc-exp-month" autocomplete="cc-exp-month">
<input id="cc-exp-year" autocomplete="cc-exp-year">
<input id="submit" type="submit">
</form>`,
targetElementId: "cc-type",
formValue: {
"cc-name": "John Doe",
"cc-number1": "3714",
"cc-number2": "4963",
"cc-number3": "5398",
"cc-number4": "431",
"cc-exp-month": 12,
"cc-exp-year": 2000,
"cc-type": "amex",
},
expectedResult: {
formSubmission: true,
records: {
address: [],
creditCard: [
{
guid: null,
record: {
"cc-name": "John Doe",
"cc-number": "371449635398431",
"cc-exp-month": 12,
"cc-exp-year": 2000,
"cc-type": "amex",
},
untouchedFields: [],
},
],
},
},
},
{
description: "Trigger address and credit card saving",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
country: "US",
@ -170,6 +280,8 @@ const TESTCASES = [
},
{
description: "Profile saved with trimmed string",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue ",
country: "US",
@ -198,6 +310,8 @@ const TESTCASES = [
},
{
description: "Eliminate the field that is empty after trimmed",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
country: "US",
@ -227,6 +341,8 @@ const TESTCASES = [
},
{
description: "Save state with regular select option",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "CA",
"street-addr": "331 E. Evelyn Avenue",
@ -255,6 +371,8 @@ const TESTCASES = [
},
{
description: "Save state with lowercase value",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "ca",
"street-addr": "331 E. Evelyn Avenue",
@ -283,6 +401,8 @@ const TESTCASES = [
},
{
description: "Save state with a country code prefixed to the label",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "AR",
"street-addr": "331 E. Evelyn Avenue",
@ -311,6 +431,8 @@ const TESTCASES = [
},
{
description: "Save state with a country code prefixed to the value",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "US-CA",
"street-addr": "331 E. Evelyn Avenue",
@ -340,6 +462,8 @@ const TESTCASES = [
{
description:
"Save state with a country code prefixed to the value and label",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "US-AZ",
"street-addr": "331 E. Evelyn Avenue",
@ -369,6 +493,8 @@ const TESTCASES = [
{
description:
"Should save select label instead when failed to abbreviate the value",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "Ariz",
"street-addr": "331 E. Evelyn Avenue",
@ -397,6 +523,8 @@ const TESTCASES = [
},
{
description: "Shouldn't save select with multiple selections",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": ["AL", "AK", "AP"],
"street-addr": "331 E. Evelyn Avenue",
@ -426,6 +554,8 @@ const TESTCASES = [
},
{
description: "Shouldn't save select with empty value",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"address-level1": "",
"street-addr": "331 E. Evelyn Avenue",
@ -455,6 +585,8 @@ const TESTCASES = [
},
{
description: "Shouldn't save tel whose length is too short",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
"address-level1": "CA",
@ -484,6 +616,8 @@ const TESTCASES = [
},
{
description: "Shouldn't save tel whose length is too long",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
"address-level1": "CA",
@ -513,6 +647,8 @@ const TESTCASES = [
},
{
description: "Shouldn't save tel which contains invalid characters",
document: DEFAULT_TEST_DOC,
targetElementId: TARGET_ELEMENT_ID,
formValue: {
"street-addr": "331 E. Evelyn Avenue",
"address-level1": "CA",
@ -544,7 +680,11 @@ const TESTCASES = [
add_task(async function handle_invalid_form() {
info("Starting testcase: Test an invalid form element");
let fakeForm = MOCK_DOC.createElement("form");
let doc = MockDocument.createTestDocument(
"http://localhost:8080/test",
DEFAULT_TEST_DOC
);
let fakeForm = doc.createElement("form");
sinon.spy(FormAutofillContent, "_onFormSubmit");
FormAutofillContent.formSubmitted(fakeForm, null);
@ -553,7 +693,11 @@ add_task(async function handle_invalid_form() {
});
add_task(async function autofill_disabled() {
let form = MOCK_DOC.getElementById("form1");
let doc = MockDocument.createTestDocument(
"http://localhost:8080/test",
DEFAULT_TEST_DOC
);
let form = doc.getElementById("form1");
form.reset();
let testcase = {
@ -563,11 +707,11 @@ add_task(async function autofill_disabled() {
"cc-number": "1111222233334444",
};
for (let key in testcase) {
let input = MOCK_DOC.getElementById(key);
let input = doc.getElementById(key);
input.value = testcase[key];
}
let element = MOCK_DOC.getElementById(TARGET_ELEMENT_ID);
let element = doc.getElementById(TARGET_ELEMENT_ID);
FormAutofillContent.identifyAutofillFields(element);
sinon.stub(FormAutofillContent, "_onFormSubmit");
@ -642,13 +786,15 @@ TESTCASES.forEach(testcase => {
"extensions.formautofill.creditCards.enabled",
true
);
let form = MOCK_DOC.getElementById("form1");
let doc = MockDocument.createTestDocument(
"http://localhost:8080/test/",
testcase.document
);
let form = doc.getElementById("form1");
form.reset();
for (let key in testcase.formValue) {
let input = MOCK_DOC.getElementById(key);
let input = doc.getElementById(key);
let value = testcase.formValue[key];
if (ChromeUtils.getClassName(input) === "HTMLSelectElement" && value) {
input.multiple = Array.isArray(value);
[...input.options].forEach(option => {
@ -660,7 +806,7 @@ TESTCASES.forEach(testcase => {
}
sinon.stub(FormAutofillContent, "_onFormSubmit");
let element = MOCK_DOC.getElementById(TARGET_ELEMENT_ID);
let element = doc.getElementById(testcase.targetElementId);
FormAutofillContent.identifyAutofillFields(element);
FormAutofillContent.formSubmitted(form, null);

Просмотреть файл

@ -799,7 +799,6 @@ var FormAutofillContent = {
this.debug("No control is removed or inserted since last collection.");
return;
}
let validDetails = formHandler.collectFormFields();
this._formsDetails.set(formHandler.form.rootElement, formHandler);

Просмотреть файл

@ -284,6 +284,13 @@ class FormAutofillSection {
).slice(-2);
const year2Digits = profile["cc-exp-year"].toString().slice(-2);
profile[key] = `${month2Digits}/${year2Digits}`;
} else if (key == "cc-number") {
// We want to show the last four digits of credit card so that
// the masked credit card previews correctly and appears correctly
// in the autocomplete menu
profile[key] = profile[key].substr(
profile[key].length - maxLength
);
} else {
profile[key] = profile[key].substr(0, maxLength);
}
@ -364,6 +371,10 @@ class FormAutofillSection {
profile[`${fieldDetail.fieldName}-formatted`] ||
profile[fieldDetail.fieldName];
// Bug 1688607: The transform function allows us to handle the multiple credit card number fields case
if (fieldDetail.transform) {
value = fieldDetail.transform(value);
}
if (ChromeUtils.getClassName(element) === "HTMLInputElement" && value) {
// For the focused input element, it will be filled with a valid value
// anyway.
@ -561,6 +572,35 @@ class FormAutofillSection {
return !!this.filledRecordGUID;
}
/**
* Condenses multiple credit card number fields into one fieldDetail
* in order to submit the credit card record correctly.
*
* @param {Array.<object>} condensedDetails
* An array of fieldDetails
* @memberof FormAutofillSection
*/
_condenseMultipleCCNumberFields(condensedDetails) {
let countOfCCNumbers = 0;
// We ignore the cases where there are more than or less than four credit card number
// fields in a form as this is not a valid case for filling the credit card number.
for (let i = condensedDetails.length - 1; i >= 0; i--) {
if (condensedDetails[i].fieldName == "cc-number") {
countOfCCNumbers++;
if (countOfCCNumbers == 4) {
countOfCCNumbers = 0;
condensedDetails[i].fieldValue =
condensedDetails[i].elementWeakRef.get()?.value +
condensedDetails[i + 1].elementWeakRef.get()?.value +
condensedDetails[i + 2].elementWeakRef.get()?.value +
condensedDetails[i + 3].elementWeakRef.get()?.value;
condensedDetails.splice(i + 1, 3);
}
} else {
countOfCCNumbers = 0;
}
}
}
/**
* Return the record that is converted from `fieldDetails` and only valid
* form record is included.
@ -586,11 +626,16 @@ class FormAutofillSection {
if (this.flowId) {
data.flowId = this.flowId;
}
let condensedDetails = this.fieldDetails;
// This NIGHTLY_BUILD statement should be removed as part of Bug 1735562 once the feature is stable
if (AppConstants.NIGHTLY_BUILD) {
this._condenseMultipleCCNumberFields(condensedDetails);
}
details.forEach(detail => {
condensedDetails.forEach(detail => {
let element = detail.elementWeakRef.get();
// Remove the unnecessary spaces
let value = element && element.value.trim();
let value = detail.fieldValue ?? (element && element.value.trim());
value = this.computeFillingValue(value, detail, element);
if (!value || value.length > FormAutofillUtils.MAX_FIELD_VALUE_LENGTH) {
@ -1179,7 +1224,7 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
}
/**
* Customize for previewing prorifle.
* Customize for previewing profile
*
* @param {Object} profile
* A profile for pre-processing before previewing values.
@ -1190,11 +1235,15 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
// disabled.
if (profile["cc-number-decrypted"]) {
profile["cc-number"] = profile["cc-number-decrypted"];
} else if (!profile["cc-number"].startsWith("****")) {
// Show the previewed credit card as "**** 4444" which is
// needed when a credit card number field has a maxlength of four.
profile["cc-number"] = "****" + profile["cc-number"];
}
}
/**
* Customize for filling prorifle.
* Customize for filling profile
*
* @param {Object} profile
* A profile for pre-processing before filling values.

Просмотреть файл

@ -17,6 +17,10 @@ const { XPCOMUtils } = ChromeUtils.import(
const { FormAutofill } = ChromeUtils.import(
"resource://autofill/FormAutofill.jsm"
);
// This AppConstants import can be removed as part of Bug 1735562
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FormAutofillUtils",
@ -143,17 +147,69 @@ class FieldScanner {
});
}
_classifyMultipleCCNumberFields() {
if (this._sections.length != 4) {
return;
}
let firstDetails = this._sections[0].fieldDetails;
// Ensure that there is only one cc-number field and it is last in this subsection
if (
firstDetails.findIndex(detail => detail.fieldName == "cc-number") !=
firstDetails.length - 1
) {
return;
}
let secondDetails = this._sections[1].fieldDetails;
// Ensure that the second cc-number field is only element of this subsection
if (
!(secondDetails.length == 1 && secondDetails[0].fieldName == "cc-number")
) {
return;
}
let thirdDetails = this._sections[2].fieldDetails;
// Ensure that the third cc-number field is only element of this subsection
if (
!(thirdDetails.length == 1 && thirdDetails[0].fieldName == "cc-number")
) {
return;
}
let fourthDetails = this._sections[3].fieldDetails;
// Ensure that there is only one cc-number field and it is first element of 4th subsection
let foundCCNumber = false;
for (let [index, detail] of fourthDetails.entries()) {
if (detail.fieldName == "cc-number") {
if (index == 0) {
foundCCNumber = true;
} else {
return;
}
}
}
if (!foundCCNumber) {
return;
}
// Collect the other subsections into the first subsection
// and then remove the other subsections from the list
this._sections[0].fieldDetails = firstDetails.concat(
secondDetails,
thirdDetails,
fourthDetails
);
this._sections.splice(1);
}
_classifySections() {
let fieldDetails = this._sections[0].fieldDetails;
this._sections = [];
let seenTypes = new Set();
let previousType;
let sectionCount = 0;
for (let fieldDetail of fieldDetails) {
if (!fieldDetail.fieldName) {
continue;
}
if (
seenTypes.has(fieldDetail.fieldName) &&
(previousType != fieldDetail.fieldName ||
@ -169,6 +225,10 @@ class FieldScanner {
fieldDetail
);
}
// This NIGHTLY_BUILD statement should be removed as part of Bug 1735562 once the feature is stable
if (AppConstants.NIGHTLY_BUILD) {
this._classifyMultipleCCNumberFields();
}
}
/**
@ -266,10 +326,36 @@ class FieldScanner {
return (
field1.section == field2.section &&
field1.addressType == field2.addressType &&
field1.fieldName == field2.fieldName
field1.fieldName == field2.fieldName &&
!field1.transform &&
!field2.transform
);
}
/**
* When a site has four credit card number fields and
* these fields have a max length of four
* then we transform the credit card number into
* four subsections in order to fill correctly.
*
* @param {Array<Object>} creditCardFieldDetails
* The credit card field details to be transformed for multiple cc-number fields filling
* @memberof FieldScanner
*/
_transformCCNumberForMultipleFields(creditCardFieldDetails) {
let ccNumberFields = creditCardFieldDetails.filter(
field =>
field.fieldName == "cc-number" &&
field.elementWeakRef.get().maxLength == 4
);
if (ccNumberFields.length == 4) {
ccNumberFields[0].transform = fullCCNumber => fullCCNumber.slice(0, 4);
ccNumberFields[1].transform = fullCCNumber => fullCCNumber.slice(4, 8);
ccNumberFields[2].transform = fullCCNumber => fullCCNumber.slice(8, 12);
ccNumberFields[3].transform = fullCCNumber => fullCCNumber.slice(12, 16);
}
}
/**
* Provide the final field details without invalid field name, and the
* duplicated fields will be removed as well. For the debugging purpose,
@ -301,7 +387,7 @@ class FieldScanner {
);
}
}
this._transformCCNumberForMultipleFields(creditCardFieldDetails);
return [
{
type: FormAutofillUtils.SECTION_TYPES.ADDRESS,