Backed out 5 changesets (bug 1476204) for browser chrome failures on browser_editCreditCardDialog. CLOSED TREE
authorCosmin Sabou <csabou@mozilla.com>
Thu, 26 Jul 2018 04:12:56 +0300
changeset 428411 438769e48bcee3d2ddfa57f0167342f50a4f4e18
parent 428410 10f3d10145925ae3048b859a7f0b6023f969fe89
child 428412 04b4439ae3f86592d2472e5f099b66abdd585d25
push id34335
push usershindli@mozilla.com
push dateThu, 26 Jul 2018 11:19:11 +0000
treeherdermozilla-central@4e6486b672b3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1476204
milestone63.0a1
backs out10f3d10145925ae3048b859a7f0b6023f969fe89
a88879ea32e829d2f4807bf592de30005586b546
001f13f2dd211cf4035e5efc3bc2824b27d0052a
f0f464e3c27cf3bf7738cea94327a757964f22fc
ed92000eca20db1a19b50bcd5b5ef646ee487666
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
Backed out 5 changesets (bug 1476204) for browser chrome failures on browser_editCreditCardDialog. CLOSED TREE Backed out changeset 10f3d1014592 (bug 1476204) Backed out changeset a88879ea32e8 (bug 1476204) Backed out changeset 001f13f2dd21 (bug 1476204) Backed out changeset f0f464e3c27c (bug 1476204) Backed out changeset ed92000eca20 (bug 1476204)
browser/components/payments/content/paymentDialogWrapper.js
browser/components/payments/res/components/basic-card-option.css
browser/components/payments/res/containers/address-form.js
browser/components/payments/res/containers/address-picker.js
browser/components/payments/res/containers/basic-card-form.js
browser/components/payments/res/containers/payment-dialog.js
browser/components/payments/res/debugging.html
browser/components/payments/res/debugging.js
browser/components/payments/res/mixins/PaymentStateSubscriberMixin.js
browser/components/payments/res/paymentRequest.css
browser/components/payments/res/paymentRequest.js
browser/components/payments/res/paymentRequest.xhtml
browser/components/payments/res/unprivileged-fallbacks.js
browser/components/payments/test/PaymentTestUtils.jsm
browser/components/payments/test/browser/browser_card_edit.js
browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
browser/components/payments/test/browser/browser_shippingaddresschange_error.js
browser/components/payments/test/browser/head.js
browser/components/payments/test/mochitest/test_address_form.html
browser/components/payments/test/mochitest/test_basic_card_form.html
browser/extensions/formautofill/FormAutofillStorage.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/content/autofillEditForms.js
browser/extensions/formautofill/content/editCreditCard.xhtml
browser/extensions/formautofill/content/editDialog.js
browser/extensions/formautofill/locales/en-US/formautofill.properties
browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
browser/extensions/formautofill/test/browser/head.js
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_createRecords.js
browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
browser/extensions/formautofill/test/unit/test_transformFields.js
--- a/browser/components/payments/content/paymentDialogWrapper.js
+++ b/browser/components/payments/content/paymentDialogWrapper.js
@@ -555,62 +555,79 @@ var paymentDialogWrapper = {
     paymentSrv.changeShippingOption(this.request.requestId, optionID);
   },
 
   onCloseDialogMessage() {
     // The PR is complete(), just close the dialog
     window.close();
   },
 
