Bug 1427954 - Move autofill edit dialog l10n to mutation observers. r=sfoster
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Wed, 14 Mar 2018 18:12:00 -0700
changeset 408638 4e5ce4f1f57901ebaf9d932b41db13b4fe574274
parent 408637 ac23b9ba0603839616105f4fbe7d015ddff28254
child 408639 6ab0e835a7f4e2823d5ae5a80ffbdeb775ab5591
push id100996
push userbtara@mozilla.com
push dateSat, 17 Mar 2018 10:37:43 +0000
treeherdermozilla-inbound@97160a734959 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1427954
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1427954 - Move autofill edit dialog l10n to mutation observers. r=sfoster MozReview-Commit-ID: 5jSj0xPBTQj
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/autofillEditForms.js
browser/extensions/formautofill/content/editAddress.xhtml
browser/extensions/formautofill/content/editCreditCard.xhtml
browser/extensions/formautofill/content/editDialog.js
browser/extensions/formautofill/content/l10n.js
browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
browser/extensions/formautofill/test/browser/head.js
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -823,34 +823,52 @@ this.FormAutofillUtils = {
     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 "data-localization" or "data-localization-region" attributes.
+   * @param {Element} element
+   * @param {string} attributeName
+   */
+  localizeAttributeForElement(element, attributeName) {
+    let bundle = null;
+    switch (attributeName) {
+      case "data-localization": {
+        bundle = this.stringBundle;
+        break;
+      }
+      case "data-localization-region": {
+        bundle = this.regionsBundle;
+        break;
+      }
+      default:
+        throw new Error("Unexpected attributeName");
+    }
+
+    element.textContent = bundle.GetStringFromName(element.getAttribute(attributeName));
+    element.removeAttribute(attributeName);
+  },
+
+  /**
    * Localize elements with "data-localization" or "data-localization-region" attributes.
-   * @param   {DOMElement} root
+   * @param {Element} root
    */
   localizeMarkup(root) {
     let elements = root.querySelectorAll("[data-localization]");
     for (let element of elements) {
-      element.textContent = this.stringBundle.GetStringFromName(
-        element.getAttribute("data-localization")
-      );
-      element.removeAttribute("data-localization");
+      this.localizeAttributeForElement(element, "data-localization");
     }
 
-    let regionElements = root.querySelectorAll("[data-localization-region]");
-    for (let element of regionElements) {
-      element.textContent = this.regionsBundle.GetStringFromName(
-        element.getAttribute("data-localization-region")
-      );
-      element.removeAttribute("data-localization-region");
+    elements = root.querySelectorAll("[data-localization-region]");
+    for (let element of elements) {
+      this.localizeAttributeForElement(element, "data-localization-region");
     }
   },
 };
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
 
 XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
--- a/browser/extensions/formautofill/content/autofillEditForms.js
+++ b/browser/extensions/formautofill/content/autofillEditForms.js
@@ -98,17 +98,16 @@ class EditAddress extends EditAutofillFo
    * 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) {
     const {addressLevel1Label, postalCodeLabel, fieldsOrder} = FormAutofillUtils.getFormFormat(country);
     this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
     this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
-    FormAutofillUtils.localizeMarkup(document);
     this.arrangeFields(fieldsOrder);
   }
 
   arrangeFields(fieldsOrder) {
     let fields = [
       "name",
       "organization",
       "street-address",
@@ -142,17 +141,16 @@ class EditAddress extends EditAutofillFo
     let fragment = document.createDocumentFragment();
     for (let country of FormAutofillUtils.supportedCountries) {
       let option = new Option();
       option.value = country;
       option.dataset.localizationRegion = country.toLowerCase();
       fragment.appendChild(option);
     }
     this._elements.country.appendChild(fragment);
-    FormAutofillUtils.localizeMarkup(this._elements.country);
   }
 
   handleChange(event) {
     this.formatForm(event.target.value);
   }
 
   attachEventListeners() {
     this._elements.country.addEventListener("change", this);
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -7,16 +7,17 @@
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml" width="620">
 <head>
   <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/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <div>
       <div id="name-container">
         <label id="given-name-container">
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -7,16 +7,17 @@
   %globalDTD;
 ]>
 <html xmlns="http://www.w3.org/1999/xhtml" width="500" style="width: 500px">
 <head>
   <title data-localization="addNewCreditCardTitle"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css"/>
   <link rel="stylesheet" href="chrome://formautofill-shared/skin/editCreditCard.css"/>
   <link rel="stylesheet" href="chrome://formautofill/skin/editDialog.css"/>
+  <script src="chrome://formautofill/content/l10n.js"></script>
   <script src="chrome://formautofill/content/editDialog.js"></script>
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" autocomplete="off">
     <label>
       <span data-localization="cardNumber"/>
       <input id="cc-number" type="text"/>
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -163,17 +163,16 @@ class EditAddressDialog extends Autofill
                   FormAutofillUtils.supportedCountries.find(supported => supported == FormAutofillUtils.DEFAULT_REGION);
     super("addresses", elements, record || {country});
   }
 
   localizeDocument() {
     if (this._record) {
       this._elements.title.dataset.localization = "editAddressTitle";
     }
-    FormAutofillUtils.localizeMarkup(document);
   }
 
   async handleSubmit() {
     await this.saveRecord(this._elements.fieldContainer.buildFormObject(), this._record ? this._record.guid : null);
     window.close();
   }
 }
 
