Bug 1370764 - (Part 2) Create a dialog to add/edit/view a credit card entry. r=lchang draft
authorScott Wu <scottcwwu@gmail.com>
Fri, 11 Aug 2017 15:31:57 +0800
changeset 651966 fd30f4cdb328212271185caabc6a636f973b865f
parent 651965 f17523fccb615fbf27273b00883471bfbbd0148a
child 651967 7b4ca3a749e3a0d543e1e843ec343a1fd0123dea
push id75903
push userbmo:scwwu@mozilla.com
push dateThu, 24 Aug 2017 09:16:15 +0000
reviewerslchang
bugs1370764
milestone57.0a1
Bug 1370764 - (Part 2) Create a dialog to add/edit/view a credit card entry. r=lchang MozReview-Commit-ID: 7d6cfcShrwc
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/content/editAddress.xhtml
browser/extensions/formautofill/content/editCreditCard.xhtml
browser/extensions/formautofill/content/editDialog.js
browser/extensions/formautofill/locales/en-US/formautofill.properties
browser/extensions/formautofill/skin/linux/editAddress.css
browser/extensions/formautofill/skin/linux/editDialog.css
browser/extensions/formautofill/skin/osx/editAddress.css
browser/extensions/formautofill/skin/osx/editDialog.css
browser/extensions/formautofill/skin/shared/editAddress.css
browser/extensions/formautofill/skin/shared/editCreditCard.css
browser/extensions/formautofill/skin/shared/editDialog.css
browser/extensions/formautofill/skin/windows/editAddress.css
browser/extensions/formautofill/skin/windows/editDialog.css
browser/extensions/formautofill/test/unit/test_creditCardRecords.js
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -59,16 +59,23 @@ this.FormAutofillUtils = {
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
 
+  isCCNumber(ccNumber) {
+    // Based on the information on wiki[1], the shortest valid length should be
+    // 12 digits(Maestro).
+    // [1] https://en.wikipedia.org/wiki/Payment_card_number
+    return ccNumber ? ccNumber.replace(/\s/g, "").match(/^\d{12,}$/) : false;
+  },
+
   getCategoryFromFieldName(fieldName) {
     return this._fieldNameInfo[fieldName];
   },
 
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
     for (let fieldName of fieldNames) {
       let info = this.getCategoryFromFieldName(fieldName);
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1508,25 +1508,18 @@ class CreditCards extends AutofillRecord
     // Fields that should not be set by content.
     delete creditCard["cc-number-encrypted"];
 
     // Validate and encrypt credit card numbers, and calculate the masked numbers
     if (creditCard["cc-number"]) {
       let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
       delete creditCard["cc-number"];
 
-      if (!/^\d+$/.test(ccNumber)) {
-        throw new Error("Credit card number contains invalid characters.");
-      }
-
-      // Based on the information on wiki[1], the shortest valid length should be
-      // 12 digits(Maestro).
-      // [1] https://en.wikipedia.org/wiki/Payment_card_number
-      if (ccNumber.length < 12) {
-        throw new Error("Invalid credit card number because length is under 12 digits.");
+      if (!FormAutofillUtils.isCCNumber(ccNumber)) {
+        throw new Error("Credit card number contains invalid characters or is under 12 digits.");
       }
 
       creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
       creditCard["cc-number"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
     }
   }
 }
 
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -1,22 +1,23 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 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/. -->
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml" width="620">
 <head>
-  <title data-localization="addNewDialogTitle"/>
-  <link rel="stylesheet" href="chrome://formautofill-shared/skin/editDialog.css" />
-  <link rel="stylesheet" href="chrome://formautofill/skin/editAddress.css" />
+  <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 autocomplete="off">
+  <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>
@@ -62,13 +63,22 @@
     </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancel"/>
     <button id="save" disabled="disabled" data-localization="save"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
-    // Localize strings before DOMContentLoaded to prevent flash
-    window.dialog.localizeDocument();
+    /* global EditAddress */
+    new EditAddress({
+      title: document.querySelector("title"),
+      form: document.getElementById("form"),
+      addressLevel1Label: document.querySelector("#address-level1-container > span"),
+      postalCodeLabel: document.querySelector("#postal-code-container > span"),
+      country: document.getElementById("country"),
+      controlsContainer: document.getElementById("controls-container"),
+      cancel: document.getElementById("cancel"),
+      save: document.getElementById("save"),
+    }, window.arguments && window.arguments[0]);
   ]]></script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!DOCTYPE html>
