Bug 1836450 - Redesign the address/credit card normalization flow r=credential-management-reviewers,sgalich

Differential Revision: https://phabricator.services.mozilla.com/D181360
This commit is contained in:
Dimi 2023-07-11 06:32:02 +00:00
Родитель e8fa16ce58
Коммит 9a18720b2c
20 изменённых файлов: 361 добавлений и 337 удалений

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

@ -10,5 +10,6 @@ support-files =
head_address.js
[browser_address_doorhanger_display.js]
[browser_address_doorhanger_not_shown.js]
[browser_address_doorhanger_tel.js]
[browser_address_telemetry.js]

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

@ -0,0 +1,78 @@
"use strict";
const DEFAULT_TEST_DOC = `<form id="form">
<input id="street-addr" autocomplete="street-address">
<select id="address-level1" autocomplete="address-level1">
<option value=""></option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AP">Armed Forces Pacific</option>
<option value="ca">california</option>
<option value="AR">US-Arkansas</option>
<option value="US-CA">California</option>
<option value="CA">California</option>
<option value="US-AZ">US_Arizona</option>
<option value="Ariz">Arizonac</option>
</select>
<input id="city" autocomplete="address-level2">
<input id="country" autocomplete="country">
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel">
<input id="cc-name" autocomplete="cc-name">
<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>`;
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",
},
},
];
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [
["extensions.formautofill.addresses.capture.enabled", true],
["extensions.formautofill.addresses.supported", "on"],
],
});
});
add_task(async function test_save_doorhanger_not_shown() {
for (const TEST of TESTCASES) {
info(`Test ${TEST.description}`);
await BrowserTestUtils.withNewTab(EMPTY_URL, async function (browser) {
await SpecialPowers.spawn(browser, [TEST.document], doc => {
// eslint-disable-next-line no-unsanitized/property
content.document.body.innerHTML = doc;
});
await SimpleTest.promiseFocus(browser);
await focusUpdateSubmitForm(browser, {
focusSelector: `#${TEST.targetElementId}`,
newValues: TEST.formValue,
});
await ensureNoDoorhanger(browser);
});
}
});

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

@ -65,3 +65,51 @@ add_task(async function test_save_doorhanger_tel_invalid() {
await removeAllRecords();
}
});
add_task(async function test_save_doorhanger_tel_concatenated() {
const EXPECTED = [
{
"given-name": "Test User",
organization: "Mozilla",
tel: "+15202486621",
},
];
const MARKUP = `<form id="form">
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="tel-country-code" autocomplete="tel-country-code">
<input id="tel-national" autocomplete="tel-national">
<input type="submit">
</form>`;
await expectSavedAddresses([]);
await BrowserTestUtils.withNewTab(
{ gBrowser, url: EMPTY_URL },
async function (browser) {
await SpecialPowers.spawn(browser, [MARKUP], doc => {
// eslint-disable-next-line no-unsanitized/property
content.document.body.innerHTML = doc;
});
let onPopupShown = waitForPopupShown();
await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name",
newValues: {
"#given-name": "Test User",
"#organization": "Mozilla",
"#tel-country-code": "+1",
"#tel-national": "5202486621",
},
});
await onPopupShown;
await clickDoorhangerButton(MAIN_BUTTON, 0);
}
);
await expectSavedAddresses(EXPECTED);
await removeAllRecords();
});

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

