Bug 1403881 - Add credit card update doorhanger. r=lchang draft
authorsteveck-chung <schung@mozilla.com>
Tue, 07 Nov 2017 16:50:35 +0800
changeset 695331 1dd0307d64dcaeef4cdfb5a362fe0048e1ce9f80
parent 693983 14d71984b75e814fdef13261f1879c02a20f1c19
child 695350 8c8e86236709a384d7665eb08d67d281041a01b0
push id88394
push userbmo:schung@mozilla.com
push dateThu, 09 Nov 2017 02:57:22 +0000
reviewerslchang
bugs1403881
milestone58.0a1
Bug 1403881 - Add credit card update doorhanger. r=lchang MozReview-Commit-ID: BUd70UyLC9c
browser/extensions/formautofill/FormAutofillDoorhanger.jsm
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/locales/en-US/formautofill.properties
browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
--- a/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
+++ b/browser/extensions/formautofill/FormAutofillDoorhanger.jsm
@@ -66,17 +66,17 @@ const CONTENT = {
           let checked = event.target.checked;
           Services.prefs.setBoolPref("services.sync.engine.addresses", checked);
           log.debug("Set addresses sync to", checked);
         },
       },
       hideClose: true,
     },
   },
-  update: {
+  updateAddress: {
     notificationId: "autofill-address",
     message: GetStringFromName("updateAddressMessage"),
     linkMessage: GetStringFromName(autofillOptsKey),
     anchor: {
       id: "autofill-address-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
@@ -91,17 +91,17 @@ const CONTENT = {
       callbackState: "create",
     }],
     options: {
       persistWhileVisible: true,
       popupIconURL: "chrome://formautofill/content/icon-address-update.svg",
       hideClose: true,
     },
   },
-  creditCard: {
+  addCreditCard: {
     notificationId: "autofill-credit-card",
     message: formatStringFromName("saveCreditCardMessage", [brandShortName], 1),
     linkMessage: GetStringFromName(autofillSecurityOptionsKey),
     anchor: {
       id: "autofill-credit-card-notification-icon",
       URL: "chrome://formautofill/content/formfill-anchor.svg",
       tooltiptext: GetStringFromName("openAutofillMessagePanel"),
     },
@@ -144,16 +144,41 @@ const CONTENT = {
           Services.prefs.setBoolPref("services.sync.engine.creditcards", checked);
           secondaryButton.disabled = checked;
           menubutton.disabled = checked;
           log.debug("Set creditCard sync to", checked);
         },
       },
     },
   },
