Bug 1358944 - [Form Autofill] Support "country-name" fields. r=MattN, seanlee
MozReview-Commit-ID: IV0WGFhQ35R
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -20,16 +20,17 @@ this.FormAutofillUtils = {
"street-address": "address",
"address-line1": "address",
"address-line2": "address",
"address-line3": "address",
"address-level1": "address",
"address-level2": "address",
"postal-code": "address",
"country": "address",
+ "country-name": "address",
"tel": "tel",
"email": "email",
"cc-name": "creditCard",
"cc-number": "creditCard",
"cc-exp-month": "creditCard",
"cc-exp-year": "creditCard",
},
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -88,53 +88,58 @@ ProfileAutoCompleteResult.prototype = {
* Get the secondary label based on the focused field name and related field names
* in the same form.
* @param {string} focusedFieldName The field name of the focused input
* @param {Array<Object>} allFieldNames The field names in the same section
* @param {object} profile The profile providing the labels to show.
* @returns {string} The secondary label
*/
_getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
- /* TODO: Since "name" is a special case here, so the secondary "name" label
- will be refined when the handling rule for "name" is ready.
- */
- const possibleNameFields = [
- "name",
- "given-name",
- "additional-name",
- "family-name",
- ];
-
- focusedFieldName = possibleNameFields.includes(focusedFieldName) ?
- "name" : focusedFieldName;
+ // We group similar fields into the same field name so we won't pick another
+ // field in the same group as the secondary label.
+ const GROUP_FIELDS = {
+ "name": [
+ "name",
+ "given-name",
+ "additional-name",
+ "family-name",
+ ],
+ "country-name": [
+ "country",
+ "country-name",
+ ],
+ };
const secondaryLabelOrder = [
"street-address", // Street address
"name", // Full name
"address-level2", // City/Town
"organization", // Company or organization name
"address-level1", // Province/State (Standardized code if possible)
- "country", // Country
+ "country-name", // Country name
"postal-code", // Postal code
"tel", // Phone number
"email", // Email address
];
+ for (let field in GROUP_FIELDS) {
+ if (GROUP_FIELDS[field].includes(focusedFieldName)) {
+ focusedFieldName = field;
+ break;
+ }
+ }
+
for (const currentFieldName of secondaryLabelOrder) {
- if (focusedFieldName == currentFieldName ||
- !profile[currentFieldName]) {
+ if (focusedFieldName == currentFieldName || !profile[currentFieldName]) {
continue;
}
- let matching;
- if (currentFieldName == "name") {
- matching = allFieldNames.some(fieldName => possibleNameFields.includes(fieldName));
- } else {
- matching = allFieldNames.includes(currentFieldName);
- }
+ let matching = GROUP_FIELDS[currentFieldName] ?
+ allFieldNames.some(fieldName => GROUP_FIELDS[currentFieldName].includes(fieldName)) :
+ allFieldNames.includes(currentFieldName);
if (matching) {
return profile[currentFieldName];
}
}
return ""; // Nothing matched.
},
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -30,16 +30,17 @@
* email,
*
* // computed fields (These fields are not stored in the file as they are
* // generated at runtime.)
* name,
* address-line1,
* address-line2,
* address-line3,
+ * country-name,
*
* // metadata
* timeCreated, // in ms
* timeLastUsed, // in ms
* timeLastModified, // in ms
* timesUsed
* }
* ],
@@ -90,16 +91,26 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
"resource://formautofill/FormAutofillNameUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
+XPCOMUtils.defineLazyGetter(this, "REGION_NAMES", function() {
+ let regionNames = {};
+ let countries = Services.strings.createBundle("chrome://global/locale/regionNames.properties").getSimpleEnumeration();
+ while (countries.hasMoreElements()) {
+ let country = countries.getNext().QueryInterface(Components.interfaces.nsIPropertyElement);
+ regionNames[country.key.toUpperCase()] = country.value;
+ }
+ return regionNames;
+});
+
const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
const STORAGE_SCHEMA_VERSION = 1;
const ADDRESS_SCHEMA_VERSION = 1;
const CREDIT_CARD_SCHEMA_VERSION = 1;
const VALID_PROFILE_FIELDS = [
"given-name",
@@ -413,16 +424,30 @@ class Addresses extends AutofillRecords
// TODO: we should prevent the dataloss by concatenating the rest of lines
// with a locale-specific character in the future (bug 1360114).
for (let i = 0; i < 3; i++) {
if (streetAddress[i]) {
profile["address-line" + (i + 1)] = streetAddress[i];
}
}
}
+
+ // Compute country name
+ if (profile.country) {
+ if (profile.country == "US") {
+ let countryName = REGION_NAMES[profile.country];
+ if (countryName) {
+ profile["country-name"] = countryName;
+ }
+ } else {
+ // TODO: We only support US in MVP so hide the field if it's not. We
+ // are going to support more countries in bug 1370193.
+ delete profile.country;
+ }
+ }
}
_recordWriteProcessor(profile) {
// Normalize name
if (profile.name) {
let nameParts = FormAutofillNameUtils.splitName(profile.name);
if (!profile["given-name"] && nameParts.given) {
profile["given-name"] = nameParts.given;
@@ -454,16 +479,35 @@ class Addresses extends AutofillRecords
return value;
});
// Concatenate "address-line*" if "street-address" is omitted.
if (!profile["street-address"]) {
profile["street-address"] = addressLines.join("\n");
}
}
+
+ // Normalize country
+ if (profile.country) {
+ let country = profile.country.toUpperCase();
+ // Only values included in the region list will be saved.
+ if (REGION_NAMES[country]) {
+ profile.country = country;
+ } else {
+ delete profile.country;
+ }
+ } else if (profile["country-name"]) {
+ for (let region in REGION_NAMES) {
+ if (REGION_NAMES[region].toLowerCase() == profile["country-name"].toLowerCase()) {
+ profile.country = region;
+ break;
+ }
+ }
+ }
+ delete profile["country-name"];
}
/**
* Merge new address into the specified address if mergeable.
*
* @param {string} guid
* Indicates which address to merge.
* @param {Object} address
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -140,17 +140,17 @@ ManageProfileDialog.prototype = {
// as option text. Possibly improve the algorithm in
// ProfileAutoCompleteResult.jsm and reuse it here.
const fieldOrder = [
"name",
"street-address", // Street address
"address-level2", // City/Town
"organization", // Company or organization name
"address-level1", // Province/State (Standardized code if possible)
- "country", // Country
+ "country-name", // Country name
"postal-code", // Postal code
"tel", // Phone number
"email", // Email address
];
let parts = [];
for (const fieldName of fieldOrder) {
let string = address[fieldName];
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -60,32 +60,44 @@ const ADDRESS_COMPUTE_TESTCASES = [
{
description: "\"street-address\" with multiple lines but line2 is omitted",
address: {
"street-address": "line1\n\nline3",
},
expectedResult: {
"street-address": "line1\n\nline3",
"address-line1": "line1",
- "address-line2": "",
+ "address-line2": undefined,
"address-line3": "line3",
},
},
{
description: "\"street-address\" with 4 lines",
address: {
"street-address": "line1\nline2\nline3\nline4",
},
expectedResult: {
"street-address": "line1\nline2\nline3\nline4",
"address-line1": "line1",
"address-line2": "line2",
"address-line3": "line3",
},
},
+
+ // Country
+ {
+ description: "Has \"country\"",
+ address: {
+ "country": "US",
+ },
+ expectedResult: {
+ "country": "US",
+ "country-name": "United States",
+ },
+ },
];
const ADDRESS_NORMALIZE_TESTCASES = [
// Empty
{
description: "Empty address",
address: {
},
@@ -173,16 +185,78 @@ const ADDRESS_NORMALIZE_TESTCASES = [
"street-address": "street address\nstreet address line 2",
"address-line2": "line2",
"address-line3": "line3",
},
expectedResult: {
"street-address": "street address\nstreet address line 2",
},
},
+
+ // Country
+ {
+ description: "Has \"country\" in lowercase",
+ address: {
+ "country": "us",
+ },
+ expectedResult: {
+ "country": "US",
+ },
+ },
+ {
+ description: "Has unknown \"country\"",
+ address: {
+ "country": "AA",
+ },
+ expectedResult: {
+ "country": undefined,
+ },
+ },
+ {
+ description: "Has \"country-name\"",
+ address: {
+ "country-name": "united states",
+ },
+ expectedResult: {
+ "country": "US",
+ "country-name": undefined,
+ },
+ },
+ {
+ description: "Has unknown \"country-name\"",
+ address: {
+ "country-name": "unknown country name",
+ },
+ expectedResult: {
+ "country": undefined,
+ "country-name": undefined,
+ },
+ },
+ {
+ description: "Has \"country\" and unknown \"country-name\"",
+ address: {
+ "country": "us",
+ "country-name": "unknown country name",
+ },
+ expectedResult: {
+ "country": "US",
+ "country-name": undefined,
+ },
+ },
+ {
+ description: "Has \"country-name\" and unknown \"country\"",
+ address: {
+ "country": "AA",
+ "country-name": "united states",
+ },
+ expectedResult: {
+ "country": undefined,
+ "country-name": undefined,
+ },
+ },
];
const CREDIT_CARD_COMPUTE_TESTCASES = [
// Empty
{
description: "Empty credit card",
creditCard: {
},
@@ -236,17 +310,17 @@ const CREDIT_CARD_NORMALIZE_TESTCASES =
expectedResult: {
"cc-name": "John Doe",
},
},
];
let do_check_record_matches = (expectedRecord, record) => {
for (let key in expectedRecord) {
- do_check_eq(expectedRecord[key], record[key] || "");
+ do_check_eq(expectedRecord[key], record[key]);
}
};
add_task(async function test_computeAddressFields() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
let profileStorage = new ProfileStorage(path);
await profileStorage.initialize();
@@ -272,17 +346,17 @@ add_task(async function test_normalizeAd
await profileStorage.initialize();
ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
await profileStorage._saveImmediately();
profileStorage = new ProfileStorage(path);
await profileStorage.initialize();
- let addresses = profileStorage.addresses.getAll();
+ let addresses = profileStorage.addresses.getAll({noComputedFields: true});
for (let i in addresses) {
do_print("Verify testcase: " + ADDRESS_NORMALIZE_TESTCASES[i].description);
do_check_record_matches(ADDRESS_NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
}
});
add_task(async function test_computeCreditCardFields() {