@ -2,7 +2,7 @@
const URL = BASE_URL + "autocomplete_basic.html";
add_task(async function setup_storage() {
add_setup(async function setup_storage() {
await setStorage(
TEST_ADDRESS_2,
TEST_ADDRESS_3,
@ -104,13 +104,7 @@ add_task(async function test_phishing_warning_single_category() {
".autocomplete-richlistitem:last-child"
)._warningTextBox;
ok(warningBox, "Got phishing warning box");
await expectWarningText(browser, "Autofills phone");
is(
warningBox.ownerGlobal.getComputedStyle(warningBox).backgroundColor,
"rgba(248, 232, 28, 0.2)",
"Check warning text background color"
);
await expectWarningText(browser, "Also autofills address");
await closePopup(browser);
}
);

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

@ -25,6 +25,8 @@ skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug
skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
[browser_creditCard_doorhanger_logo.js]
skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
[browser_creditCard_doorhanger_not_shown.js]
skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
[browser_creditCard_doorhanger_sync.js]
skip-if = (!debug && os == "mac") || (os == "win" && ccov) # perma-fail see Bug 1655601, Bug 1655600
[browser_creditCard_dropdown_layout.js]

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

@ -0,0 +1,93 @@
"use strict";
const DEFAULT_TEST_DOC = `<form id="form">
<input id="street-addr" autocomplete="street-address">
<select id="address-level1" autocomplete="address-level1">
<option value=""></option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AP">Armed Forces Pacific</option>
<option value="ca">california</option>
<option value="AR">US-Arkansas</option>
<option value="US-CA">California</option>
<option value="CA">California</option>
<option value="US-AZ">US_Arizona</option>
<option value="Ariz">Arizonac</option>
</select>
<input id="city" autocomplete="address-level2">
<input id="country" autocomplete="country">
<input id="email" autocomplete="email">
<input id="tel" autocomplete="tel">
<input id="cc-name" autocomplete="cc-name">
<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>`;
const TESTCASES = [
{
description: "Should not trigger credit card saving if number is empty",
document: DEFAULT_TEST_DOC,
targetElementId: "cc-name",
formValue: {
"#cc-name": "John Doe",
"#cc-exp-month": 12,
"#cc-exp-year": 2000,
},
},
{
description:
"Should not trigger credit card saving if there is more than one cc-number field but less than four fields",
document: `<form id="form">
<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",
},
},
];
add_task(async function test_save_doorhanger_not_shown() {
for (const TEST of TESTCASES) {
info(`Test ${TEST.description}`);
await BrowserTestUtils.withNewTab(EMPTY_URL, async function (browser) {
await SpecialPowers.spawn(browser, [TEST.document], doc => {
// eslint-disable-next-line no-unsanitized/property
content.document.body.innerHTML = doc;
});
await SimpleTest.promiseFocus(browser);
await focusUpdateSubmitForm(browser, {
focusSelector: `#${TEST.targetElementId}`,
newValues: TEST.formValue,
});
await ensureNoDoorhanger(browser);
});
}
});

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

