--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -24,16 +24,22 @@ this.FormAutofillUtils = {
"address-line2": "address",
"address-line3": "address",
"address-level1": "address",
"address-level2": "address",
"postal-code": "address",
"country": "address",
"country-name": "address",
"tel": "tel",
+ "tel-country-code": "tel",
+ "tel-national": "tel",
+ "tel-area-code": "tel",
+ "tel-local": "tel",
+ "tel-local-prefix": "tel",
+ "tel-local-suffix": "tel",
"email": "email",
"cc-name": "creditCard",
"cc-number": "creditCard",
"cc-exp-month": "creditCard",
"cc-exp-year": "creditCard",
},
_addressDataLoaded: false,
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -21,26 +21,32 @@
* additional-name,
* family-name,
* organization, // Company
* street-address, // (Multiline)
* address-level2, // City/Town
* address-level1, // Province (Standardized code if possible)
* postal-code,
* country, // ISO 3166
- * tel,
+ * tel, // Stored in E.164 format
* email,
*
- * // computed fields (These fields are not stored in the file as they are
- * // generated at runtime.)
+ * // computed fields (These fields are computed based on the above fields
+ * // and are not allowed to be modified directly.)
* name,
* address-line1,
* address-line2,
* address-line3,
* country-name,
+ * tel-country-code,
+ * tel-national,
+ * tel-area-code,
+ * tel-local,
+ * tel-local-prefix,
+ * tel-local-suffix,
*
* // metadata
* timeCreated, // in ms
* timeLastUsed, // in ms
* timeLastModified, // in ms
* timesUsed
* }
* ],
@@ -52,18 +58,18 @@
* // credit card fields
* cc-name,
* cc-number-encrypted,
* cc-number-masked, // e.g. ************1234
* cc-exp-month,
* cc-exp-year, // 2-digit year will be converted to 4 digits
* // upon saving
*
- * // computed fields (These fields are not stored in the file as they are
- * // generated at runtime.)
+ * // computed fields (These fields are computed based on the above fields
+ * // and are not allowed to be modified directly.)
* cc-given-name,
* cc-additional-name,
* cc-family-name,
*
* // metadata
* timeCreated, // in ms
* timeLastUsed, // in ms
* timeLastModified, // in ms
@@ -86,16 +92,18 @@ Cu.import("resource://gre/modules/Servic
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "JSONFile",
"resource://gre/modules/JSONFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
"resource://formautofill/FormAutofillNameUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PhoneNumber",
+ "resource://formautofill/phonenumberutils/PhoneNumber.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();
@@ -127,20 +135,29 @@ const VALID_ADDRESS_FIELDS = [
];
const STREET_ADDRESS_COMPONENTS = [
"address-line1",
"address-line2",
"address-line3",
];
+const TEL_COMPONENTS = [
+ "tel-country-code",
+ "tel-national",
+ "tel-area-code",
+ "tel-local",
+ "tel-local-prefix",
+ "tel-local-suffix",
+];
+
const VALID_ADDRESS_COMPUTED_FIELDS = [
"name",
"country-name",
-].concat(STREET_ADDRESS_COMPONENTS);
+].concat(STREET_ADDRESS_COMPONENTS, TEL_COMPONENTS);
const VALID_CREDIT_CARD_FIELDS = [
"cc-name",
"cc-number-encrypted",
"cc-number-masked",
"cc-exp-month",
"cc-exp-year",
];
@@ -548,54 +565,104 @@ class Addresses extends AutofillRecords
if (address.country && REGION_NAMES[address.country]) {
address["country-name"] = REGION_NAMES[address.country];
} else {
address["country-name"] = "";
}
hasNewComputedFields = true;
}
+ // Compute tel
+ if (!("tel-national" in address)) {
+ if (address.tel) {
+ // Set "US" as the default region as we only support "en-US" for now.
+ let browserCountryCode = Services.prefs.getCharPref("browser.search.countryCode", "US");
+ let tel = PhoneNumber.Parse(address.tel, address.country || browserCountryCode);
+ if (tel) {
+ if (tel.countryCode) {
+ address["tel-country-code"] = tel.countryCode;
+ }
+ if (tel.nationalNumber) {
+ address["tel-national"] = tel.nationalNumber;
+ }
+
+ // PhoneNumberUtils doesn't support parsing the components of a telephone
+ // number so we hard coded the parser for US numbers only. We will need
+ // to figure out how to parse numbers from other regions when we support
+ // new countries in the future.
+ if (tel.nationalNumber && tel.countryCode == "+1") {
+ let telComponents = tel.nationalNumber.match(/(\d{3})((\d{3})(\d{4}))$/);
+ if (telComponents) {
+ address["tel-area-code"] = telComponents[1];
+ address["tel-local"] = telComponents[2];
+ address["tel-local-prefix"] = telComponents[3];
+ address["tel-local-suffix"] = telComponents[4];
+ }
+ }
+ } else {
+ // Treat "tel" as "tel-national" directly if it can't be parsed.
+ address["tel-national"] = address.tel;
+ }
+ }
+
+ TEL_COMPONENTS.forEach(c => {
+ address[c] = address[c] || "";
+ });
+ }
+
return hasNewComputedFields;
}
_normalizeFields(address) {
- // Normalize name
- if (address.name) {
- let nameParts = FormAutofillNameUtils.splitName(address.name);
- if (!address["given-name"] && nameParts.given) {
- address["given-name"] = nameParts.given;
- }
- if (!address["additional-name"] && nameParts.middle) {
- address["additional-name"] = nameParts.middle;
- }
- if (!address["family-name"] && nameParts.family) {
- address["family-name"] = nameParts.family;
- }
- delete address.name;
+ this._normalizeName(address);
+ this._normalizeAddress(address);
+ this._normalizeCountry(address);
+ this._normalizeTel(address);
+ }
+
+ _normalizeName(address) {
+ if (!address.name) {
+ return;
}
- // Normalize address lines
- 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.
- if (!address["address-line1"] && address["street-address"] &&
- !address["street-address"].includes("\n")) {
- address["address-line1"] = address["street-address"];
- delete address["street-address"];
- }
+ let nameParts = FormAutofillNameUtils.splitName(address.name);
+ if (!address["given-name"] && nameParts.given) {
+ address["given-name"] = nameParts.given;
+ }
+ if (!address["additional-name"] && nameParts.middle) {
+ address["additional-name"] = nameParts.middle;
+ }
+ if (!address["family-name"] && nameParts.family) {
+ address["family-name"] = nameParts.family;
+ }
+ delete address.name;
+ }
- // Concatenate "address-line*" if "street-address" is omitted.
- if (!address["street-address"]) {
- address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
- }
-
- STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+ _normalizeAddress(address) {
+ if (STREET_ADDRESS_COMPONENTS.every(c => !address[c])) {
+ return;
}
- // Normalize country
+ // Treat "street-address" as "address-line1" if it contains only one line
+ // and "address-line1" is omitted.
+ if (!address["address-line1"] && address["street-address"] &&
+ !address["street-address"].includes("\n")) {
+ address["address-line1"] = address["street-address"];
+ delete address["street-address"];
+ }
+
+ // Concatenate "address-line*" if "street-address" is omitted.
+ if (!address["street-address"]) {
+ address["street-address"] = STREET_ADDRESS_COMPONENTS.map(c => address[c]).join("\n");
+ }
+
+ STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+ }
+
+ _normalizeCountry(address) {
if (address.country) {
let country = address.country.toUpperCase();
// Only values included in the region list will be saved.
if (REGION_NAMES[country]) {
address.country = country;
} else {
delete address.country;
}
@@ -605,16 +672,48 @@ class Addresses extends AutofillRecords
address.country = region;
break;
}
}
}
delete address["country-name"];
}
+ _normalizeTel(address) {
+ if (!address.tel && TEL_COMPONENTS.every(c => !address[c])) {
+ return;
+ }
+
+ // Set "US" as the default region as we only support "en-US" for now.
+ let browserCountryCode = Services.prefs.getCharPref("browser.search.countryCode", "US");
+ let region = address["tel-country-code"] || address.country || browserCountryCode;
+ let number;
+
+ if (address.tel) {
+ number = address.tel;
+ } else if (address["tel-national"]) {
+ number = address["tel-national"];
+ } else if (address["tel-local"]) {
+ number = (address["tel-area-code"] || "") + address["tel-local"];
+ } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
+ number = (address["tel-area-code"] || "") + address["tel-local-prefix"] + address["tel-local-suffix"];
+ }
+
+ let tel = PhoneNumber.Parse(number, region);
+ if (tel) {
+ // Force to save numbers in E.164 format if parse success.
+ address.tel = tel.internationalNumber;
+ } else if (!address.tel) {
+ // Save the original number anyway if "tel" is omitted.
+ address.tel = number;
+ }
+
+ TEL_COMPONENTS.forEach(c => delete address[c]);
+ }
+
/**
* Merge new address into the specified address if mergeable.
*
* @param {string} guid
* Indicates which address to merge.
* @param {Object} address
* The new address used to merge into the old one.
* @returns {boolean}
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -14,17 +14,17 @@ const TEST_ADDRESS_1 = {
"additional-name": "R.",
"family-name": "Smith",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
"address-level1": "MA",
"postal-code": "02139",
country: "US",
- tel: "+1 617 253 5702",
+ tel: "+16172535702",
email: "timbl@w3.org",
};
const TEST_ADDRESS_2 = {
"street-address": "Some Address",
country: "US",
};
--- a/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -19,23 +19,23 @@ Form autofill test: simple form address
"use strict";
const {FormAutofillUtils} = SpecialPowers.Cu.import("resource://formautofill/FormAutofillUtils.jsm");
let MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.\n2-line\n3-line",
- tel: "1-345-345-3456",
+ tel: "+13453453456",
country: "US",
"address-level1": "NY",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue\n2-line\n3-line",
- tel: "1-650-903-0800",
+ tel: "+16509030800",
country: "US",
"address-level1": "CA",
}];
function checkElementFilled(element, expectedvalue) {
return [
new Promise(resolve => {
element.addEventListener("input", function onInput() {
@@ -82,31 +82,31 @@ function checkFormFilled(address) {
async function setupAddressStorage() {
await addAddress(MOCK_STORAGE[0]);
await addAddress(MOCK_STORAGE[1]);
}
async function setupFormHistory() {
await updateFormHistory([
- {op: "add", fieldname: "tel", value: "1-234-567-890"},
+ {op: "add", fieldname: "tel", value: "+1234567890"},
{op: "add", fieldname: "email", value: "foo@mozilla.com"},
]);
}
initPopupListener();
// Form with history only.
add_task(async function history_only_menu_checking() {
await setupFormHistory();
await setInput("#tel", "");
doKey("down");
await expectPopup();
- checkMenuEntries(["1-234-567-890"], false);
+ checkMenuEntries(["+1234567890"], false);
});
// Form with both history and address storage.
add_task(async function check_menu_when_both_existed() {
await setupAddressStorage();
await setInput("#organization", "");
doKey("down");
@@ -172,17 +172,17 @@ add_task(async function check_fields_aft
await checkFormFilled(MOCK_STORAGE[1]);
});
// Fallback to history search after autofill address.
add_task(async function check_fallback_after_form_autofill() {
await setInput("#tel", "");
doKey("down");
await expectPopup();
- checkMenuEntries(["1-234-567-890"], false);
+ checkMenuEntries(["+1234567890"], false);
});
// Resume form autofill once all the autofilled fileds are changed.
add_task(async function check_form_autofill_resume() {
document.querySelector("#tel").blur();
document.querySelector("#form1").reset();
await setInput("#tel", "");
doKey("down");
--- a/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
+++ b/browser/extensions/formautofill/test/mochitest/test_formautofill_preview_highlight.html
@@ -18,23 +18,23 @@ Form autofill test: preview and highligh
/* import-globals-from formautofill_common.js */
"use strict";
let defaultTextColor;
const MOCK_STORAGE = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
- tel: "1-345-345-3456",
+ tel: "+13453453456",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
}, {
organization: "Tel org",
- tel: "2-222-333-444",
+ tel: "+12223334444",
}];
// We could not get ManuallyManagedState of element now, so directly check if
// filter and text color style are applied.
function checkFieldPreview(elem, expectedText) {
const computedStyle = window.getComputedStyle(elem);
const isStyleApplied = computedStyle.getPropertyValue("filter") !== "none" &&
computedStyle.getPropertyValue("color") !== defaultTextColor;
--- a/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html
+++ b/browser/extensions/formautofill/test/mochitest/test_on_address_submission.html
@@ -17,21 +17,21 @@ Form autofill test: check if address is
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
/* import-globals-from formautofill_common.js */
"use strict";
let TEST_ADDRESSES = [{
organization: "Sesame Street",
"street-address": "123 Sesame Street.",
- tel: "1-345-345-3456",
+ tel: "+13453453456",
}, {
organization: "Mozilla",
"street-address": "331 E. Evelyn Avenue",
- tel: "1-650-903-0800",
+ tel: "+16509030800",
}];
initPopupListener();
// Submit first address for saving.
add_task(async function check_storage_after_form_submitted() {
// We already verified the first time use case in browser test
await SpecialPowers.pushPrefEnv({
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -11,17 +11,17 @@ const TEST_ADDRESS_1 = {
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
"address-level1": "MA",
"postal-code": "02139",
country: "US",
- tel: "+1 617 253 5702",
+ tel: "+16172535702",
email: "timbl@w3.org",
};
const TEST_ADDRESS_2 = {
"street-address": "Some Address",
country: "US",
};
@@ -43,67 +43,67 @@ const TEST_ADDRESS_WITH_INVALID_FIELD =
};
const MERGE_TESTCASES = [
{
description: "Merge a superset",
addressInStorage: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
},
addressToMerge: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
expectedAddress: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
},
{
description: "Merge a subset",
addressInStorage: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
addressToMerge: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
},
expectedAddress: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
},
{
description: "Merge an address with partial overlaps",
addressInStorage: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
},
addressToMerge: {
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
expectedAddress: {
"given-name": "Timothy",
"street-address": "331 E. Evelyn Avenue",
- "tel": "1-650-903-0800",
+ "tel": "+16509030800",
country: "US",
},
},
];
let do_check_record_matches = (recordWithMeta, record) => {
for (let key in record) {
do_check_eq(recordWithMeta[key], record[key]);
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -100,16 +100,94 @@ const ADDRESS_COMPUTE_TESTCASES = [
address: {
"country": "US",
},
expectedResult: {
"country": "US",
"country-name": "United States",
},
},
+
+ // Tel
+ {
+ description: "\"tel\" with US country code",
+ address: {
+ "tel": "+16172535702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ "tel-country-code": "+1",
+ "tel-national": "6172535702",
+ "tel-area-code": "617",
+ "tel-local": "2535702",
+ "tel-local-prefix": "253",
+ "tel-local-suffix": "5702",
+ },
+ },
+ {
+ description: "\"tel\" with TW country code (the components won't be parsed)",
+ address: {
+ "tel": "+886212345678",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ "tel-country-code": "+886",
+ "tel-national": "0212345678",
+ "tel-area-code": "",
+ "tel-local": "",
+ "tel-local-prefix": "",
+ "tel-local-suffix": "",
+ },
+ },
+ {
+ description: "\"tel\" without country code so use \"US\" as default resion",
+ address: {
+ "tel": "6172535702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ "tel-country-code": "+1",
+ "tel-national": "6172535702",
+ "tel-area-code": "617",
+ "tel-local": "2535702",
+ "tel-local-prefix": "253",
+ "tel-local-suffix": "5702",
+ },
+ },
+ {
+ description: "\"tel\" without country code but \"country\" is \"TW\"",
+ address: {
+ "tel": "0212345678",
+ "country": "TW",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ "tel-country-code": "+886",
+ "tel-national": "0212345678",
+ "tel-area-code": "",
+ "tel-local": "",
+ "tel-local-prefix": "",
+ "tel-local-suffix": "",
+ },
+ },
+ {
+ description: "\"tel\" can't be parsed so leave it as-is",
+ address: {
+ "tel": "12345",
+ },
+ expectedResult: {
+ "tel": "12345",
+ "tel-country-code": "",
+ "tel-national": "12345",
+ "tel-area-code": "",
+ "tel-local": "",
+ "tel-local-prefix": "",
+ "tel-local-suffix": "",
+ },
+ },
];
const ADDRESS_NORMALIZE_TESTCASES = [
// Empty
{
description: "Empty address",
address: {
},
@@ -150,17 +228,16 @@ const ADDRESS_NORMALIZE_TESTCASES = [
"given-name": "Timothy",
},
expectedResult: {
"given-name": "Timothy",
"family-name": "Doe",
},
},
-
// Address
{
description: "Has \"address-line1~3\" and \"street-address\" is omitted",
address: {
"address-line1": "line1",
"address-line2": "line2",
"address-line3": "line3",
},
@@ -269,16 +346,107 @@ const ADDRESS_NORMALIZE_TESTCASES = [
address: {
"country": "CA",
},
expectedResult: {
"country": undefined,
"country-name": "",
},
},
+
+ // Tel
+ {
+ description: "Has \"tel\" with country code",
+ address: {
+ "tel": "+16172535702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ },
+ },
+ {
+ description: "Has \"tel\" without country code but \"country\" is set",
+ address: {
+ "tel": "0212345678",
+ "country": "TW",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ },
+ },
+ {
+ description: "Has \"tel\" without country code and \"country\" so use \"US\" as default region",
+ address: {
+ "tel": "6172535702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ },
+ },
+ {
+ description: "\"tel\" can't be parsed so leave it as-is",
+ address: {
+ "tel": "12345",
+ },
+ expectedResult: {
+ "tel": "12345",
+ },
+ },
+ {
+ description: "Has \"tel-national\" and \"tel-country-code\"",
+ address: {
+ "tel-national": "0212345678",
+ "tel-country-code": "+886",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ },
+ },
+ {
+ description: "Has \"tel-national\" and \"country\"",
+ address: {
+ "tel-national": "0212345678",
+ "country": "TW",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ },
+ },
+ {
+ description: "Has \"tel-national\", \"tel-country-code\" and \"country\"",
+ address: {
+ "tel-national": "0212345678",
+ "tel-country-code": "+886",
+ "country": "US",
+ },
+ expectedResult: {
+ "tel": "+886212345678",
+ },
+ },
+ {
+ description: "Has \"tel-area-code\" and \"tel-local\"",
+ address: {
+ "tel-area-code": "617",
+ "tel-local": "2535702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ },
+ },
+ {
+ description: "Has \"tel-area-code\", \"tel-local-prefix\" and \"tel-local-suffix\"",
+ address: {
+ "tel-area-code": "617",
+ "tel-local-prefix": "253",
+ "tel-local-suffix": "5702",
+ },
+ expectedResult: {
+ "tel": "+16172535702",
+ },
+ },
];
const CREDIT_CARD_COMPUTE_TESTCASES = [
// Empty
{
description: "Empty credit card",
creditCard: {
},