+<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/editDialog.js"></script>
+</head>
+<body>
+  <form id="form" autocomplete="off">
+    <label>
+      <span data-localization="cardNumber"/>
+      <input id="cc-number" type="text"/>
+    </label>
+    <label>
+      <span data-localization="nameOnCard"/>
+      <input id="cc-name" type="text"/>
+    </label>
+    <div>
+      <span data-localization="cardExpires"/>
+      <select id="cc-exp-month">
+        <option/>
+        <option value="1">01</option>
+        <option value="2">02</option>
+        <option value="3">03</option>
+        <option value="4">04</option>
+        <option value="5">05</option>
+        <option value="6">06</option>
+        <option value="7">07</option>
+        <option value="8">08</option>
+        <option value="9">09</option>
+        <option value="10">10</option>
+        <option value="11">11</option>
+        <option value="12">12</option>
+      </select>
+      <select id="cc-exp-year">
+        <option/>
+      </select>
+    </div>
+  </form>
+  <div id="controls-container">
+    <button id="cancel" data-localization="cancel"/>
+    <button id="save" disabled="disabled" data-localization="save"/>
+  </div>
+  <script type="application/javascript"><![CDATA[
+    "use strict";
+    /* global EditCreditCard */
+    new EditCreditCard({
+      title: document.querySelector("title"),
+      form: document.getElementById("form"),
+      ccNumber: document.getElementById("cc-number"),
+      year: document.getElementById("cc-exp-year"),
+      controlsContainer: document.getElementById("controls-container"),
+      cancel: document.getElementById("cancel"),
+      save: document.getElementById("save"),
+    }, window.arguments && window.arguments[0]);
+  ]]></script>
+</body>
+</html>
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -1,201 +1,277 @@
 /* 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/. */
 
+/* exported EditAddress, EditCreditCard */
+
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
 const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
-function EditDialog() {
-  this._address = window.arguments && window.arguments[0];
-  window.addEventListener("DOMContentLoaded", this, {once: true});
-}
+XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
+                                  "resource://formautofill/ProfileStorage.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
+                                  "resource://formautofill/MasterPassword.jsm");
 
-EditDialog.prototype = {
-  get _elements() {
-    if (this._elementRefs) {
-      return this._elementRefs;
+class EditDialog {
+  constructor(subStorageName, elements, record) {
+    this._storageInitPromise = profileStorage.initialize();
+    this._subStorageName = subStorageName;
+    this._elements = elements;
+    this._record = record;
+    this.localizeDocument();
+    window.addEventListener("DOMContentLoaded", this, {once: true});
+  }
+
+  async init() {
+    if (this._record) {
+      await this.loadInitialValues(this._record);
     }
-    this._elementRefs = {
-      title: document.querySelector("title"),
-      addressLevel1Label: document.querySelector("#address-level1-container > span"),
-      postalCodeLabel: document.querySelector("#postal-code-container > span"),
-      country: document.getElementById("country"),
-      controlsContainer: document.getElementById("controls-container"),
-      cancel: document.getElementById("cancel"),
-      save: document.getElementById("save"),
-    };
-    return this._elementRefs;
-  },
-
-  set _elements(refs) {
-    this._elementRefs = refs;
-  },
-
-  init() {
     this.attachEventListeners();
-  },
+  }
 
   uninit() {
     this.detachEventListeners();
     this._elements = null;
-  },
+  }
+
+  /**
+   * Fill the form with a record object.
+   * @param  {object} record
+   */
+  loadInitialValues(record) {
+    for (let field in record) {
+      let input = document.getElementById(field);
+      if (input) {
+        input.value = record[field];
+      }
+    }
+  }
+
+  /**
+   * Get inputs from the form.
+   * @returns {object}
+   */
+  buildFormObject() {
+    return Array.from(document.forms[0].elements).reduce((obj, input) => {
+      if (input.value) {
+        obj[input.id] = input.value;
+      }
+      return obj;
+    }, {});
+  }
+
+  /**
+   * Get storage and ensure it has been initialized.
+   * @returns {object}
+   */
+  async getStorage() {
+    await this._storageInitPromise;
+    return profileStorage[this._subStorageName];
+  }
+
+  /**
+   * Asks FormAutofillParent to save or update an record.
+   * @param  {object} record
+   * @param  {string} guid [optional]
+   */
+  async saveRecord(record, guid) {
+    let storage = await this.getStorage();
+    if (guid) {
+      storage.update(guid, record);
+    } else {
+      storage.add(record);
+    }
+  }
+
+  /**
+   * Handle events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleEvent(event) {
+    switch (event.type) {
+      case "DOMContentLoaded": {
+        this.init();
+        break;
+      }
+      case "unload": {
+        this.uninit();
+        break;
+      }
+      case "click": {
+        this.handleClick(event);
+        break;
+      }
+      case "input": {
+        this.handleInput(event);
+        break;
+      }
+      case "keypress": {
+        this.handleKeyPress(event);
+        break;
+      }
+    }
+  }
+
+  /**
+   * Handle click events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleClick(event) {
+    if (event.target == this._elements.cancel) {
+      window.close();
+    }
+    if (event.target == this._elements.save) {
+      this.handleSubmit();
+    }
+  }
+
+  /**
+   * Handle input events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleInput(event) {
+    // Toggle disabled attribute on the save button based on
+    // whether the form is filled or empty.
+    if (Object.keys(this.buildFormObject()).length == 0) {
+      this._elements.save.setAttribute("disabled", true);
+    } else {
+      this._elements.save.removeAttribute("disabled");
+    }
+  }
+
+  /**
+   * Handle key press events
+   *
+   * @param  {DOMEvent} event
+   */
+  handleKeyPress(event) {
+    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+      window.close();
+    }
+  }
+
+  /**
+   * Attach event listener
+   */
+  attachEventListeners() {
+    window.addEventListener("keypress", this);
+    this._elements.controlsContainer.addEventListener("click", this);
+    document.addEventListener("input", this);
+  }
+
+  /**
+   * Remove event listener
+   */
+  detachEventListeners() {
+    window.removeEventListener("keypress", this);
+    this._elements.controlsContainer.removeEventListener("click", this);
+    document.removeEventListener("input", this);
+  }
+}
+
+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);
     this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
     this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
     FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
