Bug 1480886 - Position the form fields for the address-form and basic-card-form according to the spec. r=sfoster
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 28 Aug 2018 15:34:50 -0700
changeset 491461 ba1272b8b6390e8413e5d54f500914b1a95d72ba
parent 491460 690f56519829649c9df2519feb614f6afbca2664
child 491462 1f763bb7c0efd5b8e85d6c19ad8d7c267011239e
push id1815
push userffxbld-merge
push dateMon, 15 Oct 2018 10:40:45 +0000
treeherdermozilla-release@18d4c09e9378 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfoster
bugs1480886
milestone63.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 1480886 - Position the form fields for the address-form and basic-card-form according to the spec. r=sfoster Based on work by Jared Wein. A follow-up will handle the persist checkbox and billing address. Differential Revision: https://phabricator.services.mozilla.com/D4174
browser/components/payments/res/containers/address-form.js
browser/components/payments/res/containers/basic-card-form.js
browser/components/payments/res/unprivileged-fallbacks.js
browser/extensions/formautofill/addressmetadata/addressReferencesExt.js
browser/extensions/formautofill/content/editAddress.xhtml
browser/extensions/formautofill/content/editCreditCard.xhtml
browser/extensions/formautofill/skin/shared/editAddress.css
browser/extensions/formautofill/skin/shared/editCreditCard.css
browser/extensions/formautofill/skin/shared/editDialog-shared.css
browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -268,17 +268,17 @@ export default class AddressForm extends
   }
 
   updateRequiredState() {
     for (let field of this.form.elements) {
       let container = field.closest(`#${field.id}-container`);
       if (field.localName == "button" || !container) {
         continue;
       }
-      let span = container.querySelector("span");
+      let span = container.querySelector(".label-text");
       span.setAttribute("fieldRequiredSymbol", this.dataset.fieldRequiredSymbol);
       let required = field.required && !field.disabled;
       if (required) {
         container.setAttribute("required", "true");
       } else {
         container.removeAttribute("required");
       }
     }
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -307,21 +307,21 @@ export default class BasicCardForm exten
     this.saveButton.disabled = true;
   }
 
   updateSaveButtonState() {
     this.saveButton.disabled = !this.form.checkValidity();
   }
 
   updateRequiredState() {
-    for (let formElement of this.form.elements) {
-      let container = formElement.closest("label") || formElement.closest("div");
-      let span = container.querySelector("span");
+    for (let field of this.form.elements) {
+      let container = field.closest(".container");
+      let span = container.querySelector(".label-text");
       span.setAttribute("fieldRequiredSymbol", this.dataset.fieldRequiredSymbol);
-      let required = formElement.required && !formElement.disabled;
+      let required = field.required && !field.disabled;
       if (required) {
         container.setAttribute("required", "true");
       } else {
         container.removeAttribute("required");
       }
     }
   }
 
--- a/browser/components/payments/res/unprivileged-fallbacks.js
+++ b/browser/components/payments/res/unprivileged-fallbacks.js
@@ -29,18 +29,42 @@ var PaymentDialogUtils = {
         ` (${address.guid})`;
     }
     return `${address.name} (${address.guid})`;
   },
   isCCNumber(str) {
     return !!str.replace(/[-\s]/g, "").match(/^\d{9,}$/);
   },
   DEFAULT_REGION: "US",
