--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -652,23 +652,25 @@ this.FormAutofillUtils = {
/**
* Get formatting information of a given country
* @param {string} country
* @returns {object}
* {
* {string} addressLevel1Label
* {string} postalCodeLabel
+ * {object} fieldsOrder
* }
*/
getFormFormat(country) {
const dataset = this.getCountryAddressData(country);
return {
"addressLevel1Label": dataset.state_name_type || "province",
"postalCodeLabel": dataset.zip_name_type || "postalCode",
+ "fieldsOrder": this.parseAddressFormat(dataset.fmt || "%N%n%O%n%A%n%C, %S %Z"),
};
},
/**
* Localize elements with "data-localization" attribute
* @param {string} bundleURI
* @param {DOMElement} root
*/
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -8,48 +8,52 @@
<title data-localization="addNewAddressTitle"/>
<link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
<link rel="stylesheet" href="chrome://formautofill-shared/skin/editAddress.css"/>
<link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
<script src="chrome://formautofill/content/editDialog.js"></script>
</head>
<body>
<form id="form" autocomplete="off">
- <label id="given-name-container">
- <span data-localization="givenName"/>
- <input id="given-name" type="text"/>
- </label>
- <label id="additional-name-container">
- <span data-localization="additionalName"/>
- <input id="additional-name" type="text"/>
- </label>
- <label id="family-name-container">
- <span data-localization="familyName"/>
- <input id="family-name" type="text"/>
- </label>
- <label id="organization-container">
- <span data-localization="organization2"/>
- <input id="organization" type="text"/>
- </label>
- <label id="street-address-container">
- <span data-localization="streetAddress"/>
- <textarea id="street-address" rows="3"/>
- </label>
- <label id="address-level2-container">
- <span data-localization="city"/>
- <input id="address-level2" type="text"/>
- </label>
- <label id="address-level1-container">
- <span/>
- <input id="address-level1" type="text"/>
- </label>
- <label id="postal-code-container">
- <span/>
- <input id="postal-code" type="text"/>
- </label>
+ <div>
+ <div id="name-container">
+ <label id="given-name-container">
+ <span data-localization="givenName"/>
+ <input id="given-name" type="text"/>
+ </label>
+ <label id="additional-name-container">
+ <span data-localization="additionalName"/>
+ <input id="additional-name" type="text"/>
+ </label>
+ <label id="family-name-container">
+ <span data-localization="familyName"/>
+ <input id="family-name" type="text"/>
+ </label>
+ </div>
+ <label id="organization-container">
+ <span data-localization="organization2"/>
+ <input id="organization" type="text"/>
+ </label>
+ <label id="street-address-container">
+ <span data-localization="streetAddress"/>
+ <textarea id="street-address" rows="3"/>
+ </label>
+ <label id="address-level2-container">
+ <span data-localization="city"/>
+ <input id="address-level2" type="text"/>
+ </label>
+ <label id="address-level1-container">
+ <span/>
+ <input id="address-level1" type="text"/>
+ </label>
+ <label id="postal-code-container">
+ <span/>
+ <input id="postal-code" type="text"/>
+ </label>
+ </div>
<label id="country-container">
<span data-localization="country"/>
<select id="country">
<option/>
</select>
</label>
<p id="country-warning-message" data-localization="countryWarningMessage2"/>
<label id="email-container">
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -115,16 +115,20 @@ class EditDialog {
case "input": {
this.handleInput(event);
break;
}
case "keypress": {
this.handleKeyPress(event);
break;
}
+ case "change": {
+ this.handleChange(event);
+ break;
+ }
}
}
/**
* Handle click events
*
* @param {DOMEvent} event
*/
@@ -175,35 +179,75 @@ class EditDialog {
/**
* Remove event listener
*/
detachEventListeners() {
window.removeEventListener("keypress", this);
this._elements.controlsContainer.removeEventListener("click", this);
document.removeEventListener("input", this);
}
+
+ // An interface to be inherited.
+ localizeDocument() {}
+
+ // An interface to be inherited.
+ handleSubmit(event) {}
+
+ // An interface to be inherited.
+ handleChange(event) {}
}
class EditAddress extends EditDialog {
constructor(elements, record) {
super("addresses", elements, record);
this.formatForm(record && record.country);
}
/**
* Format the form based on country. The address-level1 and postal-code labels
* should be specific to the given country.
* @param {string} country
*/
formatForm(country) {
- // TODO: Use fmt to show/hide and order fields (Bug 1383687)
- const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
+ const {addressLevel1Label, postalCodeLabel, fieldsOrder} = FormAutofillUtils.getFormFormat(country);
this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+ this.arrangeFields(fieldsOrder);
+ }
+
+ arrangeFields(fieldsOrder) {
+ let fields = [
+ "name",
+ "organization",
+ "street-address",
+ "address-level2",
+ "address-level1",
+ "postal-code",
+ ];
+ let inputs = [];
+ for (let i = 0; i < fieldsOrder.length; i++) {
+ let {fieldId, newLine} = fieldsOrder[i];
+ let container = document.getElementById(`${fieldId}-container`);
+ inputs.push(...container.querySelectorAll("input, textarea, select"));
+ container.style.display = "flex";
+ container.style.order = i;
+ container.style.pageBreakAfter = newLine ? "always" : "auto";
+ // Remove the field from the list of fields
+ fields.splice(fields.indexOf(fieldId), 1);
+ }
+ for (let i = 0; i < inputs.length; i++) {
+ // Assign tabIndex starting from 1
+ inputs[i].tabIndex = i + 1;
+ }
+ // Hide the remaining fields
+ for (let field of fields) {
+ let container = document.getElementById(`${field}-container`);
+ container.style.display = "none";
+ }
}
localizeDocument() {
if (this._record) {
this._elements.title.dataset.localization = "editAddressTitle";
}
let fragment = document.createDocumentFragment();
for (let country of FormAutofillUtils.supportedCountries) {
@@ -215,16 +259,30 @@ class EditAddress extends EditDialog {
this._elements.country.appendChild(fragment);
FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
}
async handleSubmit() {
await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
window.close();
}
+
+ handleChange(event) {
+ this.formatForm(event.target.value);
+ }
+
+ attachEventListeners() {
+ this._elements.country.addEventListener("change", this);
+ super.attachEventListeners();
+ }
+
+ detachEventListeners() {
+ this._elements.country.removeEventListener("change", this);
+ super.detachEventListeners();
+ }
}
class EditCreditCard extends EditDialog {
constructor(elements, record) {
super("creditCards", elements, record);
this.generateYears();
}
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -21,23 +21,21 @@ select {
#country-warning-message,
#family-name-container,
#organization-container,
#address-level2-container,
#tel-container {
flex: 0 1 50%;
}
-#family-name-container,
-#organization-container,
-#address-level2-container,
#tel-container {
padding-inline-end: 50%;
}
+#name-container,
#street-address-container,
#email-container {
flex: 0 1 100%;
}
#street-address,
#email {
flex: 1 0 auto;
--- a/browser/extensions/formautofill/skin/shared/editDialog.css
+++ b/browser/extensions/formautofill/skin/shared/editDialog.css
@@ -4,23 +4,27 @@
form,
label,
div,
p {
display: flex;
}
+form,
+div {
+ flex-wrap: wrap;
+}
+
form {
- flex-wrap: wrap;
/* Add extra space to ensure invalid input box is displayed properly */
padding: 2px;
}
-form > label,
+form label,
form > p {
margin: 0 0 0.5em !important;
}
label > span,
div > span {
box-sizing: border-box;
padding-inline-end: 0.7em;
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -29,42 +29,51 @@ add_task(async function test_cancelEditA
add_task(async function test_saveAddress() {
await new Promise(resolve => {
let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL);
win.addEventListener("load", () => {
win.addEventListener("unload", () => {
ok(true, "Edit address dialog is closed");
resolve();
}, {once: true});
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["given-name"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["additional-name"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["family-name"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1.organization, {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["street-address"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level2"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["address-level1"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1["postal-code"], {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1.country, {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1.email, {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey(TEST_ADDRESS_1.tel, {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- EventUtils.synthesizeKey("VK_TAB", {}, win);
- info("saving address");
- EventUtils.synthesizeKey("VK_RETURN", {}, win);
+ let doc = win.document;
+ // Verify labels
+ is(doc.querySelector("#address-level1-container > span").textContent, "State",
+ "US address-level1 label should be 'State'");
+ is(doc.querySelector("#postal-code-container > span").textContent, "Zip Code",
+ "US postal-code label should be 'Zip Code'");
+ // Input address info and verify move through form with tab keys
+ const keyInputs = [
+ "VK_TAB",
+ TEST_ADDRESS_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_1["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS_1["postal-code"],
+ "VK_TAB",
+ TEST_ADDRESS_1.country,
+ "VK_TAB",
+ TEST_ADDRESS_1.email,
+ "VK_TAB",
+ TEST_ADDRESS_1.tel,
+ "VK_TAB",
+ "VK_TAB",
+ "VK_RETURN",
+ ];
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
}, {once: true});
});
let addresses = await getAddresses();
is(addresses.length, 1, "only one address is in storage");
is(Object.keys(TEST_ADDRESS_1).length, 11, "Sanity check number of properties");
for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_1)) {
is(addresses[0][fieldName], fieldValue, "check " + fieldName);
@@ -90,8 +99,114 @@ add_task(async function test_editAddress
is(addresses.length, 1, "only one address is in storage");
is(addresses[0]["given-name"], TEST_ADDRESS_1["given-name"] + "test", "given-name changed");
await removeAddresses([addresses[0].guid]);
addresses = await getAddresses();
is(addresses.length, 0, "Address storage is empty");
});
+
+add_task(async function test_saveAddressCA() {
+ await new Promise(resolve => {
+ let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL);
+ win.addEventListener("load", () => {
+ win.addEventListener("unload", () => {
+ ok(true, "Edit address dialog is closed");
+ resolve();
+ }, {once: true});
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Canada", {}, win);
+ is(doc.querySelector("#address-level1-container > span").textContent, "Province",
+ "CA address-level1 label should be 'Province'");
+ is(doc.querySelector("#postal-code-container > span").textContent, "Postal Code",
+ "CA postal-code label should be 'Postal Code'");
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ const keyInputs = [
+ TEST_ADDRESS_CA_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["address-level1"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1["postal-code"],
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.country,
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.email,
+ "VK_TAB",
+ TEST_ADDRESS_CA_1.tel,
+ "VK_TAB",
+ "VK_TAB",
+ "VK_RETURN",
+ ];
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ }, {once: true});
+ });
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_CA_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
+
+add_task(async function test_saveAddressDE() {
+ await new Promise(resolve => {
+ let win = window.openDialog(EDIT_ADDRESS_DIALOG_URL);
+ win.addEventListener("load", () => {
+ win.addEventListener("unload", () => {
+ ok(true, "Edit address dialog is closed");
+ resolve();
+ }, {once: true});
+ let doc = win.document;
+ // Change country to verify labels
+ doc.querySelector("#country").focus();
+ EventUtils.synthesizeKey("Germany", {}, win);
+ is(doc.querySelector("#postal-code-container > span").textContent, "Postal Code",
+ "DE postal-code label should be 'Postal Code'");
+ is(doc.querySelector("#address-level1-container").style.display, "none",
+ "DE address-level1 should be hidden");
+ // Input address info and verify move through form with tab keys
+ doc.querySelector("#given-name").focus();
+ const keyInputs = [
+ TEST_ADDRESS_DE_1["given-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["additional-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["family-name"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.organization,
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["street-address"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["postal-code"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1["address-level2"],
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.country,
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.email,
+ "VK_TAB",
+ TEST_ADDRESS_DE_1.tel,
+ "VK_TAB",
+ "VK_TAB",
+ "VK_RETURN",
+ ];
+ keyInputs.forEach(input => EventUtils.synthesizeKey(input, {}, win));
+ }, {once: true});
+ });
+ let addresses = await getAddresses();
+ for (let [fieldName, fieldValue] of Object.entries(TEST_ADDRESS_DE_1)) {
+ is(addresses[0][fieldName], fieldValue, "check " + fieldName);
+ }
+ await removeAllRecords();
+});
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,10 +1,10 @@
/* exported MANAGE_ADDRESSES_DIALOG_URL, MANAGE_CREDIT_CARDS_DIALOG_URL, EDIT_ADDRESS_DIALOG_URL, EDIT_CREDIT_CARD_DIALOG_URL,
- BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5,
+ BASE_URL, TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3, TEST_ADDRESS_4, TEST_ADDRESS_5, TEST_ADDRESS_CA_1, TEST_ADDRESS_DE_1,
TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF, CREDITCARDS_USED_STATUS_PREF,
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
getAddresses, saveAddress, removeAddresses, saveCreditCard,
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
getNotification, getDoorhangerButton, removeAllRecords */
@@ -62,16 +62,43 @@ const TEST_ADDRESS_4 = {
country: "US",
email: "timbl@w3.org",
};
const TEST_ADDRESS_5 = {
tel: "+16172535702",
};
+const TEST_ADDRESS_CA_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "Mozilla",
+ "street-address": "163 W Hastings\nSuite 209",
+ "address-level2": "Vancouver",
+ "address-level1": "BC",
+ "postal-code": "V6B 1H5",
+ country: "CA",
+ tel: "+17787851540",
+ email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_DE_1 = {
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ organization: "Mozilla",
+ "street-address": "Geb\u00E4ude 3, 4. Obergeschoss\nSchlesische Stra\u00DFe 27",
+ "address-level2": "Berlin",
+ "postal-code": "10997",
+ country: "DE",
+ tel: "+4930983333000",
+ email: "timbl@w3.org",
+};
+
const TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "1234567812345678",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
const TEST_CREDIT_CARD_2 = {