-  },
+  }
 
   localizeDocument() {
-    if (this._address) {
-      this._elements.title.dataset.localization = "editDialogTitle";
+    if (this._record) {
+      this._elements.title.dataset.localization = "editAddressTitle";
     }
     FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
-    this.formatForm(this._address && this._address.country);
-  },
+  }
+
+  async handleSubmit() {
+    await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
+    window.close();
+  }
+}
 
-  /**
-   * Asks FormAutofillParent to save or update an address.
-   * @param  {object} data
-   *         {
-   *           {string} guid [optional]
-   *           {object} address
-   *         }
-   */
-  saveAddress(data) {
-    Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", data);
-  },
+class EditCreditCard extends EditDialog {
+  constructor(elements, record) {
+    super("creditCards", elements, record);
+    this.generateYears();
+  }
+
+  generateYears() {
+    const count = 11;
+    const currentYear = new Date().getFullYear();
+    const ccExpYear = this._record && this._record["cc-exp-year"];
 
-  /**
-   * Fill the form with an address object.
-   * @param  {object} address
-   */
-  loadInitialValues(address) {
-    for (let field in address) {
-      let input = document.getElementById(field);
-      if (input) {
-        input.value = address[field];
-      }
+    if (ccExpYear && ccExpYear < currentYear) {
+      this._elements.year.appendChild(new Option(ccExpYear));
     }
-  },
+
+    for (let i = 0; i < count; i++) {
+      let year = currentYear + i;
+      let option = new Option(year);
+      this._elements.year.appendChild(option);
+    }
 
-  /**
-   * Get inputs from the form.
-   * @returns {object}
-   */
-  buildAddressObject() {
-    return Array.from(document.forms[0].elements).reduce((obj, input) => {
-      if (input.value) {
-        obj[input.id] = input.value;
-      }
-      return obj;
-    }, {});
-  },
+    if (ccExpYear && ccExpYear > currentYear + count) {
+      this._elements.year.appendChild(new Option(ccExpYear));
+    }
+  }
+
+  localizeDocument() {
+    if (this._record) {
+      this._elements.title.dataset.localization = "editCreditCardTitle";
+    }
+    FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+  }
 
   /**
-   * Handle events
-   *
-   * @param  {DOMEvent} event
+   * Decrypt cc-number first and fill the form.
+   * @param  {object} creditCard
    */
-  handleEvent(event) {
-    switch (event.type) {
-      case "DOMContentLoaded": {
-        this.init();
-        if (this._address) {
-          this.loadInitialValues(this._address);
-        }
-        break;
-      }
-      case "click": {
-        this.handleClick(event);
-        break;
-      }
-      case "input": {
-        // Toggle disabled attribute on the save button based on
-        // whether the form is filled or empty.
-        if (Object.keys(this.buildAddressObject()).length == 0) {
-          this._elements.save.setAttribute("disabled", true);
-        } else {
-          this._elements.save.removeAttribute("disabled");
-        }
-        break;
-      }
-      case "unload": {
-        this.uninit();
-        break;
-      }
-      case "keypress": {
-        this.handleKeyPress(event);
-        break;
-      }
-    }
-  },
+  async loadInitialValues(creditCard) {
+    let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
+    super.loadInitialValues(Object.assign({}, creditCard, {"cc-number": decryptedCC}));
+  }
 
-  /**
-   * Handle click events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleClick(event) {
-    if (event.target == this._elements.cancel) {
-      window.close();
+  async handleSubmit() {
+    let creditCard = this.buildFormObject();
+    // Show error on the cc-number field if it's empty or invalid
+    if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
+      this._elements.ccNumber.setCustomValidity(true);
+      return;
     }
-    if (event.target == this._elements.save) {
-      if (this._address) {
-        this.saveAddress({
-          guid: this._address.guid,
-          address: this.buildAddressObject(),
-        });
-      } else {
-        this.saveAddress({
-          address: this.buildAddressObject(),
-        });
-      }
-      window.close();
-    }
-  },
+    let storage = await this.getStorage();
+    await storage.normalizeCCNumberFields(creditCard);
+    await this.saveRecord(creditCard, this._record ? this._record.guid : null);
+    window.close();
+  }
 
-  /**
-   * Handle key press events
-   *
-   * @param  {DOMEvent} event
-   */
-  handleKeyPress(event) {
-    if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
-      window.close();
+  handleInput(event) {
+    // Clear the error message if cc-number is valid
+    if (event.target == this._elements.ccNumber &&
+        FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
+      this._elements.ccNumber.setCustomValidity("");
     }
-  },
-
-  /**
-   * Attach event listener
-   */
-  attachEventListeners() {
-    window.addEventListener("keypress", this);
-    this._elements.controlsContainer.addEventListener("click", this);
-    document.addEventListener("input", this);
-  },
-
-  /**
-   * Remove event listener
-   */
-  detachEventListeners() {
-    window.removeEventListener("keypress", this);
-    this._elements.controlsContainer.removeEventListener("click", this);
-    document.removeEventListener("input", this);
-  },
-};
-
-window.dialog = new EditDialog();
+    super.handleInput(event);
+  }
+}
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -34,18 +34,18 @@ phishingWarningMessage = Also autofills 
 phishingWarningMessage2 = Autofills %S
 
 manageDialogTitle = Saved Addresses
 addressListHeader = Addresses
 remove = Remove
 add = Add…
 edit = Edit…
 