@@ -181,17 +180,16 @@ class EditCreditCardDialog extends Autof
   constructor(elements, record) {
     super("creditCards", elements, record);
   }
 
   localizeDocument() {
     if (this._record) {
       this._elements.title.dataset.localization = "editCreditCardTitle";
     }
-    FormAutofillUtils.localizeMarkup(document);
   }
 
   /**
    * Decrypt cc-number first and fill the form.
    * @param  {object} creditCard
    */
   async loadInitialValues(creditCard) {
     let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/l10n.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file will be replaced by Fluent but it's a middle ground so we can share
+ * the edit dialog code with the unprivileged PaymentRequest dialog before the
+ * Fluent conversion
+ */
+
+ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
+
+const L10N_ATTRIBUTES = ["data-localization", "data-localization-region"];
+
+let mutationObserver = new MutationObserver(function onMutation(mutations) {
+  for (let mutation of mutations) {
+    if (!mutation.target.hasAttribute(mutation.attributeName)) {
+      // The attribute was removed in the meantime.
+      continue;
+    }
+    FormAutofillUtils.localizeAttributeForElement(mutation.target, mutation.attributeName);
+  }
+});
+
+document.addEventListener("DOMContentLoaded", function onDCL() {
+  FormAutofillUtils.localizeMarkup(document);
+  mutationObserver.observe(document, {
+    attributes: true,
+    attributeFilter: L10N_ATTRIBUTES,
+    subtree: true,
+  });
+}, {
+  once: true,
+});
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -106,21 +106,25 @@ add_task(async function test_editAddress
   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 testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
     let doc = win.document;
     // Change country to verify labels
     doc.querySelector("#country").focus();
     EventUtils.synthesizeKey("Canada", {}, win);
+
+    await TestUtils.waitForCondition(() => {
+      return doc.querySelector("#address-level1-container > span").textContent == "Province";
+    }, "Wait for the mutation observer to change the labels");
     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"],
@@ -153,21 +157,24 @@ add_task(async function test_saveAddress
   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 testDialog(EDIT_ADDRESS_DIALOG_URL, win => {
+  await testDialog(EDIT_ADDRESS_DIALOG_URL, async win => {
     let doc = win.document;
     // Change country to verify labels
     doc.querySelector("#country").focus();
     EventUtils.synthesizeKey("Germany", {}, win);
+    await TestUtils.waitForCondition(() => {
+      return doc.querySelector("#postal-code-container > span").textContent == "Postal Code";
+    }, "Wait for the mutation observer to change the labels");
     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"],
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -334,16 +334,16 @@ async function removeAllRecords() {
 async function waitForFocusAndFormReady(win) {
   return Promise.all([
     new Promise(resolve => waitForFocus(resolve, win)),
     BrowserTestUtils.waitForEvent(win, "FormReady"),
   ]);
 }
 
 async function testDialog(url, testFn, arg) {
-  let win = window.openDialog(url, null, null, arg);
+  let win = window.openDialog(url, null, "width=600,height=600", arg);
   await waitForFocusAndFormReady(win);
   let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
-  testFn(win);
+  await testFn(win);
   return unloadPromise;
 }
 
 registerCleanupFunction(removeAllRecords);