-  supportedCountries: ["US", "CA"],
+  supportedCountries: ["US", "CA", "DE"],
   getFormFormat(country) {
+    if (country == "DE") {
+      return {
+        addressLevel1Label: "province",
+        postalCodeLabel: "postalCode",
+        fieldsOrder: [
+          {
+            fieldId: "name",
+            newLine: true,
+          },
+          {
+            fieldId: "organization",
+            newLine: true,
+          },
+          {
+            fieldId: "street-address",
+            newLine: true,
+          },
+          {fieldId: "postal-code"},
+          {fieldId: "address-level2"},
+        ],
+        postalCodePattern: "\\d{5}",
+      };
+    }
+
     return {
       "addressLevel1Label": country == "US" ? "state" : "province",
       "postalCodeLabel": country == "US" ? "zip" : "postalCode",
       "fieldsOrder": [
         {fieldId: "name", newLine: true},
         {fieldId: "organization", newLine: true},
         {fieldId: "street-address", newLine: true},
         {fieldId: "address-level2"},
--- a/browser/extensions/formautofill/addressmetadata/addressReferencesExt.js
+++ b/browser/extensions/formautofill/addressmetadata/addressReferencesExt.js
@@ -8,10 +8,13 @@
 "use strict";
 
 // "addressDataExt" uses the same key as "addressData" in "addressReferences.js" and contains
 //  contains the information we need but absent in "libaddressinput" such as alternative names.
 
 // TODO: We only support the alternative name of US in MVP. We are going to support more countries in
 //       bug 1370193.
 var addressDataExt = {
-  "data/US": {"alternative_names": ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"]},
+  "data/US": {
+    alternative_names: ["US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A"],
+    fmt: "%N%n%A%n%C%S%n%Z%O",
+  },
 };
--- a/browser/extensions/formautofill/content/editAddress.xhtml
+++ b/browser/extensions/formautofill/content/editAddress.xhtml
@@ -17,66 +17,64 @@
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" class="editAddressForm" autocomplete="off">
     <!--
         The <span class="label-text" …/> needs to be after the form field in the same element in
         order to get proper label styling with :focus and :moz-ui-invalid.
       -->
-    <div>
-      <div id="name-container">
-        <label id="given-name-container">
-          <input id="given-name" type="text" required="required"/>
-          <span data-localization="givenName" class="label-text"/>
-        </label>
-        <label id="additional-name-container">
-          <input id="additional-name" type="text"/>
-          <span data-localization="additionalName" class="label-text"/>
-        </label>
-        <label id="family-name-container">
-          <input id="family-name" type="text" required="required"/>
-          <span data-localization="familyName" class="label-text"/>
-        </label>
-      </div>
-      <label id="organization-container">
-        <input id="organization" type="text"/>
-        <span data-localization="organization2" class="label-text"/>
-      </label>
-      <label id="street-address-container">
-        <textarea id="street-address" rows="3" required="required"/>
-        <span data-localization="streetAddress" class="label-text"/>
+    <div id="name-container" class="container">
+      <label id="given-name-container">
+        <input id="given-name" type="text" required="required"/>
+        <span data-localization="givenName" class="label-text"/>
       </label>
-      <label id="address-level2-container">
-        <input id="address-level2" type="text" required="required"/>
-        <span data-localization="city" class="label-text"/>
-      </label>
-      <label id="address-level1-container">
-        <input id="address-level1" type="text" required="required"/>
-        <span class="label-text"/>
-      </label>
-      <label id="postal-code-container">
-        <input id="postal-code" type="text" required="required"/>
-        <span class="label-text"/>
+      <label id="additional-name-container">
+        <input id="additional-name" type="text"/>
+        <span data-localization="additionalName" class="label-text"/>
       </label>
-      <label id="country-container">
-        <select id="country" required="required">
-          <option/>
-        </select>
-        <span data-localization="country" class="label-text"/>
-      </label>
-      <label id="tel-container">
-        <input id="tel" type="tel"/>
-        <span data-localization="tel" class="label-text"/>
-      </label>
-      <label id="email-container">
-        <input id="email" type="email" required="required"/>
-        <span data-localization="email" class="label-text"/>
+      <label id="family-name-container">
+        <input id="family-name" type="text" required="required"/>
+        <span data-localization="familyName" class="label-text"/>
       </label>
     </div>
+    <label id="organization-container" class="container">
+      <input id="organization" type="text"/>
+      <span data-localization="organization2" class="label-text"/>
+    </label>
+    <label id="street-address-container" class="container">
+      <textarea id="street-address" rows="3" required="required"/>
+      <span data-localization="streetAddress" class="label-text"/>
+    </label>
+    <label id="address-level2-container" class="container">
+      <input id="address-level2" type="text" required="required"/>
+      <span data-localization="city" class="label-text"/>
+    </label>
+    <label id="address-level1-container" class="container">
+      <input id="address-level1" type="text" required="required"/>
+      <span class="label-text"/>
+    </label>
+    <label id="postal-code-container" class="container">
+      <input id="postal-code" type="text" required="required"/>
+      <span class="label-text"/>
+    </label>
+    <label id="country-container" class="container">
+      <select id="country" required="required">
+        <option/>
+      </select>
+      <span data-localization="country" class="label-text"/>
+    </label>
+    <label id="tel-container" class="container">
+      <input id="tel" type="tel"/>
+      <span data-localization="tel" class="label-text"/>
+    </label>
+    <label id="email-container" class="container">
+      <input id="email" type="email" required="required"/>
+      <span data-localization="email" class="label-text"/>
+    </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
     <span id="country-warning-message" data-localization="countryWarningMessage2"/>
   </div>
   <script type="application/javascript"><![CDATA[
     "use strict";
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -17,26 +17,22 @@
   <script src="chrome://formautofill/content/autofillEditForms.js"></script>
 </head>
 <body dir="&locale.dir;">
   <form id="form" class="editCreditCardForm" autocomplete="off">
     <!--
         The <span class="label-text" …/> needs to be after the form field in the same element in
         order to get proper label styling with :focus and :moz-ui-invalid.
       -->
-    <label>
+    <label id="cc-number-container" class="container">
       <span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
       <input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
       <span data-localization="cardNumber" class="label-text"/>
     </label>
-    <label>
-      <input id="cc-name" type="text" required="required"/>
-      <span data-localization="nameOnCard" class="label-text"/>
-    </label>
-    <label>
+    <label id="cc-exp-month-container" class="container">
       <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>
@@ -44,23 +40,32 @@
         <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>
       <span data-localization="cardExpiresMonth" class="label-text"/>
     </label>
-    <label>
+    <label id="cc-exp-year-container" class="container">
       <select id="cc-exp-year">
         <option/>
       </select>
       <span data-localization="cardExpiresYear" class="label-text"/>
     </label>
-    <label class="billingAddressRow">
+    <label id="cc-name-container" class="container">
+      <input id="cc-name" type="text" required="required"/>
+      <span data-localization="nameOnCard" class="label-text"/>
+    </label>
+    <label id="cc-type-container" class="container">
+      <select id="cc-type" disabled="disabled">
+      </select>
+      <span class="label-text">Card Type</span>
+    </label>
+    <label id="billingAddressGUID-container" class="billingAddressRow container">
       <select id="billingAddressGUID">
       </select>
       <span data-localization="billingAddress" class="label-text"/>
     </label>
   </form>
   <div id="controls-container">
     <button id="cancel" data-localization="cancelBtnLabel"/>
     <button id="save" disabled="disabled" data-localization="saveBtnLabel"/>
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -1,35 +1,37 @@
 /* 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/. */
 
-.editAddressForm input,
-.editAddressForm select {
-  flex: 1 0 auto;
-  margin: 0;
+.editAddressForm {
+  display: flex;
+  flex-wrap: wrap;
+  /* Use space-between so --grid-column-row-gap is in between the elements on a row */
+  justify-content: space-between;
 }
 
-#name-container,
-:root[subdialog] form label,
-:root[subdialog] form > p {
-  margin: 0 0 0.5em !important;
+:root:not([subdialog]) .editAddressForm {
+  margin-inline-start: calc(var(--grid-column-row-gap) / -2);
+  margin-inline-end: calc(var(--grid-column-row-gap) / -2);
 }
 
-#given-name-container,
-#additional-name-container,
-#address-level1-container,
-#postal-code-container,
-#country-container,
-#family-name-container,
-#organization-container,
-#address-level2-container,
-#tel-container {
-  display: flex;
-  flex: 0 1 50%;
+.editAddressForm .container {
+  /* !important is needed to override preferences.css's generic label rule. */
+  margin-top: var(--grid-column-row-gap) !important;
+  margin-inline-start: calc(var(--grid-column-row-gap) / 2);
+  margin-inline-end: calc(var(--grid-column-row-gap) / 2);
+  flex-grow: 1;
+}
+
+#country-container {
+  /* The country dropdown has a different intrinsic (content) width than the
+     other fields which are <input>. */
+  flex-basis: calc(50% - var(--grid-column-row-gap));
+  flex-grow: 0;
 }
 
 
 /* Begin name field rules */
 
 #name-container input {
   /* Override the default @size="20" on <input>, which acts like a min-width, not
    * allowing the fields to shrink with flexbox as small as they need to to match
@@ -48,16 +50,19 @@
 #name-container:focus-within input:-moz-ui-invalid {
   border-color: transparent;
 }
 
 #given-name-container,
 #additional-name-container,
 #family-name-container {
   display: flex;
+  /* The 3 pieces inside the name container don't have the .container class so
+     need to set flex-grow themselves. See `.editAddressForm .container` */
+  flex-grow: 1;
   /* Remove the bottom margin from the name containers so that the outer
      #name-container provides the margin on the outside */
   margin-bottom: 0 !important;
   margin-left: 0;
   margin-right: 0;
 }
 
 /* The name fields are placed adjacent to each other.
@@ -90,26 +95,21 @@
 #name-container input:-moz-ui-invalid,
 #name-container input:-moz-ui-invalid ~ .label-text {
   z-index: 1;
 }
 
 /* End name field rules */
 
 #name-container,
-#street-address-container,
-#email-container {
+#street-address-container {
+  /* Name and street address are always full-width */
   flex: 0 1 100%;
 }
 
-#street-address,
-#email {
-  flex: 1 0 auto;
-}
-
 #country-warning-message {
   box-sizing: border-box;
   font-size: 1rem;
   align-items: center;
   text-align: start;
   color: #737373;
   padding-inline-start: 1em;
 }
--- a/browser/extensions/formautofill/skin/shared/editCreditCard.css
+++ b/browser/extensions/formautofill/skin/shared/editCreditCard.css
@@ -1,29 +1,49 @@
 /* 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/. */
 
 .editCreditCardForm {
-  justify-content: center;
+  display: grid;
+  grid-template-areas:
+    "cc-number          cc-exp-month       cc-exp-year"
+    "cc-name            cc-type            cc-csc"
+    "billingAddressGUID billingAddressGUID billingAddressGUID";
+  grid-row-gap: var(--grid-column-row-gap);
+  grid-column-gap: var(--grid-column-row-gap);
 }
 
-.editCreditCardForm > label,
-.editCreditCardForm > div {
-  flex: 1 0 100%;
-  align-self: center;
-  margin: 0 0 0.5em !important;
+.editCreditCardForm label {
+  /* Remove the margin on these labels since they are styled on top of
+     the input/select element. */
+  margin-inline-start: 0;
+  margin-inline-end: 0;
+}
+
+.editCreditCardForm .container {
+  display: flex;
 }
 
-.editCreditCardForm #billingAddressGUID,
-.editCreditCardForm input {
-  flex: 1 0 auto;
+#cc-number-container {
+  grid-area: cc-number;
+}
+
+#cc-exp-month-container {
+  grid-area: cc-exp-month;
+}
+
+#cc-exp-year-container {
+  grid-area: cc-exp-year;
 }
 
-.editCreditCardForm select {
-  margin: 0;
-  margin-inline-end: 0.7em;
+#cc-name-container {
+  grid-area: cc-name;
 }
 
-.editCreditCardForm label > span,
-.editCreditCardForm div > span {
-  flex: 0 0 9.5em;
+#cc-type-container {
+  grid-area: cc-type;
+  visibility: hidden; /* TODO: Bug 1477105 */
 }
+
+#billingAddressGUID-container {
+  grid-area: billingAddressGUID;
+}
--- a/browser/extensions/formautofill/skin/shared/editDialog-shared.css
+++ b/browser/extensions/formautofill/skin/shared/editDialog-shared.css
@@ -1,42 +1,46 @@
 /* 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/. */
 
 :root {
   --in-field-label-size: .8em;
+  --grid-column-row-gap: 8px;
   /* Use the animation-easing-function that is defined in xul.css. */
   --animation-easing-function: cubic-bezier(.07,.95,0,1);
 }
 
 :root[subdialog] form {
   /* Add extra space to ensure invalid input box is displayed properly */
   padding: 2px;
 }
 
+/* The overly specific input attributes are required to override
+   padding from common.css */
 form input[type="email"],
 form input[type="tel"],
 form input[type="text"],
 form textarea,
 form select {
+  flex-grow: 1;
   padding-top: calc(var(--in-field-label-size) + .4em);
 }
 
 select {
   margin: 0;
   padding-bottom: 5px;
 }
 
 form :-moz-any(label, div) {
+  /* Positioned so that the .label-text and .error-text children will be
+     positioned relative to this. */
   position: relative;
   display: block;
   line-height: 1em;
-  margin-left: 0;
-  margin-right: 0;
 }
 
 form :-moz-any(label, div) > .label-text {
   position: absolute;
   color: GrayText;
   pointer-events: none;
   left: 10px;
   top: .2em;
@@ -60,13 +64,28 @@ form :-moz-any(input, select, textarea):
   color: var(--in-content-text-color);
 }
 
 form div[required] > label > .label-text::after,
 form :-moz-any(label, div)[required] > .label-text::after {
   content: attr(fieldRequiredSymbol);
 }
 
+.persist-checkbox label {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-top: var(--grid-column-row-gap);
+  margin-bottom: var(--grid-column-row-gap);
+}
+
+:root[subdialog] form {
+  /* Match the margin-inline-start of the #controls-container buttons
+     and provide enough padding at the top of the form so button outlines
+     don't get clipped. */
+  padding: 4px 4px 0;
+}
+
 #controls-container {
   flex: 0 1 100%;
   justify-content: end;
   margin: 1em 0 0;
 }