-addNewDialogTitle = Add New Address
-editDialogTitle = Edit Address
+addNewAddressTitle = Add New Address
+editAddressTitle = Edit Address
 givenName = First Name
 additionalName = Middle Name
 familyName = Last Name
 organization = Company
 streetAddress = Street Address
 city = City
 province = Province
 state = State
@@ -53,9 +53,15 @@ postalCode = Postal Code
 zip = Zip Code
 country = Country or Region
 tel = Phone
 email = Email
 cancel = Cancel
 save = Save
 countryWarningMessage = Autofill is currently available only for US addresses
 
+addNewCreditCardTitle = Add New Credit Card
+editCreditCardTitle = Edit Credit Card
+cardNumber = Card Number
+nameOnCard = Name on Card
+cardExpires = Expires
+
 insecureFieldWarningDescription = %S has detected an insecure site. Credit card autofill is temporarily disabled
rename from browser/extensions/formautofill/skin/linux/editAddress.css
rename to browser/extensions/formautofill/skin/linux/editDialog.css
rename from browser/extensions/formautofill/skin/osx/editAddress.css
rename to browser/extensions/formautofill/skin/osx/editDialog.css
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -0,0 +1,50 @@
+/* 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/. */
+
+
+label > span {
+  flex: 0 0 9.5em;
+}
+
+input,
+select {
+  width: calc(50% - 9.5em);
+}
+
+#given-name-container,
+#additional-name-container,
+#address-level1-container,
+#postal-code-container,
+#country-container {
+  flex: 0 1 50%;
+}
+
+#family-name-container,
+#organization-container,
+#street-address-container,
+#address-level2-container,
+#email-container,
+#tel-container {
+  flex: 0 1 100%;
+}
+
+#family-name,
+#organization,
+#address-level2,
+#tel {
+  flex: 0 0 auto;
+}
+
+#street-address,
+#email {
+  flex: 1 0 auto;
+}
+
+#country-warning-message {
+  flex: 1;
+  align-items: center;
+  text-align: start;
+  color: #737373;
+  padding-inline-start: 1em;
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/skin/shared/editCreditCard.css
@@ -0,0 +1,28 @@
+/* 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/. */
+
+form {
+  justify-content: center;
+  /* Add extra space to ensure invalid input box is displayed properly */
+  padding: 2px;
+}
+
+form > label,
+form > div {
+  flex: 1 0 100%;
+  align-self: center;
+  font-size: 1.3em;
+  margin: 0 0 0.5em !important;
+}
+
+select {
+  flex: 0 0 5em;
+  margin: 0;
+  margin-inline-end: 0.7em;
+}
+
+label > span,
+div > span {
+  flex: 0 0 9.5em;
+}
--- a/browser/extensions/formautofill/skin/shared/editDialog.css
+++ b/browser/extensions/formautofill/skin/shared/editDialog.css
@@ -17,80 +17,43 @@ form {
   flex-wrap: wrap;
 }
 
 label,
 p {
   margin: 0 0 0.5em;
 }
 
