Bug 1403881 - Add credit card update doorhanger. r=lchang
MozReview-Commit-ID: BUd70UyLC9c
--- 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();
+});