@ -193,6 +193,7 @@ const MENU_BUTTON = "menubutton";
const TIMEOUT_ENSURE_PROFILE_NOT_SAVED = 1000;
const TIMEOUT_ENSURE_CC_DIALOG_NOT_CLOSED = 500;
const TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN = 1000;
const TIMEOUT_ENSURE_DOORHANGER_NOT_SHOWN = 1000;
async function ensureCreditCardDialogNotClosed(win) {
const unloadHandler = () => {
@ -229,7 +230,16 @@ async function ensureNoAutocompletePopup(browser) {
setTimeout(resolve, TIMEOUT_ENSURE_AUTOCOMPLETE_NOT_SHOWN)
);
const items = getDisplayedPopupItems(browser);
ok(!items.length, "Should not found autocomplete items");
ok(!items.length, "Should not find autocomplete items");
}
async function ensureNoDoorhanger(browser) {
await new Promise(resolve =>
setTimeout(resolve, TIMEOUT_ENSURE_DOORHANGER_NOT_SHOWN)
);
let notifications = PopupNotifications.panel.childNodes;
ok(!notifications.length, "Should not find a doorhanger");
}
/**

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

@ -20,30 +20,36 @@ Form autofill test: preview and highlight
const MOCK_STORAGE = [
{
organization: "Sesame Street",
country: "US",
"street-address": "123 Sesame Street.",
tel: "+13453453456",
},
{
organization: "Mozilla",
country: "US",
"street-address": "331 E. Evelyn Avenue",
},
{
organization: "Tel org",
country: "US",
tel: "+12223334444",
},
{
organization: "Random Org",
country: "US",
"address-level1": "First Admin Level",
tel: "+13453453456",
},
{
organization: "readonly Org",
country: "US",
"address-level1": "First Admin Level",
tel: "+13453453456",
name: "John Doe",
},
{
organization: "test org",
country: "US",
"address-level2": "Not a Town",
tel: "+13453453456",
name: "John Doe",

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

@ -19,10 +19,12 @@ Form autofill test: check if address is saved/updated correctly
let TEST_ADDRESSES = [{
organization: "Sesame Street",
country: "US",
"street-address": "123 Sesame Street.",
tel: "+13453453456",
}, {
organization: "Mozilla",
country: "US",
"street-address": "331 E. Evelyn Avenue",
tel: "+16509030800",
}];

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

@ -63,11 +63,6 @@ const TEST_ADDRESS_EMPTY_AFTER_NORMALIZE = {
country: "XXXXXX",
};
const TEST_ADDRESS_EMPTY_AFTER_UPDATE_ADDRESS_2 = {
"street-address": "",
country: "XXXXXX",
};
ChromeUtils.defineESModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.sys.mjs",
});
@ -245,7 +240,7 @@ add_task(async function test_update() {
let address = await profileStorage.addresses.get(guid, { rawData: true });
Assert.equal(address.country, undefined);
Assert.equal(address.country, "US");
Assert.ok(address.timeLastModified > timeLastModified);
do_check_record_matches(address, TEST_ADDRESS_3);
Assert.equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
@ -317,13 +312,6 @@ add_task(async function test_update() {
);
profileStorage.addresses.update(guid, TEST_ADDRESS_2);
await Assert.rejects(
profileStorage.addresses.update(
guid,
TEST_ADDRESS_EMPTY_AFTER_UPDATE_ADDRESS_2
),
/Record contains no valid field\./
);
});
add_task(async function test_notifyUsed() {

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

@ -64,150 +64,6 @@ const TESTCASES = [
creditCard: [],
},
},
{
description: `"country" using @autocomplete shouldn't be identified aggressively`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="country" autocomplete="country">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
country: "United States",
},
expectedRecord: {
// "United States" is not a valid country, only country-name. See isRecordCreatable.
address: [],
creditCard: [],
},
},
{
description: `"country" using heuristics should be identified aggressively`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="country" name="country">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
country: "United States",
},
expectedRecord: {
address: [
{
"given-name": "John",
organization: "Mozilla",
country: "US",
},
],
creditCard: [],
},
},
{
description: `"tel" related fields should be concatenated`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="tel-country-code" autocomplete="tel-country-code">
<input id="tel-national" autocomplete="tel-national">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
"tel-country-code": "+1",
"tel-national": "1234567890",
},
expectedRecord: {
address: [
{
"given-name": "John",
organization: "Mozilla",
tel: "+11234567890",
},
],
creditCard: [],
},
},
{
description: `"tel" should be removed if it's too short`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="country" autocomplete="country">
<input id="tel" autocomplete="tel-national">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "1234",
},
expectedRecord: {
address: [
{
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "",
},
],
creditCard: [],
},
},
{
description: `"tel" should be removed if it's too long`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="country" autocomplete="country">
<input id="tel" autocomplete="tel-national">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "1234567890123456",
},
expectedRecord: {
address: [
{
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "",
},
],
creditCard: [],
},
},
{
description: `"tel" should be removed if it contains invalid characters`,
document: `<form>
<input id="given-name" autocomplete="given-name">
<input id="organization" autocomplete="organization">
<input id="country" autocomplete="country">
<input id="tel" autocomplete="tel-national">
</form>`,
formValue: {
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "12345###!!!",
},
expectedRecord: {
address: [
{
"given-name": "John",
organization: "Mozilla",
country: "US",
tel: "",
},
],
creditCard: [],
},
},
{
description: "All name related fields should be counted as 1 field only.",
document: `<form>
@ -268,8 +124,6 @@ const TESTCASES = [
"cc-name": "Foo Bar",
"cc-exp": "2022-06",
"cc-type": "Visa",
"cc-exp-month": "6",
"cc-exp-year": "2022",
},
],
},
@ -463,8 +317,8 @@ const TESTCASES = [
creditCard: [
{
"cc-number": "5105105105105100",
"cc-exp-month": "5",
"cc-exp-year": "2026",
"cc-exp-month": "05",
"cc-exp-year": "26",
},
],
},
@ -486,8 +340,6 @@ const TESTCASES = [
{
"cc-number": "5105105105105100",
"cc-exp": "07/27",
"cc-exp-month": "7",
"cc-exp-year": "2027",
},
],
},

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

@ -72,12 +72,6 @@ const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
"cc-exp-year": 12,
};
const TEST_CREDIT_CARD_WITH_INVALID_FIELD = {
"cc-name": "John Doe",
"cc-number": "344060747836806",
"cc-type": { invalid: "invalid" },
};
const TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE = {
"cc-name": "John Doe",
"cc-number": "5103059495477870",
@ -255,11 +249,6 @@ add_task(async function test_add() {
)
);
await Assert.rejects(
profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
/"cc-type" contains invalid data type: object/
);
await Assert.rejects(
profileStorage.creditCards.add({}),
/Record contains no valid field\./
@ -394,14 +383,6 @@ add_task(async function test_update() {
/No matching record\./
);
await Assert.rejects(
profileStorage.creditCards.update(
guid,
TEST_CREDIT_CARD_WITH_INVALID_FIELD
),
/"cc-type" contains invalid data type: object/
);
await Assert.rejects(
profileStorage.creditCards.update(guid, {}),
/Record contains no valid field\./

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

@ -232,16 +232,19 @@ const ADDRESS_RECONCILE_TESTCASES = [
"given-name": "Mark",
"family-name": "Hammond",
tel: "123456",
country: "US",
},
local: [
{
"given-name": "Skip",
"family-name": "Hammond",
country: "US",
},
{
"given-name": "Skip",
"family-name": "Hammond",
organization: "Mozilla",
country: "US",
},
],
remote: {

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

@ -107,9 +107,11 @@ const ADDRESS_COMPUTE_TESTCASES = [
description: 'Has "country"',
address: {
country: "US",
name: "Timothy John Berners-Lee",
},
expectedResult: {
country: "US",
name: "Timothy John Berners-Lee",
"country-name": "United States",
},
},
@ -322,9 +324,11 @@ const ADDRESS_NORMALIZE_TESTCASES = [
description: 'Has "country" in lowercase',
address: {
country: "us",
name: "name",
},
expectedResult: {
country: "US",
name: "name",
},
},
{
@ -334,59 +338,67 @@ const ADDRESS_NORMALIZE_TESTCASES = [
country: "AA",
},
expectedResult: {
country: undefined,
country: "US",
},
},
{
description: 'Has "country-name"',
address: {
"country-name": "united states",
name: "name",
},
expectedResult: {
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has alternative "country-name"',
address: {
"country-name": "america",
name: "name",
},
expectedResult: {
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has "country-name" as a substring',
address: {
"country-name": "test america test",
name: "name",
},
expectedResult: {
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has "country-name" as part of a word',
address: {
"given-name": "John", // Make sure it won't be an empty record.
"country-name": "TRUST",
name: "name", // Make sure it won't be an empty record.
},
expectedResult: {
country: undefined,
"country-name": undefined,
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has unknown "country-name"',
address: {
"given-name": "John", // Make sure it won't be an empty record.
"country-name": "unknown country name",
name: "name", // Make sure it won't be an empty record.
},
expectedResult: {
country: undefined,
"country-name": undefined,
country: "US",
"country-name": "United States",
name: "name",
},
},
{
@ -394,33 +406,37 @@ const ADDRESS_NORMALIZE_TESTCASES = [
address: {
country: "us",
"country-name": "unknown country name",
name: "name",
},
expectedResult: {
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has "country-name" and unknown "country"',
address: {
"given-name": "John", // Make sure it won't be an empty record.
country: "AA",
"country-name": "united states",
name: "name",
},
expectedResult: {
country: undefined,
"country-name": undefined,
country: "US",
"country-name": "United States",
name: "name",
},
},
{
description: 'Has unsupported "country"',
address: {
"given-name": "John", // Make sure it won't be an empty record.
country: "XX",
name: "name",
},
expectedResult: {
country: undefined,
"country-name": undefined,
country: "US",
"country-name": "United States",
name: "name",
},
},

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

@ -39,10 +39,10 @@ skip-if =
[test_clearPopulatedForm.js]
[test_collectFormFields.js]
[test_createRecords.js]
[test_creditCardRecords.js]
skip-if =
tsan # Times out, bug 1612707
apple_silicon # bug 1729554
[test_creditCardRecords.js]
[test_extractLabelStrings.js]
[test_findLabelElements.js]
[test_getAdaptedProfiles.js]

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

@ -465,7 +465,11 @@ export class FormAutofillParent extends JSWindowActorParent {
const storage = lazy.gFormAutofillStorage.addresses;
// Make sure record is normalized before comparing with records in the storage
storage._normalizeRecord(address.record);
try {
storage._normalizeRecord(address.record);
} catch (_e) {
return false;
}
const newAddress = new lazy.AddressComponent(
address.record,
@ -524,7 +528,6 @@ export class FormAutofillParent extends JSWindowActorParent {
return false;
}
dump(`[Dimi]Save: ${JSON.stringify(newAddress.record)}\n`);
return async () => {
await lazy.FormAutofillPrompter.promptToSaveAddress(
browser,
@ -537,13 +540,14 @@ export class FormAutofillParent extends JSWindowActorParent {
}
async _onCreditCardSubmit(creditCard, browser) {
// Let's reset the credit card to empty, and then network auto-detect will
// pick it up.
delete creditCard.record["cc-type"];
const storage = lazy.gFormAutofillStorage.creditCards;
// Make sure record is normalized before comparing with records in the storage
storage._normalizeRecord(creditCard.record);
try {
storage._normalizeRecord(creditCard.record);
} catch (_e) {
return false;
}
// If the record alreay exists in the storage, don't bother showing the prompt
const matchRecord = (

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

@ -1383,7 +1383,7 @@ class AutofillRecords {
_normalizeRecord(record, preserveEmptyFields = false) {
this._normalizeFields(record);
for (let key in record) {
for (const key in record) {
if (!this.VALID_FIELDS.includes(key)) {
// Though we allow unknown fields, certain fields are still protected
// from being changed
@ -1405,7 +1405,10 @@ class AutofillRecords {
}
}
if (!Object.keys(record).length) {
const keys = Object.keys(record);
// By default we ensure there is always a country field, so if this record
// doesn't contain other fields, this is an empty record
if (!keys.length || (keys.length == 1 && keys[0] == "country")) {
throw new Error("Record contains no valid field.");
}
}
@ -1609,13 +1612,13 @@ export class AddressesBase extends AutofillRecords {
}
_normalizeFields(address) {
this._normalizeName(address);
this._normalizeAddress(address);
this._normalizeCountry(address);
this._normalizeTel(address);
this._normalizeCountryFields(address);
this._normalizeNameFields(address);
this._normalizeAddressFields(address);
this._normalizeTelFields(address);
}
_normalizeName(address) {
_normalizeNameFields(address) {
if (address.name) {
let nameParts = lazy.FormAutofillNameUtils.splitName(address.name);
if (!address["given-name"] && nameParts.given) {
@ -1631,7 +1634,7 @@ export class AddressesBase extends AutofillRecords {
delete address.name;
}
_normalizeAddress(address) {
_normalizeAddressFields(address) {
if (STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) {
// Treat "street-address" as "address-line1" if it contains only one line
// and "address-line1" is omitted.
@ -1656,16 +1659,13 @@ export class AddressesBase extends AutofillRecords {
STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
}
_normalizeCountry(address) {
let country;
if (address.country) {
country = address.country.toUpperCase();
} else if (address["country-name"]) {
country = lazy.FormAutofillUtils.identifyCountryCode(
address["country-name"]
);
}
_normalizeCountryFields(address) {
// When we can't identify the country code, it is possible because that the region exists
// in regionNames.properties but not in libaddressinput.
const country =
lazy.FormAutofillUtils.identifyCountryCode(
address.country || address["country-name"]
) || address.country;
// Only values included in the region list will be saved.
let hasLocalizedName = false;
@ -1681,13 +1681,13 @@ export class AddressesBase extends AutofillRecords {
if (country && hasLocalizedName) {
address.country = country;
} else {
delete address.country;
address.country = FormAutofill.DEFAULT_REGION;
}
delete address["country-name"];
}
_normalizeTel(address) {
_normalizeTelFields(address) {
if (address.tel || TEL_COMPONENTS.some(c => !!address[c])) {
lazy.FormAutofillUtils.compressTel(address);
@ -1838,12 +1838,13 @@ export class CreditCardsBase extends AutofillRecords {
}
_normalizeFields(creditCard) {
this._normalizeCCName(creditCard);
this._normalizeCCNumber(creditCard);
this._normalizeCCExpirationDate(creditCard);
this._normalizeCCNameFields(creditCard);
this._normalizeCCNumberFields(creditCard);
this._normalizeCCExpirationDateFields(creditCard);
this._normalizeCCTypeFields(creditCard);
}
_normalizeCCName(creditCard) {
_normalizeCCNameFields(creditCard) {
if (
creditCard["cc-given-name"] ||
creditCard["cc-additional-name"] ||
@ -1862,19 +1863,21 @@ export class CreditCardsBase extends AutofillRecords {
delete creditCard["cc-family-name"];
}
_normalizeCCNumber(creditCard) {
_normalizeCCNumberFields(creditCard) {
if (!("cc-number" in creditCard)) {
return;
}
if (!lazy.CreditCard.isValidNumber(creditCard["cc-number"])) {
delete creditCard["cc-number"];
return;
}
let card = new lazy.CreditCard({ number: creditCard["cc-number"] });
const card = new lazy.CreditCard({ number: creditCard["cc-number"] });
creditCard["cc-number"] = card.number;
}
_normalizeCCExpirationDate(creditCard) {
_normalizeCCExpirationDateFields(creditCard) {
let normalizedExpiration = lazy.CreditCard.normalizeExpiration({
expirationMonth: creditCard["cc-exp-month"],
expirationYear: creditCard["cc-exp-year"],
@ -1893,6 +1896,12 @@ export class CreditCardsBase extends AutofillRecords {
delete creditCard["cc-exp"];
}
_normalizeCCTypeFields(creditCard) {
// Let's overwrite the credit card type with auto-detect algorithm
creditCard["cc-type"] =
lazy.CreditCard.getType(creditCard["cc-number"]) ?? "";
}
_validateFields(creditCard) {
if (!creditCard["cc-number"]) {
throw new Error("Missing/invalid cc-number");

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

@ -98,15 +98,6 @@ export class FormAutofillSection {
throw new TypeError("isRecordCreatable method must be overridden");
}
/*
* Override this method if any data for `createRecord` is needed to be
* normalized before submitting the record.
*
* @param {Object} profile
* A record for normalization.
*/
createNormalizedRecord(data) {}
/**
* Override this method if the profile is needed to apply some transformers.
*
@ -636,7 +627,18 @@ export class FormAutofillSection {
}
});
this.createNormalizedRecord(data);
const telFields = this.fieldDetails.filter(
f => FormAutofillUtils.getCategoryFromFieldName(f.fieldName) == "tel"
);
if (
telFields.length &&
telFields.every(f => data.untouchedFields.includes(f.fieldName))
) {
// No need to verify it if none of related fields are modified after autofilling.
if (!data.untouchedFields.includes("tel")) {
data.untouchedFields.push("tel");
}
}
if (!this.isRecordCreatable(data.record)) {
return null;
@ -692,9 +694,12 @@ export class FormAutofillAddressSection extends FormAutofillSection {
}
isRecordCreatable(record) {
const country = FormAutofillUtils.identifyCountryCode(
record.country || record["country-name"]
);
if (
record.country &&
!FormAutofill.isAutofillAddressesAvailableInCountry(record.country)
country &&
!FormAutofill.isAutofillAddressesAvailableInCountry(country)
) {
// We don't want to save data in the wrong fields due to not having proper
// heuristic regexes in countries we don't yet support.
@ -705,19 +710,21 @@ export class FormAutofillAddressSection extends FormAutofillSection {
return false;
}
let hasName = 0;
let length = 0;
for (let key of Object.keys(record)) {
if (!record[key]) {
continue;
}
if (FormAutofillUtils.getCategoryFromFieldName(key) == "name") {
hasName = 1;
continue;
}
length++;
}
return length + hasName >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
// Multiple name or tel fields are treat as 1 field while countng whether
// the number of fields exceed the valid address secton threshold
const categories = Object.entries(record)
.filter(e => !!e[1])
.map(e => FormAutofillUtils.getCategoryFromFieldName(e[0]));
return (
categories.reduce(
(acc, category) =>
["name", "tel"].includes(category) && acc.includes(category)
? acc
: [...acc, category],
[]
).length >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD
);
}
_getOneLineStreetAddress(address) {
@ -851,53 +858,6 @@ export class FormAutofillAddressSection extends FormAutofillSection {
}
return value;
}
createNormalizedRecord(address) {
if (!address) {
return;
}
// Normalize Country
if (address.record.country) {
let detail = this.getFieldDetailByName("country");
// Try identifying country field aggressively if it doesn't come from
// @autocomplete.
if (detail.reason != "autocomplete") {
let countryCode = FormAutofillUtils.identifyCountryCode(
address.record.country
);
if (countryCode) {
address.record.country = countryCode;
}
}
}
// Normalize Tel
FormAutofillUtils.compressTel(address.record);
if (address.record.tel) {
let allTelComponentsAreUntouched = Object.keys(address.record)
.filter(
field => FormAutofillUtils.getCategoryFromFieldName(field) == "tel"
)
.every(field => address.untouchedFields.includes(field));
if (allTelComponentsAreUntouched) {
// No need to verify it if none of related fields are modified after autofilling.
if (!address.untouchedFields.includes("tel")) {
address.untouchedFields.push("tel");
}
} else {
let strippedNumber = address.record.tel.replace(/[\s\(\)-]/g, "");
// Remove "tel" if it contains invalid characters or the length of its
// number part isn't between 5 and 15.
// (The maximum length of a valid number in E.164 format is 15 digits
// according to https://en.wikipedia.org/wiki/E.164 )
if (!/^(\+?)[\da-zA-Z]{5,15}$/.test(strippedNumber)) {
address.record.tel = "";
}
}
}
}
}
export class FormAutofillCreditCardSection extends FormAutofillSection {
@ -1309,27 +1269,4 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
return true;
}
createNormalizedRecord(creditCard) {
if (!creditCard?.record["cc-number"]) {
return;
}
// Normalize cc-number
creditCard.record["cc-number"] = lazy.CreditCard.normalizeCardNumber(
creditCard.record["cc-number"]
);
// Normalize cc-exp-month and cc-exp-year
let { month, year } = lazy.CreditCard.normalizeExpiration({
expirationString: creditCard.record["cc-exp"],
expirationMonth: creditCard.record["cc-exp-month"],
expirationYear: creditCard.record["cc-exp-year"],
});
if (month) {
creditCard.record["cc-exp-month"] = month;
}
if (year) {
creditCard.record["cc-exp-year"] = year;
}
}
}

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

@ -270,7 +270,7 @@ FormAutofillUtils = {
},
isCCNumber(ccNumber) {
return lazy.CreditCard.isValidNumber(ccNumber);
return ccNumber && lazy.CreditCard.isValidNumber(ccNumber);
},
ensureLoggedIn(promptMessage) {

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

@ -189,9 +189,9 @@ export class CreditCard {
}
// Remove dashes and whitespace
let number = this._number.replace(/[\-\s]/g, "");
const number = CreditCard.normalizeCardNumber(this._number);
let len = number.length;
const len = number.length;
if (len < 12 || len > 19) {
return false;
}