-label > span {
+label > span,
+div > span {
   box-sizing: border-box;
-  flex: 0 0 9.5em;
-  padding-inline-end: 0.5em;
+  padding-inline-end: 0.7em;
   align-self: center;
   text-align: end;
   -moz-user-select: none;
 }
 
-input,
-select {
-  box-sizing: border-box;
-  flex: 1 0 auto;
-  width: calc(50% - 9.5em);
-}
-
 option {
-  padding: 5px 10px;
+  padding: 0.3em 0.5em;
 }
 
 textarea {
   resize: none;
 }
 
 button {
   font-size: 1.2em;
   padding: 3px 2em;
   margin-inline-start: 10px;
   margin-inline-end: 0;
 }
 
-#given-name-container,
-#additional-name-container,
-#address-level1-container,
-#postal-code-container,
-#country-container {
-  flex: 0 1 50%;
-}
-
-#family-name-container,
-#organization-container,
-#street-address-container,
-#address-level2-container,
-#email-container,
-#tel-container,
-#controls-container {
-  flex: 0 1 100%;
+input,
+select {
+  box-sizing: border-box;
+  flex: 1 0 auto;
 }
 
 #controls-container {
+  flex: 0 1 100%;
   justify-content: end;
-}
-
-#family-name,
-#organization,
-#address-level2,
-#tel {
-  flex: 0 0 auto;
+  margin: 1em 0 0;
 }
-
-#street-address,
-#email {
-  flex: 1 0 auto;
-}
-
-#country-warning-message {
-  flex: 1;
-  align-items: center;
-  text-align: start;
-  color: #737373;
-  padding-inline-start: 1em;
-}
rename from browser/extensions/formautofill/skin/windows/editAddress.css
rename to browser/extensions/formautofill/skin/windows/editDialog.css
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -254,24 +254,24 @@ add_task(async function test_validate() 
   do_check_eq(creditCards[1]["cc-exp"], year + "-" + month.toString().padStart(2, "0"));
 
   do_check_eq(creditCards[2]["cc-number"].length, 16);
 
   try {
     await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS);
     throw new Error("Not receiving invalid characters error");
   } catch (e) {
-    Assert.equal(e.message, "Credit card number contains invalid characters.");
+    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
   }
 
   try {
     await profileStorage.creditCards.normalizeCCNumberFields(TEST_CREDIT_CARD_WITH_SHORT_NUMBERS);
     throw new Error("Not receiving invalid characters error");
   } catch (e) {
-    Assert.equal(e.message, "Invalid credit card number because length is under 12 digits.");
+    Assert.equal(e.message, "Credit card number contains invalid characters or is under 12 digits.");
   }
 });
 
 add_task(async function test_notifyUsed() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);