-  async onUpdateAutofillRecord(collectionName, record, guid, messageID) {
-    let responseMessage = {
-      guid,
-      messageID,
-      stateChange: {},
-    };
+  async onUpdateAutofillRecord(collectionName, record, guid, {
+    errorStateChange,
+    preserveOldProperties,
+    selectedStateKey,
+    successStateChange,
+  }) {
+    if (collectionName == "creditCards" && !guid && !record.isTemporary) {
+      // We need to be logged in so we can encrypt the credit card number and
+      // that's only supported when we're adding a new record.
+      // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
+      // APIs are refactored to be async functions (bug 1399367).
+      if (!await MasterPassword.ensureLoggedIn()) {
+        Cu.reportError("User canceled master password entry");
+        return;
+      }
+    }
+    let isTemporary = record.isTemporary;
+    let collection = isTemporary ? this.temporaryStore[collectionName] :
+                                   formAutofillStorage[collectionName];
+
     try {
-      if (collectionName == "creditCards" && !guid && !record.isTemporary) {
-        // We need to be logged in so we can encrypt the credit card number and
-        // that's only supported when we're adding a new record.
-        // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
-        // APIs are refactored to be async functions (bug 1399367).
-        if (!await MasterPassword.ensureLoggedIn()) {
-          throw new Error("User canceled master password entry");
-        }
-      }
-
-      let isTemporary = record.isTemporary;
-      let collection = isTemporary ? this.temporaryStore[collectionName] :
-                                     formAutofillStorage[collectionName];
-
       if (guid) {
-        let preserveOldProperties = true;
         await collection.update(guid, record, preserveOldProperties);
       } else {
-        responseMessage.guid = await collection.add(record);
+        guid = await collection.add(record);
       }
 
       if (isTemporary && collectionName == "addresses") {
         // there will be no formautofill-storage-changed event to update state
         // so add updated collection here
-        Object.assign(responseMessage.stateChange, {
+        Object.assign(successStateChange, {
           tempAddresses: this.temporaryStore.addresses.getAll(),
         });
       }
       if (isTemporary && collectionName == "creditCards") {
         // there will be no formautofill-storage-changed event to update state
         // so add updated collection here
-        Object.assign(responseMessage.stateChange, {
+        Object.assign(successStateChange, {
           tempBasicCards: this.temporaryStore.creditCards.getAll(),
         });
       }
+
+      // Select the new record
+      if (selectedStateKey) {
+        if (selectedStateKey.length == 1) {
+          Object.assign(successStateChange, {
+            [selectedStateKey[0]]: guid,
+          });
+        } else if (selectedStateKey.length == 2) {
+          // Need to keep properties like preserveFieldValues from getting removed.
+          let subObj = Object.assign({}, successStateChange[selectedStateKey[0]]);
+          subObj[selectedStateKey[1]] = guid;
+          Object.assign(successStateChange, {
+            [selectedStateKey[0]]: subObj,
+          });
+        } else {
+          throw new Error(`selectedStateKey not supported: '${selectedStateKey}'`);
+        }
+      }
+
+      this.sendMessageToContent("updateState", successStateChange);
     } catch (ex) {
-      responseMessage.error = true;
-    } finally {
-      this.sendMessageToContent("updateAutofillRecord:Response", responseMessage);
+      this.sendMessageToContent("updateState", errorStateChange);
     }
   },
 
   /**
    * @implement {nsIDOMEventListener}
    * @param {Event} event
    */
   handleEvent(event) {
@@ -673,17 +690,22 @@ var paymentDialogWrapper = {
         this.onPaymentCancel();
         break;
       }
       case "pay": {
         this.onPay(data);
         break;
       }
       case "updateAutofillRecord": {
-        this.onUpdateAutofillRecord(data.collectionName, data.record, data.guid, data.messageID);
+        this.onUpdateAutofillRecord(data.collectionName, data.record, data.guid, {
+          errorStateChange: data.errorStateChange,
+          preserveOldProperties: data.preserveOldProperties,
+          selectedStateKey: data.selectedStateKey,
+          successStateChange: data.successStateChange,
+        });
         break;
       }
     }
   },
 };
 
 if ("document" in this) {
   // Running in a browser, not a unit test
--- a/browser/components/payments/res/components/basic-card-option.css
+++ b/browser/components/payments/res/components/basic-card-option.css
@@ -1,16 +1,16 @@
 /* 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/. */
 
 basic-card-option {
-  grid-column-gap: 1ch;
-  grid-template-areas: "type cc-number cc-exp cc-name";
-  justify-content: start;
+  grid-row-gap: 5px;
+  grid-column-gap: 10px;
+  grid-template-areas: "type cc-number cc-name cc-exp";
 }
 
 basic-card-option > .cc-number {
   grid-area: cc-number;
 }
 
 basic-card-option > .cc-name {
   grid-area: cc-name;
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -238,86 +238,64 @@ export default class AddressForm extends
         break;
       }
       default: {
         throw new Error("Unexpected click target");
       }
     }
   }
 
-  async saveRecord() {
+  saveRecord() {
     let record = this.formHandler.buildFormObject();
     let currentState = this.requestStore.getState();
     let {
       page,
       tempAddresses,
       savedBasicCards,
       "address-page": addressPage,
     } = currentState;
     let editing = !!addressPage.guid;
 
     if (editing ? (addressPage.guid in tempAddresses) : !this.persistCheckbox.checked) {
       record.isTemporary = true;
     }
 
-    let successStateChange;
+    let state = {
+      errorStateChange: {
+        page: {
+          id: "address-page",
+          onboardingWizard: page.onboardingWizard,
+          error: this.dataset.errorGenericSave,
+        },
+        "address-page": addressPage,
+      },
+      preserveOldProperties: true,
+      selectedStateKey: page.selectedStateKey,
+    };
+
     const previousId = page.previousId;
     if (page.onboardingWizard && !Object.keys(savedBasicCards).length) {
-      successStateChange = {
-        "basic-card-page": {
-          // Preserve field values as the user may have already edited the card
-          // page and went back to the address page to make a correction.
-          preserveFieldValues: true,
-        },
+      state.successStateChange = {
         page: {
           id: "basic-card-page",
           previousId: "address-page",
           onboardingWizard: page.onboardingWizard,
         },
       };
     } else {
-      successStateChange = {
+      state.successStateChange = {
         page: {
           id: previousId || "payment-summary",
           onboardingWizard: page.onboardingWizard,
         },
       };
     }
 
     if (previousId) {
-      successStateChange[previousId] = Object.assign({}, currentState[previousId]);
-      successStateChange[previousId].preserveFieldValues = true;
+      state.successStateChange[previousId] = Object.assign({}, currentState[previousId]);
+      state.successStateChange[previousId].preserveFieldValues = true;
     }
 
-    try {
-      let {guid} = await paymentRequest.updateAutofillRecord("addresses", record, addressPage.guid);
-      let selectedStateKey = addressPage.selectedStateKey;
-
-      if (selectedStateKey.length == 1) {
-        Object.assign(successStateChange, {
-          [selectedStateKey[0]]: guid,
-        });
-      } else if (selectedStateKey.length == 2) {
-        // Need to keep properties like preserveFieldValues from getting removed.
-        let subObj = Object.assign({}, successStateChange[selectedStateKey[0]]);
-        subObj[selectedStateKey[1]] = guid;
-        Object.assign(successStateChange, {
-          [selectedStateKey[0]]: subObj,
-        });
-      } else {
-        throw new Error(`selectedStateKey not supported: '${selectedStateKey}'`);
-      }
-
-      this.requestStore.setState(successStateChange);
-    } catch (ex) {
-      log.warn("saveRecord: error:", ex);
-      this.requestStore.setState({
-        page: {
-          id: "address-page",
-          onboardingWizard: page.onboardingWizard,
-          error: this.dataset.errorGenericSave,
-        },
-      });
-    }
+    paymentRequest.updateAutofillRecord("addresses", record, addressPage.guid, state);
   }
 }
 
 customElements.define("address-form", AddressForm);
--- a/browser/components/payments/res/containers/address-picker.js
+++ b/browser/components/payments/res/containers/address-picker.js
@@ -139,20 +139,20 @@ export default class AddressPicker exten
       });
     }
   }
 
   onClick({target}) {
     let nextState = {
       page: {
         id: "address-page",
+        selectedStateKey: [this.selectedStateKey],
       },
       "address-page": {
         addressFields: this.getAttribute("address-fields"),
-        selectedStateKey: [this.selectedStateKey],
       },
     };
 
     switch (target) {
       case this.addLink: {
         nextState["address-page"].guid = null;
         nextState["address-page"].title = this.dataset.addAddressTitle;
         break;
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -17,18 +17,16 @@ import paymentRequest from "../paymentRe
  * as it will be much easier to share the logic once we switch to Fluent.
  */
 
 export default class BasicCardForm extends PaymentStateSubscriberMixin(PaymentRequestPage) {
   constructor() {
     super();
 
     this.genericErrorText = document.createElement("div");
-    this.genericErrorText.setAttribute("aria-live", "polite");
-    this.genericErrorText.classList.add("page-error");
 
     this.addressAddLink = document.createElement("a");
     this.addressAddLink.className = "add-link";
     this.addressAddLink.href = "javascript:void(0)";
     this.addressAddLink.addEventListener("click", this);
     this.addressEditLink = document.createElement("a");
     this.addressEditLink.className = "edit-link";
     this.addressEditLink.href = "javascript:void(0)";
@@ -51,18 +49,16 @@ export default class BasicCardForm exten
     this.saveButton.addEventListener("click", this);
 
     this.footer.append(this.cancelButton, this.backButton, this.saveButton);
 
     // The markup is shared with form autofill preferences.
     let url = "formautofill/editCreditCard.xhtml";
     this.promiseReady = this._fetchMarkup(url).then(doc => {
       this.form = doc.getElementById("form");
-      this.form.addEventListener("input", this);
-      this.form.addEventListener("invalid", this);
       return this.form;
     });
   }
 
   _fetchMarkup(url) {
     return new Promise((resolve, reject) => {
       let xhr = new XMLHttpRequest();
       xhr.responseType = "document";
@@ -162,34 +158,24 @@ export default class BasicCardForm exten
     this.form.querySelector(".billingAddressRow").hidden = false;
 
     let billingAddressSelect = this.form.querySelector("#billingAddressGUID");
     if (basicCardPage.billingAddressGUID) {
       billingAddressSelect.value = basicCardPage.billingAddressGUID;
     } else if (!editing) {
       billingAddressSelect.value = Object.keys(addresses)[0];
     }
-
-    this.updateSaveButtonState();
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "click": {
         this.onClick(event);
         break;
       }
-      case "input": {
-        this.onInput(event);
-        break;
-      }
-      case "invalid": {
-        this.onInvalid(event);
-        break;
-      }
     }
   }
 
   onClick(evt) {
     switch (evt.target) {
       case this.cancelButton: {
         paymentRequest.cancel();
         break;
@@ -198,20 +184,20 @@ export default class BasicCardForm exten
       case this.addressEditLink: {
         let {
           "basic-card-page": basicCardPage,
         } = this.requestStore.getState();
         let nextState = {
           page: {
             id: "address-page",
             previousId: "basic-card-page",
+            selectedStateKey: ["basic-card-page", "billingAddressGUID"],
           },
           "address-page": {
             guid: null,
-            selectedStateKey: ["basic-card-page", "billingAddressGUID"],
             title: this.dataset.billingAddressTitleAdd,
           },
           "basic-card-page": {
             preserveFieldValues: true,
             guid: basicCardPage.guid,
           },
         };
         let billingAddressGUID = this.form.querySelector("#billingAddressGUID");
@@ -256,43 +242,30 @@ export default class BasicCardForm exten
             "basic-card-page": basicCardPageState,
           });
         }
 
         this.requestStore.setState(nextState);
         break;
       }
       case this.saveButton: {
-        if (this.form.checkValidity()) {
-          this.saveRecord();
-        }
+        this.saveRecord();
         break;
       }
       default: {
         throw new Error("Unexpected click target");
       }
     }
   }
 
-  onInput(event) {
-    this.updateSaveButtonState();
-  }
-
-  onInvalid(event) {
-    this.saveButton.disabled = true;
-  }
-
-  updateSaveButtonState() {
-    this.saveButton.disabled = !this.form.checkValidity();
-  }
-
-  async saveRecord() {
+  saveRecord() {
     let record = this.formHandler.buildFormObject();
     let currentState = this.requestStore.getState();
     let {
+      page,
       tempBasicCards,
       "basic-card-page": basicCardPage,
     } = currentState;
     let editing = !!basicCardPage.guid;
 
     if (editing ? (basicCardPage.guid in tempBasicCards) : !this.persistCheckbox.checked) {
       record.isTemporary = true;
     }
@@ -302,30 +275,34 @@ export default class BasicCardForm exten
     }
 
     // Only save the card number if we're saving a new record, otherwise we'd
     // overwrite the unmasked card number with the masked one.
     if (!editing) {
       record["cc-number"] = record["cc-number"] || "";
     }
 
-    try {
-      let {guid} = await paymentRequest.updateAutofillRecord("creditCards", record,
-                                                             basicCardPage.guid);
-      this.requestStore.setState({
-        page: {
-          id: "payment-summary",
-        },
-        selectedPaymentCard: guid,
-      });
-    } catch (ex) {
-      log.warn("saveRecord: error:", ex);
-      this.requestStore.setState({
+    let state = {
+      errorStateChange: {
         page: {
           id: "basic-card-page",
           error: this.dataset.errorGenericSave,
         },
-      });
+      },
+      preserveOldProperties: true,
+      selectedStateKey: ["selectedPaymentCard"],
+      successStateChange: {
+        page: {
+          id: "payment-summary",
+        },
+      },
+    };
+
+    const previousId = page.previousId;
+    if (previousId) {
+      state.successStateChange[previousId] = Object.assign({}, currentState[previousId]);
     }
+
+    paymentRequest.updateAutofillRecord("creditCards", record, basicCardPage.guid, state);
   }
 }
 
 customElements.define("basic-card-form", BasicCardForm);
--- a/browser/components/payments/res/containers/payment-dialog.js
+++ b/browser/components/payments/res/containers/payment-dialog.js
@@ -48,17 +48,17 @@ export default class PaymentDialog exten
 
     this._shippingAddressPicker = contents.querySelector("address-picker.shipping-related");
     this._shippingRelatedEls = contents.querySelectorAll(".shipping-related");
     this._payerRelatedEls = contents.querySelectorAll(".payer-related");
     this._payerAddressPicker = contents.querySelector("address-picker.payer-related");
 
     this._header = contents.querySelector("header");
 
-    this._errorText = contents.querySelector("header > .page-error");
+    this._errorText = contents.querySelector("#error-text");
 
     this._disabledOverlay = contents.getElementById("disabled-overlay");
 
     this.appendChild(contents);
 
     super.connectedCallback();
   }
 
--- a/browser/components/payments/res/debugging.html
+++ b/browser/components/payments/res/debugging.html
@@ -54,17 +54,16 @@
           <label class="block"><input type="radio" name="setCompleteStatus" value="timeout">Timeout</label>
         </fieldset>
         <label class="block"><input type="checkbox" id="setChangesPrevented">Prevent changes</label>
 
 
         <section class="group">
           <fieldset>
             <legend>User Data Errors</legend>
-            <button id="saveVisibleForm" title="Bypasses field validation">Save Visible Form</button>
             <button id="setShippingError">Shipping Error</button>
             <button id="setAddressErrors">Address Errors</button>
           </fieldset>
         </section>
       </section>
     </div>
   </body>
 </html>
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -328,21 +328,16 @@ let buttonActions = {
   refresh() {
     window.parent.location.reload(true);
   },
 
   rerender() {
     requestStore.setState({});
   },
 
-  saveVisibleForm() {
-    // Bypasses field validation which is useful to test error handling.
-    paymentDialog.querySelector("#main-container > .page:not([hidden])").saveRecord();
-  },
-
   setAddresses1() {
     paymentDialog.setStateFromParent({savedAddresses: ADDRESSES_1});
   },
 
   setDupesAddresses() {
     paymentDialog.setStateFromParent({savedAddresses: DUPED_ADDRESSES});
   },
 
--- a/browser/components/payments/res/mixins/PaymentStateSubscriberMixin.js
+++ b/browser/components/payments/res/mixins/PaymentStateSubscriberMixin.js
@@ -15,26 +15,26 @@ export let requestStore = new PaymentsSt
   changesPrevented: false,
   orderDetailsShowing: false,
   "basic-card-page": {
     guid: null,
     // preserveFieldValues: true,
   },
   "address-page": {
     guid: null,
-    selectedStateKey: null,
     title: "",
   },
   "payment-summary": {
   },
   page: {
     id: "payment-summary",
     previousId: null,
     // onboardingWizard: true,
     // error: "",
+    // selectedStateKey: "",
   },
   request: {
     completeStatus: "",
     tabId: null,
     topLevelPrincipal: {URI: {displayHost: null}},
     requestId: null,
     paymentMethods: [],
     paymentDetails: {
--- a/browser/components/payments/res/paymentRequest.css
+++ b/browser/components/payments/res/paymentRequest.css
@@ -40,35 +40,21 @@ payment-dialog > header,
 .page > .page-body,
 .page > footer {
   padding: 0 10%;
 }
 
 payment-dialog > header {
   border-bottom: 1px solid rgba(0,0,0,0.1);
   display: flex;
-  /* Wrap so that the error text appears full-width above the rest of the contents */
-  flex-wrap: wrap;
   /* from visual spec: */
   padding-bottom: 19px;
   padding-top: 19px;
 }
 
-payment-dialog > header > .page-error:empty {
-  display: none;
-}
-
-payment-dialog > header > .page-error {
-  background: #D70022;
-  border-radius: 3px;
-  color: white;
-  padding: 6px;
-  width: 100%;
-}
-
 #main-container {
   display: flex;
   grid-area: main;
   position: relative;
   max-height: 100%;
   padding-top: 18px;
 }
 
@@ -87,29 +73,29 @@ payment-dialog > header > .page-error {
   /* The area above the footer should scroll, if necessary. */
   overflow: auto;
 }
 
 .page > .page-body > h2:empty {
   display: none;
 }
 
-.page-error {
-  color: #D70022;
-}
-
 .page > footer {
   align-items: center;
   background-color: #eaeaee;
   display: flex;
   /* from visual spec: */
   padding-top: 20px;
   padding-bottom: 18px;
 }
 
+#error-text {
+  text-align: center;
+}
+
 #order-details-overlay {
   background-color: var(--in-content-page-background);
   overflow: auto;
   position: absolute;
   top: 0;
   right: 0;
   bottom: 0;
   left: 0;
--- a/browser/components/payments/res/paymentRequest.js
+++ b/browser/components/payments/res/paymentRequest.js
@@ -6,17 +6,16 @@
  * Loaded in the unprivileged frame of each payment dialog.
  *
  * Communicates with privileged code via DOM Events.
  */
 
 /* import-globals-from unprivileged-fallbacks.js */
 
 var paymentRequest = {
-  _nextMessageID: 1,
   domReadyPromise: null,
 
   init() {
     // listen to content
     window.addEventListener("paymentChromeToContent", this);
 
     window.addEventListener("keydown", this);
 
@@ -50,33 +49,25 @@ var paymentRequest = {
         break;
       }
       default: {
         throw new Error("Unexpected event type");
       }
     }
   },
 
-  /**
-   * @param {string} messageType
-   * @param {[object]} detail
-   * @returns {number} message ID to be able to identify a reply (where applicable).
-   */
   sendMessageToChrome(messageType, detail = {}) {
-    let messageID = this._nextMessageID++;
-    log.debug("sendMessageToChrome:", messageType, messageID, detail);
+    log.debug("sendMessageToChrome:", messageType, detail);
     let event = new CustomEvent("paymentContentToChrome", {
       bubbles: true,
       detail: Object.assign({
         messageType,
-        messageID,
       }, detail),
     });
     document.dispatchEvent(event);
-    return messageID;
   },
 
   toggleDebuggingConsole() {
     let debuggingConsole = document.getElementById("debugging-console");
     if (debuggingConsole.hidden && !debuggingConsole.src) {
       debuggingConsole.src = "debugging.html";
     }
     debuggingConsole.hidden = !debuggingConsole.hidden;
@@ -147,24 +138,24 @@ var paymentRequest = {
 
       state["address-page"] = {
         addressFields: null,
         guid: null,
       };
 
       if (shippingRequested) {
         Object.assign(state["address-page"], {
-          selectedStateKey: ["selectedShippingAddress"],
           title: paymentDialog.dataset.shippingAddressTitleAdd,
         });
+        state.page.selectedStateKey = ["selectedShippingAddress"];
       } else {
         Object.assign(state["address-page"], {
-          selectedStateKey: ["basic-card-page", "billingAddressGUID"],
           title: paymentDialog.dataset.billingAddressTitleAdd,
         });
+        state.page.selectedStateKey = ["basic-card-page", "billingAddressGUID"];
       }
     } else if (!hasSavedCards) {
       state.page = {
         id: "basic-card-page",
         onboardingWizard: true,
       };
     }
 
@@ -193,40 +184,31 @@ var paymentRequest = {
 
   /**
    * Add/update an autofill storage record.
    *
    * If the the `guid` argument is provided update the record; otherwise, add it.
    * @param {string} collectionName The autofill collection that record belongs to.
    * @param {object} record The autofill record to add/update
    * @param {string} [guid] The guid of the autofill record to update
-   * @returns {Promise} when the update response is received
    */
-  updateAutofillRecord(collectionName, record, guid) {
-    return new Promise((resolve, reject) => {
-      let messageID = this.sendMessageToChrome("updateAutofillRecord", {
-        collectionName,
-        guid,
-        record,
-      });
-
-      window.addEventListener("paymentChromeToContent", function onMsg({detail}) {
-        if (detail.messageType != "updateAutofillRecord:Response"
-            || detail.messageID != messageID) {
-          return;
-        }
-        log.debug("updateAutofillRecord: response:", detail);
-        window.removeEventListener("paymentChromeToContent", onMsg);
-        document.querySelector("payment-dialog").setStateFromParent(detail.stateChange);
-        if (detail.error) {
-          reject(detail);
-        } else {
-          resolve(detail);
-        }
-      });
+  updateAutofillRecord(collectionName, record, guid, {
+    errorStateChange,
+    preserveOldProperties,
+    selectedStateKey,
+    successStateChange,
+  }) {
+    this.sendMessageToChrome("updateAutofillRecord", {
+      collectionName,
+      guid,
+      record,
+      errorStateChange,
+      preserveOldProperties,
+      selectedStateKey,
+      successStateChange,
     });
   },
 
   /**
    * @param {object} state object representing the UI state
    * @param {string} methodID (GUID) uniquely identifying the selected payment method
    * @returns {object?} the applicable modifier for the payment method
    */
--- a/browser/components/payments/res/paymentRequest.xhtml
+++ b/browser/components/payments/res/paymentRequest.xhtml
@@ -89,29 +89,29 @@
 
   <script src="formautofill/autofillEditForms.js"></script>
 
   <script type="module" src="containers/payment-dialog.js"></script>
   <script type="module" src="paymentRequest.js"></script>
 
   <template id="payment-dialog-template">
     <header>
-      <div class="page-error" aria-live="polite"></div>
       <div id="total">
         <currency-amount display-code="display-code"></currency-amount>
         <div>&header.payTo; <span id="host-name"></span></div>
       </div>
       <div id="top-buttons" hidden="hidden">
         <button id="view-all" class="closed">&viewAllItems;</button>
       </div>
     </header>
 
     <div id="main-container">
       <payment-request-page id="payment-summary">
         <div class="page-body">
+          <div id="error-text"></div>
           <address-picker class="shipping-related"
                           data-add-link-label="&address.addLink.label;"
                           data-edit-link-label="&address.editLink.label;"
                           data-shipping-address-label="&shippingAddressLabel;"
                           data-delivery-address-label="&deliveryAddressLabel;"
                           data-pickup-address-label="&pickupAddressLabel;"
                           selected-state-key="selectedShippingAddress"></address-picker>
 
@@ -123,16 +123,17 @@
                                  data-edit-link-label="&basicCard.editLink.label;"
                                  label="&paymentMethodsLabel;">
           </payment-method-picker>
           <address-picker class="payer-related"
                           label="&payerLabel;"
                           data-add-link-label="&payer.addLink.label;"
                           data-edit-link-label="&payer.editLink.label;"
                           selected-state-key="selectedPayerAddress"></address-picker>
+          <div id="error-text"></div>
         </div>
 
         <footer>
           <button id="cancel">&cancelPaymentButton.label;</button>
           <button id="pay"
                   class="primary"
                   data-label="&approvePaymentButton.label;"
                   data-processing-label="&processingPaymentButton.label;"
--- a/browser/components/payments/res/unprivileged-fallbacks.js
+++ b/browser/components/payments/res/unprivileged-fallbacks.js
@@ -21,17 +21,17 @@ var log = {
   debug: console.debug.bind(console, "paymentRequest.xhtml:"),
 };
 
 var PaymentDialogUtils = {
   getAddressLabel(address) {
     return `${address.name} (${address.guid})`;
   },
   isCCNumber(str) {
-    return !!str.replace(/[-\s]/g, "").match(/^\d{9,}$/);
+    return str.length > 0;
   },
   DEFAULT_REGION: "US",
   supportedCountries: ["US", "CA"],
   getFormFormat(country) {
     return {
       "addressLevel1Label": country == "US" ? "state" : "province",
       "postalCodeLabel": country == "US" ? "zip" : "postalCode",
       "fieldsOrder": [
--- a/browser/components/payments/test/PaymentTestUtils.jsm
+++ b/browser/components/payments/test/PaymentTestUtils.jsm
@@ -167,17 +167,16 @@ var PaymentTestUtils = {
      * Don't await on this method from a ContentTask when expecting the dialog to close
      *
      * @returns {undefined}
      */
     clickPrimaryButton: () => {
       let {requestStore} = Cu.waiveXrays(content.document.querySelector("payment-dialog"));
       let {page} = requestStore.getState();
       let button = content.document.querySelector(`#${page.id} button.primary`);
-      ok(!button.disabled, "Primary button should not be disabled when clicking it");
       button.click();
     },
 
     /**
      * Click the cancel button
      *
      * Don't await on this task since the cancel can close the dialog before
      * ContentTask can resolve the promise.
--- a/browser/components/payments/test/browser/browser_card_edit.js
+++ b/browser/components/payments/test/browser/browser_card_edit.js
@@ -108,23 +108,23 @@ async function add_link(aOptions = {}) {
     }, aOptions);
 
     await navigateToAddAddressPage(frame, addressOptions);
 
     await fillInAddressForm(frame, PTU.Addresses.TimBL2, addressOptions);
 
     await verifyPersistCheckbox(frame, addressOptions);
 
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
-
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
+      content.document.querySelector("address-form button:last-of-type").click();
+
       let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "basic-card-page" && !state["basic-card-page"].guid;
       }, "Check address was added and we're back on basic-card page (add)");
 
       let addressCount = Object.keys(state.savedAddresses).length +
                          Object.keys(state.tempAddresses).length;
       is(addressCount, 2, "Check address was added");
 
@@ -147,29 +147,28 @@ async function add_link(aOptions = {}) {
       is(selectedAddressGuid, lastAddress.guid, "The select should have the new address selected");
     }, aOptions);
 
     await fillInCardForm(frame, PTU.BasicCards.JaneMasterCard, {
       isTemporary: aOptions.isPrivate,
       checkboxSelector: "basic-card-form .persist-checkbox",
     });
 
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
-
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
+      content.document.querySelector("basic-card-form button:last-of-type").click();
+
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
-      }, "Check we are back on the summary page");
+      }, "Check we are back on the sumamry page");
     });
 
-
     await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.setSecurityCode, {
       securityCode: "123",
     });
 
     await spawnPaymentDialogTask(frame, async (testArgs = {}) => {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
@@ -366,26 +365,26 @@ add_task(async function test_edit_link()
       let field = content.document.getElementById(key);
       if (!field) {
         ok(false, `${key} field not found`);
       }
       field.value = val.slice(0, -1) + "7";
       ok(!field.disabled, `Field #${key} shouldn't be disabled`);
     }
 
-    content.document.querySelector("address-form button.save-button").click();
+    content.document.querySelector("address-form button:last-of-type").click();
     state = await PTU.DialogContentUtils.waitForState(content, (state) => {
       return state.page.id == "basic-card-page" && state["basic-card-page"].guid &&
              Object.keys(state.savedAddresses).length == 1;
     }, "Check still only one address and we're back on basic-card page");
 
     is(Object.values(state.savedAddresses)[0].tel, PTU.Addresses.TimBL.tel.slice(0, -1) + "7",
        "Check that address was edited and saved");
 
-    content.document.querySelector("basic-card-form button.save-button").click();
+    content.document.querySelector("basic-card-form button:last-of-type").click();
 
     state = await PTU.DialogContentUtils.waitForState(content, (state) => {
       let cards = Object.entries(state.savedBasicCards);
       return cards.length == 1 &&
              cards[0][1]["cc-name"] == card["cc-name"];
     }, "Check card was edited");
 
     let cardGUIDs = Object.keys(state.savedBasicCards);
@@ -399,78 +398,70 @@ add_task(async function test_edit_link()
     state = await PTU.DialogContentUtils.waitForState(content, (state) => {
       return state.page.id == "payment-summary";
     }, "Switched back to payment-summary");
   }, args);
 });
 
 add_task(async function test_private_card_adding() {
   await setup([PTU.Addresses.TimBL], [PTU.BasicCards.JohnDoe]);
+  const args = {
+    methodData: [PTU.MethodData.basicCard],
+    details: PTU.Details.total60USD,
+  };
   let privateWin = await BrowserTestUtils.openNewBrowserWindow({private: true});
-
-  await BrowserTestUtils.withNewTab({
-    gBrowser: privateWin.gBrowser,
-    url: BLANK_PAGE_URL,
-  }, async browser => {
-    let {win, frame} = await setupPaymentDialog(browser, {
-      methodData: [PTU.MethodData.basicCard],
-      details: PTU.Details.total60USD,
-      merchantTaskFn: PTU.ContentTasks.createAndShowRequest,
-    });
+  await spawnInDialogForMerchantTask(PTU.ContentTasks.createAndShowRequest, async function check() {
+    let {
+      PaymentTestUtils: PTU,
+    } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
-    await spawnPaymentDialogTask(frame, async function check() {
-      let {
-        PaymentTestUtils: PTU,
-      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+    let addLink = content.document.querySelector("payment-method-picker .add-link");
+    is(addLink.textContent, "Add", "Add link text");
 
-      let addLink = content.document.querySelector("payment-method-picker .add-link");
-      is(addLink.textContent, "Add", "Add link text");
+    addLink.click();
 
-      addLink.click();
+    let state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+      return state.page.id == "basic-card-page" && !state["basic-card-page"].guid;
+    },
+                                                          "Check add page state");
 
-      await PTU.DialogContentUtils.waitForState(content, (state) => {
-        return state.page.id == "basic-card-page" && !state["basic-card-page"].guid;
-      },
-                                                "Check card page state");
-    });
+    let savedCardCount = Object.keys(state.savedBasicCards).length;
+    let tempCardCount = Object.keys(state.tempBasicCards).length;
 
-    await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
+    let card = Object.assign({}, PTU.BasicCards.JohnDoe);
 
-    await spawnPaymentDialogTask(frame, async function() {
-      let {
-        PaymentTestUtils: PTU,
-      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+    info("filling fields");
+    for (let [key, val] of Object.entries(card)) {
+      let field = content.document.getElementById(key);
+      field.value = val;
+      ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+    }
 
-      let card = Object.assign({}, PTU.BasicCards.JohnDoe);
-      let state = await PTU.DialogContentUtils.getCurrentState(content);
-      let savedCardCount = Object.keys(state.savedBasicCards).length;
-      let tempCardCount = Object.keys(state.tempBasicCards).length;
-      content.document.querySelector("basic-card-form button.save-button").click();
+    content.document.querySelector("basic-card-form button:last-of-type").click();
 
-      state = await PTU.DialogContentUtils.waitForState(content, (state) => {
-        return Object.keys(state.tempBasicCards).length > tempCardCount;
-      },
-                                                        "Check card was added to temp collection");
+    state = await PTU.DialogContentUtils.waitForState(content, (state) => {
+      return Object.keys(state.tempBasicCards).length > tempCardCount;
+    },
+                                                      "Check card was added to temp collection");
 
-      is(savedCardCount, Object.keys(state.savedBasicCards).length, "No card was saved in state");
-      is(Object.keys(state.tempBasicCards).length, 1, "Card was added temporarily");
+    is(savedCardCount, Object.keys(state.savedBasicCards).length, "No card was saved in state");
+    is(Object.keys(state.tempBasicCards).length, 1, "Card was added temporarily");
 
-      let cardGUIDs = Object.keys(state.tempBasicCards);
-      is(cardGUIDs.length, 1, "Check there is one card");
+    let cardGUIDs = Object.keys(state.tempBasicCards);
+    is(cardGUIDs.length, 1, "Check there is one card");
 
-      let tempCard = state.tempBasicCards[cardGUIDs[0]];
-      // Card number should be masked, so skip cc-number in the compare loop below
-      delete card["cc-number"];
-      for (let [key, val] of Object.entries(card)) {
-        is(tempCard[key], val, "Check " + key + ` ${tempCard[key]} matches ${val}`);
-      }
-      // check computed fields
-      is(tempCard["cc-number"], "************1111", "cc-number is masked");
-      is(tempCard["cc-given-name"], "John", "cc-given-name was computed");
-      is(tempCard["cc-family-name"], "Doe", "cc-family-name was computed");
-      ok(tempCard["cc-exp"], "cc-exp was computed");
-      ok(tempCard["cc-number-encrypted"], "cc-number-encrypted was computed");
-    });
-    spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
-    await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
+    let tempCard = state.tempBasicCards[cardGUIDs[0]];
+    // Card number should be masked, so skip cc-number in the compare loop below
+    delete card["cc-number"];
+    for (let [key, val] of Object.entries(card)) {
+      is(tempCard[key], val, "Check " + key + ` ${tempCard[key]} matches ${val}`);
+    }
+    // check computed fields
+    is(tempCard["cc-number"], "************1111", "cc-number is masked");
+    is(tempCard["cc-given-name"], "John", "cc-given-name was computed");
+    is(tempCard["cc-family-name"], "Doe", "cc-family-name was computed");
+    ok(tempCard["cc-exp"], "cc-exp was computed");
+    ok(tempCard["cc-number-encrypted"], "cc-number-encrypted was computed");
+  }, args, {
+    browser: privateWin.gBrowser,
   });
   await BrowserTestUtils.closeWindow(privateWin);
 });
--- a/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
+++ b/browser/components/payments/test/browser/browser_payments_onboarding_wizard.js
@@ -32,17 +32,17 @@ add_task(async function test_onboarding_
 
     await spawnPaymentDialogTask(frame, async function() {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" &&
-               state["address-page"].selectedStateKey[0] == "selectedShippingAddress";
+               state.page.selectedStateKey[0] == "selectedShippingAddress";
       }, "Address page is shown first during on-boarding if there are no saved addresses");
 
       info("Checking if the address page has been rendered");
       let addressSaveButton = content.document.querySelector("address-form .save-button");
       ok(content.isVisible(addressSaveButton), "Address save button is rendered");
 
       info("Check if the total header is visible on the address page during on-boarding");
       let header = content.document.querySelector("header");
@@ -81,33 +81,31 @@ add_task(async function test_onboarding_
       ok(content.isVisible(basicCardTitle), "Basic card page title is visible");
       is(basicCardTitle.textContent, "Add Credit Card", "Basic card page title is correctly shown");
 
       info("Check if the correct billing address is selected in the basic card page");
       PTU.DialogContentUtils.waitForState(content, (state) => {
         let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
         return state.selectedShippingAddress == billingAddressSelect.value;
       }, "Shipping address is selected as the billing address");
-    });
 
-    await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
-
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
-
-    await spawnPaymentDialogTask(frame, async function() {
-      let {
-        PaymentTestUtils: PTU,
-      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+      for (let [key, val] of Object.entries(PTU.BasicCards.JohnDoe)) {
+        let field = content.document.getElementById(key);
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+      content.document.querySelector("basic-card-form .save-button").click();
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
       }, "Payment summary page is shown after the basic card page during on boarding");
 
       let cancelButton = content.document.querySelector("#cancel");
-      ok(content.isVisible(cancelButton), "Payment summary page is rendered");
+      ok(content.isVisible(cancelButton),
+         "Payment summary page is rendered");
     });
 
     info("Closing the payment dialog");
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
   });
 });
@@ -314,18 +312,18 @@ add_task(async function test_onboarding_
 
     await spawnPaymentDialogTask(frame, async function() {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" &&
-               state["address-page"].selectedStateKey[0] == "basic-card-page" &&
-               state["address-page"].selectedStateKey[1] == "billingAddressGUID";
+               state.page.selectedStateKey[0] == "basic-card-page" &&
+               state.page.selectedStateKey[1] == "billingAddressGUID";
       // eslint-disable-next-line max-len
       }, "Billing address page is shown first during on-boarding if requestShipping is turned off");
 
       info("Checking if the billing address page has been rendered");
       let addressSaveButton = content.document.querySelector("address-form .save-button");
       ok(content.isVisible(addressSaveButton),
          "Address save button is rendered");
 
@@ -353,26 +351,23 @@ add_task(async function test_onboarding_
       let cardSaveButton = content.document.querySelector("basic-card-form .save-button");
       ok(content.isVisible(cardSaveButton), "Basic card page is rendered");
 
       info("Check if the correct billing address is selected in the basic card page");
       PTU.DialogContentUtils.waitForState(content, (state) => {
         let billingAddressSelect = content.document.querySelector("#billingAddressGUID");
         return state["basic-card-page"].billingAddressGUID == billingAddressSelect.value;
       }, "Billing Address is correctly shown");
-    });
 
-    await fillInCardForm(frame, PTU.BasicCards.JohnDoe);
-
-    await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.clickPrimaryButton);
-
-    await spawnPaymentDialogTask(frame, async function() {
-      let {
-        PaymentTestUtils: PTU,
-      } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
+      for (let [key, val] of Object.entries(PTU.BasicCards.JohnDoe)) {
+        let field = content.document.getElementById(key);
+        field.value = val;
+        ok(!field.disabled, `Field #${key} shouldn't be disabled`);
+      }
+      content.document.querySelector("basic-card-form .save-button").click();
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
       }, "payment-summary is shown after the basic card page during on boarding");
 
       let cancelButton = content.document.querySelector("#cancel");
       ok(content.isVisible(cancelButton), "Payment summary page is rendered");
     });
@@ -433,18 +428,18 @@ add_task(async function test_back_button
 
     await spawnPaymentDialogTask(frame, async function() {
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page";
-      }, "Billing address page is shown first if there are no saved addresses " +
-         "and requestShipping is false during on boarding");
+      }, "Address page is shown first if there are saved addresses during on boarding");
+
       info("Checking if the address page has been rendered");
       let addressSaveButton = content.document.querySelector("address-form .save-button");
       ok(content.isVisible(addressSaveButton), "Address save button is rendered");
 
       for (let [key, val] of Object.entries(PTU.Addresses.TimBL2)) {
         let field = content.document.getElementById(key);
         if (!field) {
           ok(false, `${key} field not found`);
--- a/browser/components/payments/test/browser/browser_shippingaddresschange_error.js
+++ b/browser/components/payments/test/browser/browser_shippingaddresschange_error.js
@@ -32,17 +32,17 @@ add_task(async function test_show_error_
     await spawnPaymentDialogTask(frame, PTU.DialogContentTasks.selectShippingOptionById, "1");
 
     info("awaiting the shippingoptionchange event");
     await ContentTask.spawn(browser, {
       eventName: "shippingoptionchange",
     }, PTU.ContentTasks.awaitPaymentRequestEventPromise);
 
     await spawnPaymentDialogTask(frame, expectedText => {
-      let errorText = content.document.querySelector("header .page-error");
+      let errorText = content.document.querySelector("#error-text");
       is(errorText.textContent, expectedText, "Error text should be on dialog");
       ok(content.isVisible(errorText), "Error text should be visible");
     }, PTU.Details.genericShippingError.error);
 
     info("setting up the event handler for shippingaddresschange");
     await ContentTask.spawn(browser, {
       eventName: "shippingaddresschange",
       details: Object.assign({},
@@ -54,17 +54,17 @@ add_task(async function test_show_error_
     await selectPaymentDialogShippingAddressByCountry(frame, "DE");
 
     info("awaiting the shippingaddresschange event");
     await ContentTask.spawn(browser, {
       eventName: "shippingaddresschange",
     }, PTU.ContentTasks.awaitPaymentRequestEventPromise);
 
     await spawnPaymentDialogTask(frame, () => {
-      let errorText = content.document.querySelector("header .page-error");
+      let errorText = content.document.querySelector("#error-text");
       is(errorText.textContent, "", "Error text should not be on dialog");
       ok(content.isHidden(errorText), "Error text should not be visible");
     });
 
     info("clicking cancel");
     spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
 
     await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
@@ -107,17 +107,17 @@ add_task(async function test_show_field_
       let {
         PaymentTestUtils: PTU,
       } = ChromeUtils.import("resource://testing-common/PaymentTestUtils.jsm", {});
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return Object.keys(state.request.paymentDetails.shippingAddressErrors).length;
       }, "Check that there are shippingAddressErrors");
 
-      is(content.document.querySelector("header .page-error").textContent,
+      is(content.document.querySelector("#error-text").textContent,
          PTU.Details.fieldSpecificErrors.error,
          "Error text should be present on dialog");
 
       info("click the Edit link");
       content.document.querySelector("address-picker .edit-link").click();
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" && state["address-page"].guid;
@@ -160,17 +160,17 @@ add_task(async function test_show_field_
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "payment-summary";
       }, "Check we're back on summary view");
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return !Object.keys(state.request.paymentDetails.shippingAddressErrors).length;
       }, "Check that there are no more shippingAddressErrors");
 
-      is(content.document.querySelector("header .page-error").textContent,
+      is(content.document.querySelector("#error-text").textContent,
          "", "Error text should not be present on dialog");
 
       info("click the Edit link again");
       content.document.querySelector("address-picker .edit-link").click();
 
       await PTU.DialogContentUtils.waitForState(content, (state) => {
         return state.page.id == "address-page" && state["address-page"].guid;
       }, "Check edit page state");
--- a/browser/components/payments/test/browser/head.js
+++ b/browser/components/payments/test/browser/head.js
@@ -493,23 +493,18 @@ async function fillInCardForm(frame, aCa
 
     // fill the form
     info("fillInCardForm: fill the form with card: " + JSON.stringify(card));
     for (let [key, val] of Object.entries(card)) {
       let field = content.document.getElementById(key);
       if (!field) {
         ok(false, `${key} field not found`);
       }
-      ok(!field.disabled, `Field #${key} shouldn't be disabled`);
-      field.value = "";
-      field.focus();
-      // cc-exp-* fields are numbers so convert to strings and pad left with 0
-      EventUtils.synthesizeKey(val.toString().padStart(2, "0"), {}, content.window);
+      field.value = val;
     }
-
     let persistCheckbox = content.document.querySelector(options.checkboxSelector);
     // only touch the checked state if explicitly told to in the options
     if (options.hasOwnProperty("isTemporary")) {
       Cu.waiveXrays(persistCheckbox).checked = !options.isTemporary;
     }
   }, {card: aCard, options: aOptions});
 }
 
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -135,36 +135,52 @@ add_task(async function test_saveButton(
   sendString("+15555551212");
 
   let messagePromise = promiseContentToChromeMessage("updateAutofillRecord");
   is(form.saveButton.textContent, "Save", "Check label");
   form.saveButton.scrollIntoView();
   synthesizeMouseAtCenter(form.saveButton, {});
 
   let details = await messagePromise;
-  ok(typeof(details.messageID) == "number" && details.messageID > 0, "Check messageID type");
-  delete details.messageID;
   is(details.collectionName, "addresses", "Check collectionName");
   isDeeply(details, {
     collectionName: "addresses",
+    errorStateChange: {
+      page: {
+        id: "address-page",
+        error: "Generic error",
+        onboardingWizard: undefined,
+      },
+      "address-page": {
+        title: "Sample page title",
+      },
+    },
     guid: undefined,
     messageType: "updateAutofillRecord",
+    preserveOldProperties: true,
     record: {
       "given-name": "Jaws",
       "family-name": "Swaj",
       "additional-name": "",
       "organization": "Allizom",
       "street-address": "404 Internet Super Highway",
       "address-level2": "Firefoxity City",
       "address-level1": "CA",
       "postal-code": "00001",
       "country": "US",
       "email": "test@example.com",
       "tel": "+15555551212",
     },
+    selectedStateKey: undefined,
+    successStateChange: {
+      page: {
+        id: "payment-summary",
+        onboardingWizard: undefined,
+      },
+    },
   }, "Check event details for the message to chrome");
   form.remove();
 });
 
 add_task(async function test_genericError() {
   let form = new AddressForm();
   await form.requestStore.setState({
     page: {
--- a/browser/components/payments/test/mochitest/test_basic_card_form.html
+++ b/browser/components/payments/test/mochitest/test_basic_card_form.html
@@ -91,51 +91,56 @@ add_task(async function test_backButton(
 add_task(async function test_saveButton() {
   let form = new BasicCardForm();
   form.dataset.saveButtonLabel = "Save";
   form.dataset.errorGenericSave = "Generic error";
   await form.promiseReady;
   display.appendChild(form);
   await asyncElementRendered();
 
-  ok(form.saveButton.disabled, "Save button should initially be disabled");
   form.form.querySelector("#cc-number").focus();
-  sendString("4111 1111-1111 1111");
+  sendString("4111111111111111");
   form.form.querySelector("#cc-name").focus();
-  // Check .disabled after .focus() so that it's after both "input" and "change" events.
-  ok(form.saveButton.disabled, "Save button should still be disabled without a name");
   sendString("J. Smith");
   form.form.querySelector("#cc-exp-month").focus();
   sendString("11");
   form.form.querySelector("#cc-exp-year").focus();
   let year = (new Date()).getFullYear().toString();
   sendString(year);
-  form.saveButton.focus();
-  ok(!form.saveButton.disabled,
-     "Save button should be enabled since the required fields are filled");
 
   let messagePromise = promiseContentToChromeMessage("updateAutofillRecord");
   is(form.saveButton.textContent, "Save", "Check label");
   synthesizeMouseAtCenter(form.saveButton, {});
 
   let details = await messagePromise;
-  ok(typeof(details.messageID) == "number" && details.messageID > 0, "Check messageID type");
-  delete details.messageID;
   is(details.collectionName, "creditCards", "Check collectionName");
   isDeeply(details, {
     collectionName: "creditCards",
+    errorStateChange: {
+      page: {
+        id: "basic-card-page",
+        error: "Generic error",
+      },
+    },
     guid: undefined,
     messageType: "updateAutofillRecord",
+    preserveOldProperties: true,
     record: {
       "cc-exp-month": "11",
       "cc-exp-year": year,
       "cc-name": "J. Smith",
-      "cc-number": "4111 1111-1111 1111",
+      "cc-number": "4111111111111111",
       "billingAddressGUID": "",
     },
+    selectedStateKey: ["selectedPaymentCard"],
+    successStateChange: {
+      page: {
+        id: "payment-summary",
+      },
+    },
   }, "Check event details for the message to chrome");
   form.remove();
 });
 
 add_task(async function test_genericError() {
   let form = new BasicCardForm();
   await form.requestStore.setState({
     page: {
--- a/browser/extensions/formautofill/FormAutofillStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -322,20 +322,16 @@ class AutofillRecords {
         if (existing.deleted) {
           this._data.splice(index, 1);
         } else {
           throw new Error(`Record ${recordToSave.guid} already exists`);
         }
       }
     } else if (!recordToSave.deleted) {
       this._normalizeRecord(recordToSave);
-      // _normalizeRecord shouldn't do any validation (throw) because in the
-      // `update` case it is called with partial records whereas
-      // `_validateFields` is called with a complete one.
-      this._validateFields(recordToSave);
 
       recordToSave.guid = this._generateGUID();
       recordToSave.version = this.version;
 
       // Metadata
       let now = Date.now();
       recordToSave.timeCreated = now;
       recordToSave.timeLastModified = now;
@@ -437,23 +433,16 @@ class AutofillRecords {
 
       this._maybeStoreLastSyncedField(recordFound, field, oldValue);
     }
 
     if (!hasValidField) {
       throw new Error("Record contains no valid field.");
     }
 
-    // _normalizeRecord above is called with the `record` argument provided to
-    // `update` which may not contain all resulting fields when
-    // `preserveOldProperties` is used. This means we need to validate for
-    // missing fields after we compose the record (`recordFound`) with the stored
-    // record like we do in the loop above.
-    this._validateFields(recordFound);
-
     recordFound.timeLastModified = Date.now();
     let syncMetadata = this._getSyncMetaData(recordFound);
     if (syncMetadata) {
       syncMetadata.changeCounter += 1;
     }
 
     this.computeFields(recordFound);
     this._data[recordFoundIndex] = recordFound;
@@ -1226,38 +1215,18 @@ class AutofillRecords {
   }
 
   // An interface to be inherited.
   _recordReadProcessor(record) {}
 
   // An interface to be inherited.
   computeFields(record) {}
 
-  /**
-  * An interface to be inherited to mutate the argument to normalize it.
-  *
-  * @param {object} partialRecord containing the record passed by the consumer of
-  *                               storage and in the case of `update` with
-  *                               `preserveOldProperties` will only include the
-  *                               properties that the user is changing so the
-  *                               lack of a field doesn't mean that the record
-  *                               won't have that field.
-  */
-  _normalizeFields(partialRecord) {}
-
-  /**
-   * An interface to be inherited to validate that the complete record is
-   * consistent and isn't missing required fields. Overrides should throw for
-   * invalid records.
-   *
-   * @param {object} record containing the complete record that would be stored
-   *                        if this doesn't throw due to an error.
-   * @throws
-   */
-  _validateFields(record) {}
+  // An interface to be inherited.
+  _normalizeFields(record) {}
 
   // An interface to be inherited.
   mergeIfPossible(guid, record, strict) {}
 }
 
 class Addresses extends AutofillRecords {
   constructor(store) {
     super(store, "addresses", VALID_ADDRESS_FIELDS, VALID_ADDRESS_COMPUTED_FIELDS, ADDRESS_SCHEMA_VERSION);
@@ -1605,17 +1574,17 @@ class CreditCards extends AutofillRecord
     delete creditCard["cc-additional-name"];
     delete creditCard["cc-family-name"];
   }
 
   _normalizeCCNumber(creditCard) {
     if (creditCard["cc-number"]) {
       let card = new CreditCard({number: creditCard["cc-number"]});
       creditCard["cc-number"] = card.number;
-      if (!card.isValidNumber()) {
+      if (!creditCard["cc-number"]) {
         delete creditCard["cc-number"];
       }
     }
   }
 
   _normalizeCCExpirationDate(creditCard) {
     let card = new CreditCard({
       expirationMonth: creditCard["cc-exp-month"],
@@ -1630,22 +1599,16 @@ class CreditCards extends AutofillRecord
     if (card.expirationYear) {
       creditCard["cc-exp-year"] = card.expirationYear;
     } else {
       delete creditCard["cc-exp-year"];
     }
     delete creditCard["cc-exp"];
   }
 
-  _validateFields(creditCard) {
-    if (!creditCard["cc-number"]) {
-      throw new Error("Missing/invalid cc-number");
-    }
-  }
-
   /**
    * Normalize the given record and return the first matched guid if storage has the same record.
    * @param {Object} targetCreditCard
    *        The credit card for duplication checking.
    * @returns {string|null}
    *          Return the first guid if storage has the same credit card and null otherwise.
    */
   getDuplicateGuid(targetCreditCard) {
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -216,17 +216,17 @@ this.FormAutofillUtils = {
   },
 
   isCreditCardField(fieldName) {
     return this._fieldNameInfo[fieldName] == "creditCard";
   },
 
   isCCNumber(ccNumber) {
     let card = new CreditCard({number: ccNumber});
-    return card.isValidNumber();
+    return !!card.number;
   },
 
   getCategoryFromFieldName(fieldName) {
     return this._fieldNameInfo[fieldName];
   },
 
   getCategoriesFromFieldNames(fieldNames) {
     let categories = new Set();
--- a/browser/extensions/formautofill/content/autofillEditForms.js
+++ b/browser/extensions/formautofill/content/autofillEditForms.js
@@ -203,17 +203,16 @@ class EditCreditCard extends EditAutofil
    */
   constructor(elements, record, addresses, config) {
     super(elements);
 
     this._addresses = addresses;
     Object.assign(this, config);
     Object.assign(this._elements, {
       ccNumber: this._elements.form.querySelector("#cc-number"),
-      invalidCardNumberStringElement: this._elements.form.querySelector("#invalidCardNumberString"),
       year: this._elements.form.querySelector("#cc-exp-year"),
       billingAddress: this._elements.form.querySelector("#billingAddressGUID"),
       billingAddressRow: this._elements.form.querySelector(".billingAddressRow"),
     });
 
     this.loadRecord(record, addresses);
     this.attachEventListeners();
   }
@@ -233,19 +232,16 @@ class EditCreditCard extends EditAutofil
   generateYears() {
     const count = 11;
     const currentYear = new Date().getFullYear();
     const ccExpYear = this._record && this._record["cc-exp-year"];
 
     // Clear the list
     this._elements.year.textContent = "";
 
-    // Provide an empty year option
-    this._elements.year.appendChild(new Option());
-
     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);
@@ -285,18 +281,17 @@ class EditCreditCard extends EditAutofil
     if (event.target != this._elements.ccNumber) {
       return;
     }
 
     let ccNumberField = this._elements.ccNumber;
 
     // Mark the cc-number field as invalid if the number is empty or invalid.
     if (!this.isCCNumber(ccNumberField.value)) {
-      let invalidCardNumberString = this._elements.invalidCardNumberStringElement.textContent;
-      ccNumberField.setCustomValidity(invalidCardNumberString || " ");
+      ccNumberField.setCustomValidity(true);
     }
   }
 
   handleInput(event) {
     // Clear the error message if cc-number is valid
     if (event.target == this._elements.ccNumber &&
         this.isCCNumber(this._elements.ccNumber.value)) {
       this._elements.ccNumber.setCustomValidity("");
--- a/browser/extensions/formautofill/content/editCreditCard.xhtml
+++ b/browser/extensions/formautofill/content/editCreditCard.xhtml
@@ -15,22 +15,21 @@
   <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"/>
-      <span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
-      <input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
+      <input id="cc-number" type="text"/>
     </label>
     <label>
       <span data-localization="nameOnCard"/>
-      <input id="cc-name" type="text" required="required"/>
+      <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>
--- a/browser/extensions/formautofill/content/editDialog.js
+++ b/browser/extensions/formautofill/content/editDialog.js
@@ -168,17 +168,17 @@ class EditCreditCardDialog extends Autof
   localizeDocument() {
     if (this._record) {
       this._elements.title.dataset.localization = "editCreditCardTitle";
     }
   }
 
   async handleSubmit() {
     let creditCard = this._elements.fieldContainer.buildFormObject();
-    if (!this._elements.fieldContainer._elements.form.reportValidity()) {
+    if (!this._elements.fieldContainer._elements.form.checkValidity()) {
       return;
     }
 
     // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
     // APIs are refactored to be async functions (bug 1399367).
     if (await MasterPassword.ensureLoggedIn()) {
       await this.saveRecord(creditCard, this._record ? this._record.guid : null);
     }
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -131,12 +131,11 @@ cancelBtnLabel = Cancel
 saveBtnLabel = Save
 countryWarningMessage2 = Form Autofill is currently available only for certain countries.
 
 # LOCALIZATION NOTE (addNewCreditCardTitle, editCreditCardTitle): The dialog title for creating or editing
 # credit cards in browser preferences.
 addNewCreditCardTitle = Add New Credit Card
 editCreditCardTitle = Edit Credit Card
 cardNumber = Card Number
-invalidCardNumber = Please enter a valid card number
 nameOnCard = Name on Card
 cardExpires = Expires
 billingAddress = Billing Address
--- a/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_editCreditCardDialog.js
@@ -1,14 +1,12 @@
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 
 "use strict";
 
-let {PromiseTestUtils} = ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", {});
-
 add_task(async function setup() {
   let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
   await formAutofillStorage.initialize();
 });
 
 add_task(async function test_cancelEditCreditCardDialog() {
   await testDialog(EDIT_CREDIT_CARD_DIALOG_URL, (win) => {
     win.document.querySelector("#cancel").click();
@@ -176,19 +174,16 @@ 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("test name", {}, win);
-    PromiseTestUtils.expectUncaughtRejection(/Missing\/invalid cc-number/);
     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);
       win.close();
     }, 500);
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -8,17 +8,16 @@
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
             getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
             getNotification, getDoorhangerButton, removeAllRecords, testDialog */
 
 "use strict";
 
 ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
-ChromeUtils.import("resource://formautofill/MasterPassword.jsm", this);
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
 const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
 const CREDITCARD_FORM_URL =
@@ -340,21 +339,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 = undefined) {
-  if (url == EDIT_CREDIT_CARD_DIALOG_URL && arg) {
-    arg = Object.assign({}, arg, {
-      "cc-number": await MasterPassword.decrypt(arg["cc-number-encrypted"]),
-    });
-  }
   let win = window.openDialog(url, null, "width=600,height=600", arg);
   await waitForFocusAndFormReady(win);
   let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
   await testFn(win);
   return unloadPromise;
 }
 
 registerCleanupFunction(removeAllRecords);
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -241,28 +241,28 @@ const TESTCASES = [
                <input id="cc-number" autocomplete="cc-number">
                <input id="cc-name" autocomplete="cc-name">
                <input id="cc-exp-month" autocomplete="cc-exp-month">
                <input id="cc-exp-year" autocomplete="cc-exp-year">
                </form>`,
     focusedInputId: "cc-number",
     profileData: {
       "guid": "123",
-      "cc-number": "4111111111111111",
+      "cc-number": "1234000056780000",
       "cc-name": "test name",
       "cc-exp-month": "06",
       "cc-exp-year": "25",
     },
     expectedResult: {
       "street-addr": "",
       "city": "",
       "country": "",
       "email": "",
       "tel": "",
-      "cc-number": "4111111111111111",
+      "cc-number": "1234000056780000",
       "cc-name": "test name",
       "cc-exp-month": "06",
       "cc-exp-year": "25",
     },
   },
 
 
 ];
--- a/browser/extensions/formautofill/test/unit/test_createRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_createRecords.js
@@ -230,41 +230,41 @@ const TESTCASES = [
   {
     description: "A credit card form with the value of cc-number, cc-exp, and cc-name.",
     document: `<form>
                 <input id="cc-number" autocomplete="cc-number">
                 <input id="cc-name" autocomplete="cc-name">
                 <input id="cc-exp" autocomplete="cc-exp">
                </form>`,
     formValue: {
-      "cc-number": "5105105105105100",
+      "cc-number": "4444000022220000",
       "cc-name": "Foo Bar",
       "cc-exp": "2022-06",
     },
     expectedRecord: {
       address: [],
       creditCard: [{
-        "cc-number": "5105105105105100",
+        "cc-number": "4444000022220000",
         "cc-name": "Foo Bar",
         "cc-exp": "2022-06",
       }],
     },
   },
   {
     description: "A credit card form with cc-number value only.",
     document: `<form>
                 <input id="cc-number" autocomplete="cc-number">
                </form>`,
     formValue: {
-      "cc-number": "4111111111111111",
+      "cc-number": "4444000022220000",
     },
     expectedRecord: {
       address: [],
       creditCard: [{
-        "cc-number": "4111111111111111",
+        "cc-number": "4444000022220000",
       }],
     },
   },
   {
     description: "A credit card form must have cc-number value.",
     document: `<form>
                 <input id="cc-number" autocomplete="cc-number">
                 <input id="cc-name" autocomplete="cc-name">
@@ -327,20 +327,20 @@ const TESTCASES = [
       "family-name-shipping": "Doe",
       "organization-shipping": "Mozilla",
       "country-shipping": "US",
 
       "given-name-billing": "Foo",
       "organization-billing": "Bar",
       "country-billing": "US",
 
-      "cc-number-section-one": "4111111111111111",
+      "cc-number-section-one": "4444000022220000",
       "cc-name-section-one": "John",
 
-      "cc-number-section-two": "5105105105105100",
+      "cc-number-section-two": "4444000022221111",
       "cc-name-section-two": "Foo Bar",
       "cc-exp-section-two": "2026-26",
     },
     expectedRecord: {
       address: [{
         "given-name": "Bar",
         "organization": "Foo",
         "country": "US",
@@ -350,20 +350,20 @@ const TESTCASES = [
         "organization": "Mozilla",
         "country": "US",
       }, {
         "given-name": "Foo",
         "organization": "Bar",
         "country": "US",
       }],
       creditCard: [{
-        "cc-number": "4111111111111111",
+        "cc-number": "4444000022220000",
         "cc-name": "John",
       }, {
-        "cc-number": "5105105105105100",
+        "cc-number": "4444000022221111",
         "cc-name": "Foo Bar",
         "cc-exp": "2026-26",
       }],
     },
   },
 
 ];
 
--- a/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
+++ b/browser/extensions/formautofill/test/unit/test_onFormSubmitted.js
@@ -77,45 +77,45 @@ const TESTCASES = [
         creditCard: [],
       },
     },
   },
   {
     description: "Trigger credit card saving",
     formValue: {
       "cc-name": "John Doe",
-      "cc-number": "5105105105105100",
+      "cc-number": "1234567812345678",
       "cc-exp-month": 12,
       "cc-exp-year": 2000,
     },
     expectedResult: {
       formSubmission: true,
       records: {
         address: [],
         creditCard: [{
           guid: null,
           record: {
             "cc-name": "John Doe",
-            "cc-number": "5105105105105100",
+            "cc-number": "1234567812345678",
             "cc-exp-month": 12,
             "cc-exp-year": 2000,
           },
           untouchedFields: [],
         }],
       },
     },
   },
   {
     description: "Trigger address and credit card saving",
     formValue: {
       "street-addr": "331 E. Evelyn Avenue",
       "country": "USA",
       "tel": "1-650-903-0800",
       "cc-name": "John Doe",
-      "cc-number": "5105105105105100",
+      "cc-number": "1234567812345678",
       "cc-exp-month": 12,
       "cc-exp-year": 2000,
     },
     expectedResult: {
       formSubmission: true,
       records: {
         address: [{
           guid: null,
@@ -128,17 +128,17 @@ const TESTCASES = [
             "tel": "1-650-903-0800",
           },
           untouchedFields: [],
         }],
         creditCard: [{
           guid: null,
           record: {
             "cc-name": "John Doe",
-            "cc-number": "5105105105105100",
+            "cc-number": "1234567812345678",
             "cc-exp-month": 12,
             "cc-exp-year": 2000,
           },
           untouchedFields: [],
         }],
       },
     },
   },
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -521,21 +521,19 @@ const ADDRESS_NORMALIZE_TESTCASES = [
 ];
 
 const CREDIT_CARD_COMPUTE_TESTCASES = [
   // Name
   {
     description: "Has \"cc-name\"",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-name": "Timothy John Berners-Lee",
-      "cc-number": "************1045",
       "cc-given-name": "Timothy",
       "cc-additional-name": "John",
       "cc-family-name": "Berners-Lee",
     },
   },
 
   // Card Number
   {
@@ -549,76 +547,66 @@ const CREDIT_CARD_COMPUTE_TESTCASES = [
   },
 
   // Expiration Date
   {
     description: "Has \"cc-exp-year\" and \"cc-exp-month\"",
     creditCard: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
       "cc-exp": "2022-12",
-      "cc-number": "************1045",
     },
   },
   {
     description: "Has only \"cc-exp-month\"",
     creditCard: {
       "cc-exp-month": 12,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp": undefined,
-      "cc-number": "************1045",
     },
   },
   {
     description: "Has only \"cc-exp-year\"",
     creditCard: {
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-year": 2022,
       "cc-exp": undefined,
-      "cc-number": "************1045",
     },
   },
 ];
 
 const CREDIT_CARD_NORMALIZE_TESTCASES = [
   // Name
   {
     description: "Has both \"cc-name\" and the split name fields",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
       "cc-given-name": "John",
       "cc-family-name": "Doe",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-name": "Timothy John Berners-Lee",
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has only the split name fields",
     creditCard: {
       "cc-given-name": "John",
       "cc-family-name": "Doe",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-name": "John Doe",
-      "cc-number": "4929001587121045",
     },
   },
 
   // Card Number
   {
     description: "Regular number",
     creditCard: {
       "cc-number": "4929001587121045",
@@ -645,191 +633,161 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = 
       "cc-number": "4111111111111111",
     },
   },
 
   // Expiration Date
   {
     description: "Has \"cc-exp\" formatted \"yyyy-mm\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "2022-12",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yyyy/mm\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "2022/12",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yyyy-m\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "2022-3",
     },
     expectedResult: {
       "cc-exp-month": 3,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yyyy/m\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "2022/3",
     },
     expectedResult: {
       "cc-exp-month": 3,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"mm-yyyy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "12-2022",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"mm/yyyy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "12/2022",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"m-yyyy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "3-2022",
     },
     expectedResult: {
       "cc-exp-month": 3,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"m/yyyy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "3/2022",
     },
     expectedResult: {
       "cc-exp-month": 3,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"mm-yy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "12-22",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"mm/yy\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "12/22",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yy-mm\"",
     creditCard: {
-      "cc-number": "4929001587121045",
       "cc-exp": "22-12",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yy/mm\"",
     creditCard: {
       "cc-exp": "22/12",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"mmyy\"",
     creditCard: {
       "cc-exp": "1222",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" formatted \"yymm\"",
     creditCard: {
       "cc-exp": "2212",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has \"cc-exp\" with spaces",
     creditCard: {
       "cc-exp": "  2033-11  ",
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 11,
       "cc-exp-year": 2033,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has invalid \"cc-exp\"",
     creditCard: {
       "cc-number": "4111111111111111", // Make sure it won't be an empty record.
       "cc-exp": "99-9999",
     },
@@ -839,48 +797,42 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = 
     },
   },
   {
     description: "Has both \"cc-exp-*\" and \"cc-exp\"",
     creditCard: {
       "cc-exp": "2022-12",
       "cc-exp-month": 3,
       "cc-exp-year": 2030,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 3,
       "cc-exp-year": 2030,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has only \"cc-exp-year\" and \"cc-exp\"",
     creditCard: {
       "cc-exp": "2022-12",
       "cc-exp-year": 2030,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
   {
     description: "Has only \"cc-exp-month\" and \"cc-exp\"",
     creditCard: {
       "cc-exp": "2022-12",
       "cc-exp-month": 3,
-      "cc-number": "4929001587121045",
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
-      "cc-number": "4929001587121045",
     },
   },
 ];
 
 let do_check_record_matches = (expectedRecord, record) => {
   for (let key in expectedRecord) {
     Assert.equal(expectedRecord[key], record[key]);
   }