Bug 1403881 - Add credit card update doorhanger. r=lchang
authorsteveck-chung <schung@mozilla.com>
Tue, 07 Nov 2017 16:50:35 +0800
changeset 444168 78acf48d63c3dacd8885ba93cdf5f1b2efccafe5
parent 444167 a4392df95024d106fb3472b746c96e31224cdee4
child 444169 f64defe0d11edb2a76bb6cca295a7fd864542501
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslchang
bugs1403881
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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();
+});