+  updateCreditCard: {
+    notificationId: "autofill-credit-card",
+    message: GetStringFromName("updateCreditCardMessage"),
+    linkMessage: GetStringFromName(autofillOptsKey),
+    anchor: {
+      id: "autofill-credit-card-notification-icon",
+      URL: "chrome://formautofill/content/formfill-anchor.svg",
+      tooltiptext: GetStringFromName("openAutofillMessagePanel"),
+    },
+    mainAction: {
+      label: GetStringFromName("updateCreditCardLabel"),
+      accessKey: "U",
+      callbackState: "update",
+    },
+    secondaryActions: [{
+      label: GetStringFromName("createCreditCardLabel"),
+      accessKey: "C",
+      callbackState: "create",
+    }],
+    options: {
+      persistWhileVisible: true,
+      popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
+      hideClose: true,
+    },
+  },
 };
 
 let FormAutofillDoorhanger = {
   /**
    * Generate the main action and secondary actions from content parameters and
    * promise resolve.
    *
    * @private
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -370,17 +370,17 @@ FormAutofillParent.prototype = {
         if (address.untouchedFields.includes(field) && originalAddress[field]) {
           address.record[field] = originalAddress[field];
         }
       }
 
       if (!this.profileStorage.addresses.mergeIfPossible(address.guid, address.record, true)) {
         this._recordFormFillingTime("address", "autofill-update", timeStartedFillingMS);
 
-        FormAutofillDoorhanger.show(target, "update").then((state) => {
+        FormAutofillDoorhanger.show(target, "updateAddress").then((state) => {
           let changedGUIDs = this.profileStorage.addresses.mergeToStorage(address.record, true);
           switch (state) {
             case "create":
               if (!changedGUIDs.length) {
                 changedGUIDs.push(this.profileStorage.addresses.add(address.record));
               }
               break;
             case "update":
@@ -464,17 +464,17 @@ FormAutofillParent.prototype = {
 
     // Early return if it's a duplicate data
     let dupGuid = this.profileStorage.creditCards.getDuplicateGuid(creditCard.record);
     if (dupGuid) {
       this.profileStorage.creditCards.notifyUsed(dupGuid);
       return;
     }
 
-    let state = await FormAutofillDoorhanger.show(target, "creditCard");
+    let state = await FormAutofillDoorhanger.show(target, creditCard.guid ? "updateCreditCard" : "addCreditCard");
     if (state == "cancel") {
       return;
     }
 
     if (state == "disable") {
       Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
       return;
     }
@@ -482,20 +482,23 @@ FormAutofillParent.prototype = {
     // TODO: "MasterPassword.ensureLoggedIn" can be removed after the storage
     // APIs are refactored to be async functions (bug 1399367).
     if (!await MasterPassword.ensureLoggedIn()) {
       log.warn("User canceled master password entry");
       return;
     }
 
     let changedGUIDs = [];
-    // TODO: Autofill(with guid) case should show update doorhanger with update/create new.
-    // It'll be implemented in bug 1403881 and only avoid mergering for now.
     if (creditCard.guid) {
-      changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+      if (state == "update") {
+        this.profileStorage.creditCards.update(creditCard.guid, creditCard.record, true);
+        changedGUIDs.push(creditCard.guid);
+      } else if ("create") {
+        changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
+      }
     } else {
       changedGUIDs.push(...this.profileStorage.creditCards.mergeToStorage(creditCard.record));
       if (!changedGUIDs.length) {
         changedGUIDs.push(this.profileStorage.creditCards.add(creditCard.record));
       }
     }
     changedGUIDs.forEach(guid => this.profileStorage.creditCards.notifyUsed(guid));
   },
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -30,16 +30,21 @@ createAddressLabel = Create New Address
 updateAddressLabel = Update Address
 # LOCALIZATION NOTE (saveCreditCardMessage, saveCreditCardLabel, cancelCreditCardLabel, neverSaveCreditCardLabel):
 # Used on the doorhanger when users submit payment with credit card.
 # LOCALIZATION NOTE (saveCreditCardMessage): %S is brandShortName.
 saveCreditCardMessage = Would you like %S to save this credit card? (Security code will not be saved)
 saveCreditCardLabel = Save Credit Card
 cancelCreditCardLabel = Don’t Save
 neverSaveCreditCardLabel = Never Save Credit Cards
+# LOCALIZATION NOTE (updateCreditCardMessage, createCreditCardLabel, updateCreditCardLabel): Used on the doorhanger
+# when an credit card change is detected.
+updateCreditCardMessage = Would you like to update your credit card with this new information?
+createCreditCardLabel = Create New Credit Card
+updateCreditCardLabel = Update Credit Card
 # LOCALIZATION NOTE (openAutofillMessagePanel): Tooltip label for Form Autofill doorhanger icon on address bar.
 openAutofillMessagePanel = Open Form Autofill message panel
 
 # LOCALIZATION NOTE (autocompleteFooterOption, autocompleteFooterOptionOSX): Used as a label for the button,
 # displayed at the bottom of the drop down suggestion, to open Form Autofill browser preferences.
 autocompleteFooterOption = Form Autofill Options
 autocompleteFooterOptionOSX = Form Autofill Preferences
 # LOCALIZATION NOTE (autocompleteFooterOptionShort, autocompleteFooterOptionOSXShort): Used as a label for the button,
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -416,8 +416,110 @@ add_task(async function test_submit_manu
     }
   );
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 3", "Verify the name field");
   await removeAllRecords();
 });
+
+add_task(async function test_update_autofill_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await openPopupOn(browser, "form #cc-name");
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.setUserInput("User 1");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 1, "Still 1 credit card");
+  is(creditCards[0]["cc-name"], "User 1", "cc-name field is updated");
+  await removeAllRecords();
+});
+
+add_task(async function test_create_new_autofill_form() {
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await openPopupOn(browser, "form #cc-name");
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.setUserInput("User 1");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(SECONDARY_BUTTON);
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 2, "2 credit cards in storage");
+  is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"],
+     "Original record's cc-name field is unchanged");
+  is(creditCards[1]["cc-name"], "User 1", "cc-name field in the new record");
+  await removeAllRecords();
+});
+
+add_task(async function test_update_duplicate_autofill_form() {
+  await saveCreditCard({
+    "cc-number": "1234123412341234",
+  });
+  await saveCreditCard({
+    "cc-number": "1111222233334444",
+  });
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 2, "2 credit card in storage");
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      await openPopupOn(browser, "form #cc-number");
+      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
+      await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let number = form.querySelector("#cc-number");
+        is(number.value, "1234123412341234", "Should be the first credit card number");
+        // Change number to the second credit card number
+        number.setUserInput("1111222233334444");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await sleep(1000);
+      is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
+    }
+  );
+
+  creditCards = await getCreditCards();
+  is(creditCards.length, 2, "Still 2 credit card");
+  await removeAllRecords();
+});