--- a/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editAddressDialog.js
@@ -57,26 +57,26 @@ add_task(async function test_saveAddress
     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.organization,
+      "VK_TAB",
       TEST_ADDRESS_1.country,
       "VK_TAB",
       TEST_ADDRESS_1.tel,
       "VK_TAB",
       TEST_ADDRESS_1.email,
       "VK_TAB",
       "VK_TAB",
       "VK_RETURN",
--- a/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
@@ -19,22 +19,22 @@ add_task(async function test_cancelEditC
   });
 });
 
 add_task(async function test_saveCreditCard() {
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-number"], {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
-    EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-name"], {}, win);
-    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("0" + TEST_CREDIT_CARD_1["cc-exp-month"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-exp-year"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey(TEST_CREDIT_CARD_1["cc-name"], {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     info("saving credit card");
     EventUtils.synthesizeKey("VK_RETURN", {}, win);
   });
   let creditCards = await getCreditCards();
 
   is(creditCards.length, 1, "only one credit card is in storage");
@@ -48,22 +48,22 @@ add_task(async function test_saveCreditC
   ok(creditCards[0]["cc-number-encrypted"], "cc-number-encrypted exists");
 });
 
 add_task(async function test_saveCreditCardWithMaxYear() {
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-number"], {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
-    EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-name"], {}, win);
-    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-exp-month"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-exp-year"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey(TEST_CREDIT_CARD_2["cc-name"], {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     info("saving credit card");
     EventUtils.synthesizeKey("VK_RETURN", {}, win);
   });
   let creditCards = await getCreditCards();
 
   is(creditCards.length, 2, "Two credit cards are in storage");
@@ -86,22 +86,22 @@ add_task(async function test_saveCreditC
   const TEST_CREDIT_CARD = Object.assign({}, TEST_CREDIT_CARD_2, {
     billingAddressGUID: billingAddress.guid,
   });
 
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-number"], {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
-    EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-name"], {}, win);
-    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-exp-month"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-exp-year"].toString(), {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey(TEST_CREDIT_CARD["cc-name"], {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey(billingAddress["given-name"], {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     info("saving credit card");
     EventUtils.synthesizeKey("VK_RETURN", {}, win);
   });
   let creditCards = await getCreditCards();
 
@@ -122,16 +122,18 @@ add_task(async function test_saveCreditC
 });
 
 add_task(async function test_editCreditCard() {
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "only one credit card is in storage");
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_RIGHT", {}, win);
     EventUtils.synthesizeKey("test", {}, win);
     win.document.querySelector("#save").click();
   }, creditCards[0]);
   ok(true, "Edit credit card dialog is closed");
   creditCards = await getCreditCards();
 
   is(creditCards.length, 1, "only one credit card is in storage");
@@ -150,16 +152,18 @@ add_task(async function test_editCreditC
 
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "one credit card in storage");
   is(creditCards[0].billingAddressGUID, TEST_CREDIT_CARD.billingAddressGUID,
      "Check saved billingAddressGUID");
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("VK_RIGHT", {}, win);
     EventUtils.synthesizeKey("test", {}, win);
     win.document.querySelector("#save").click();
   }, creditCards[0]);
   ok(true, "Edit credit card dialog is closed");
   creditCards = await getCreditCards();
 
   is(creditCards.length, 1, "only one credit card is in storage");
@@ -175,16 +179,18 @@ add_task(async function test_editCreditC
 add_task(async function test_addInvalidCreditCard() {
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     const unloadHandler = () => ok(false, "Edit credit card dialog shouldn't be closed");
     win.addEventListener("unload", unloadHandler);
 
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("test", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
+    EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeKey("test name", {}, win);
     EventUtils.synthesizeKey("VK_TAB", {}, win);
     EventUtils.synthesizeMouseAtCenter(win.document.querySelector("#save"), {}, win);
 
     is(win.document.querySelector("form").checkValidity(), false, "cc-number is invalid");
     SimpleTest.requestFlakyTimeout("Ensure the window remains open after save attempt");
     setTimeout(() => {
       win.removeEventListener("unload", unloadHandler);