Bug 1397090 - Add the ability to mask credit card numbers after they have been unmasked. r=lchang
MozReview-Commit-ID: GIjhhwgsl5b
--- a/browser/extensions/formautofill/content/manageCreditCards.xhtml
+++ b/browser/extensions/formautofill/content/manageCreditCards.xhtml
@@ -12,26 +12,26 @@
</head>
<body>
<fieldset>
<legend data-localization="creditCardsListHeader"/>
<select id="credit-cards" size="9" multiple="multiple"/>
</fieldset>
<div id="controls-container">
<button id="remove" disabled="disabled" data-localization="remove"/>
- <button id="show-credit-cards" data-localization="showCreditCards"/>
+ <button id="show-hide-credit-cards" data-localization="showCreditCards"/>
<button id="add" data-localization="add"/>
<button id="edit" disabled="disabled" data-localization="edit"/>
</div>
<script type="application/javascript">
"use strict";
/* global ManageCreditCards */
new ManageCreditCards({
records: document.getElementById("credit-cards"),
controlsContainer: document.getElementById("controls-container"),
remove: document.getElementById("remove"),
- showCreditCards: document.getElementById("show-credit-cards"),
+ showHideCreditCards: document.getElementById("show-hide-credit-cards"),
add: document.getElementById("add"),
edit: document.getElementById("edit"),
});
</script>
</body>
</html>
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -23,17 +23,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
class ManageRecords {
constructor(subStorageName, elements) {
this._storageInitPromise = profileStorage.initialize();
this._subStorageName = subStorageName;
this._elements = elements;
- this._records = [];
this._newRequest = false;
this._isLoadingRecords = false;
this.prefWin = window.opener;
this.localizeDocument();
window.addEventListener("DOMContentLoaded", this, {once: true});
}
async init() {
@@ -147,16 +146,17 @@ class ManageRecords {
// Pause listening to storage change event to avoid triggering `loadRecords`
// when removing records
Services.obs.removeObserver(this, "formautofill-storage-changed");
for (let option of options) {
storage.remove(option.value);
option.remove();
}
+ this.updateButtonsStates(this._selectedOptions);
// Resume listening to storage change event
Services.obs.addObserver(this, "formautofill-storage-changed");
// For testing only: notify record(s) has been removed
this._elements.records.dispatchEvent(new CustomEvent("RecordsRemoved"));
}
/**
@@ -322,31 +322,32 @@ class ManageAddresses extends ManageReco
}
return parts.join(", ");
}
}
class ManageCreditCards extends ManageRecords {
constructor(elements) {
super("creditCards", elements);
- this.hasMasterPassword = MasterPassword.isEnabled;
- if (this.hasMasterPassword) {
- elements.showCreditCards.setAttribute("hidden", true);
+ this._hasMasterPassword = MasterPassword.isEnabled;
+ this._isDecrypted = false;
+ if (this._hasMasterPassword) {
+ elements.showHideCreditCards.setAttribute("hidden", true);
}
}
/**
* Open the edit address dialog to create/edit a credit card.
*
* @param {object} creditCard [optional]
*/
async openEditDialog(creditCard) {
// If master password is set, ask for password if user is trying to edit an
// existing credit card.
- if (!this.hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
+ if (!this._hasMasterPassword || !creditCard || await MasterPassword.prompt()) {
this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, null, creditCard);
}
}
/**
* Get credit card display label. It should display masked numbers and the
* cardholder's name, separated by a comma. If `showCreditCards` is set to
* true, decrypted credit card numbers are shown instead.
@@ -368,23 +369,51 @@ class ManageCreditCards extends ManageRe
parts.push(ccLabel);
}
if (creditCard["cc-name"]) {
parts.push(creditCard["cc-name"]);
}
return parts.join(", ");
}
- async decryptOptions(options) {
+ async toggleShowHideCards(options) {
+ this._isDecrypted = !this._isDecrypted;
+ this.updateShowHideButtonState();
+ await this.updateLabels(options, this._isDecrypted);
+ }
+
+ async updateLabels(options, isDecrypted) {
for (let option of options) {
- option.text = await this.getLabel(option.record, true);
+ option.text = await this.getLabel(option.record, isDecrypted);
}
- // For testing only: Notify when credit cards have been decrypted
- this._elements.records.dispatchEvent(new CustomEvent("OptionsDecrypted"));
+ // For testing only: Notify when credit cards labels have been updated
+ this._elements.records.dispatchEvent(new CustomEvent("LabelsUpdated"));
+ }
+
+ async renderRecordElements(records) {
+ // Revert back to encrypted form when re-rendering happens
+ this._isDecrypted = false;
+ await super.renderRecordElements(records);
+ }
+
+ updateButtonsStates(selectedCount) {
+ this.updateShowHideButtonState();
+ super.updateButtonsStates(selectedCount);
+ }
+
+ updateShowHideButtonState() {
+ if (this._elements.records.length) {
+ this._elements.showHideCreditCards.removeAttribute("disabled");
+ } else {
+ this._elements.showHideCreditCards.setAttribute("disabled", true);
+ }
+ this._elements.showHideCreditCards.textContent =
+ this._isDecrypted ? FormAutofillUtils.stringBundle.GetStringFromName("hideCreditCards") :
+ FormAutofillUtils.stringBundle.GetStringFromName("showCreditCards");
}
handleClick(event) {
- if (event.target == this._elements.showCreditCards) {
- this.decryptOptions(this._elements.records.options);
+ if (event.target == this._elements.showHideCreditCards) {
+ this.toggleShowHideCards(this._elements.records.options);
}
super.handleClick(event);
}
}
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -41,16 +41,17 @@ fieldNameSeparator = ,\u0020
phishingWarningMessage = Also autofills %S
phishingWarningMessage2 = Autofills %S
manageAddressesTitle = Saved Addresses
manageCreditCardsTitle = Saved Credit Cards
addressesListHeader = Addresses
creditCardsListHeader = Credit Cards
showCreditCards = Show Credit Cards
+hideCreditCards = Hide Credit Cards
remove = Remove
add = Add…
edit = Edit…
addNewAddressTitle = Add New Address
editAddressTitle = Edit Address
givenName = First Name
additionalName = Middle Name
--- a/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
@@ -1,34 +1,34 @@
"use strict";
Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
const TEST_SELECTORS = {
selRecords: "#credit-cards",
btnRemove: "#remove",
- btnShowCreditCards: "#show-credit-cards",
+ btnShowHideCreditCards: "#show-hide-credit-cards",
btnAdd: "#add",
btnEdit: "#edit",
};
const DIALOG_SIZE = "width=600,height=400";
add_task(async function test_manageCreditCardsInitialState() {
await BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_CREDIT_CARDS_DIALOG_URL}, async function(browser) {
await ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
let selRecords = content.document.querySelector(args.selRecords);
let btnRemove = content.document.querySelector(args.btnRemove);
- let btnShowCreditCards = content.document.querySelector(args.btnShowCreditCards);
+ let btnShowHideCreditCards = content.document.querySelector(args.btnShowHideCreditCards);
let btnAdd = content.document.querySelector(args.btnAdd);
let btnEdit = content.document.querySelector(args.btnEdit);
is(selRecords.length, 0, "No credit card");
is(btnRemove.disabled, true, "Remove button disabled");
- is(btnShowCreditCards.disabled, false, "Show Credit Cards button disabled");
+ is(btnShowHideCreditCards.disabled, true, "Show Credit Cards button disabled");
is(btnAdd.disabled, false, "Add button enabled");
is(btnEdit.disabled, true, "Edit button disabled");
});
});
});
add_task(async function test_cancelManageCreditCardsDialogWithESC() {
await new Promise(resolve => {
@@ -99,46 +99,72 @@ add_task(async function test_showCreditC
await saveCreditCard(TEST_CREDIT_CARD_1);
await saveCreditCard(TEST_CREDIT_CARD_2);
await saveCreditCard(TEST_CREDIT_CARD_3);
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
await BrowserTestUtils.waitForEvent(win, "FormReady");
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
- let btnShowCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowCreditCards);
+ let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
- EventUtils.synthesizeMouseAtCenter(btnShowCreditCards, {}, win);
- await BrowserTestUtils.waitForEvent(selRecords, "OptionsDecrypted");
+ is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
+ is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
+ // Show credit card numbers
+ EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
is(selRecords[1].text, "1111222233334444, Timothy Berners-Lee", "Decrypted credit card 2");
is(selRecords[2].text, "1234567812345678, John Doe", "Decrypted credit card 1");
+ is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
+ // Hide credit card numbers
+ EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+ is(selRecords[0].text, "**** 6666", "Masked credit card 3");
+ is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
+ is(selRecords[2].text, "**** 5678, John Doe", "Masked credit card 1");
+ is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
+
+ // Show credit card numbers again to test if they revert back to masked form when reloaded
+ EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+ await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+ // Ensure credit card numbers are shown again
+ is(selRecords[0].text, "9999888877776666", "Decrypted credit card 3");
+ // Remove a card to trigger reloading
await removeCreditCards([selRecords.options[2].value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords[0].text, "**** 6666", "Masked credit card 3");
+ is(selRecords[1].text, "**** 4444, Timothy Berners-Lee", "Masked credit card 2");
+
+ // Remove the rest of the cards
await removeCreditCards([selRecords.options[1].value]);
await removeCreditCards([selRecords.options[0].value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
+
win.close();
});
add_task(async function test_hasMasterPassword() {
await saveCreditCard(TEST_CREDIT_CARD_1);
LoginTestUtils.masterPassword.enable();
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
await BrowserTestUtils.waitForEvent(win, "FormReady");
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
- let btnShowCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowCreditCards);
+ let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
let masterPasswordDialogShown = waitForMasterPasswordDialog();
- is(btnShowCreditCards.hidden, true, "Show credit cards button is hidden");
+ is(btnShowHideCreditCards.hidden, true, "Show credit cards button is hidden");
// Master password dialog should show when trying to edit a credit card record.
EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
await masterPasswordDialogShown;
// Master password is not required for removing credit cards.
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);