--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -126,18 +126,18 @@ var whitelist = [
{file: "resource://gre/modules/Localization.jsm"},
// Starting from here, files in the whitelist are bugs that need fixing.
// Bug 1339420
{file: "chrome://branding/content/icon128.png"},
// Bug 1339424 (wontfix?)
{file: "chrome://browser/locale/taskbar.properties",
platforms: ["linux", "macosx"]},
- // Bug 1370768 will reference this file
- {file: "chrome://formautofill/content/editCreditCard.xhtml"},
+ // Bug 1370766 will reference this file
+ {file: "chrome://formautofill/content/manageCreditCards.xhtml"},
// Bug 1316187
{file: "chrome://global/content/customizeToolbar.xul"},
// Bug 1343837
{file: "chrome://global/content/findUtils.js"},
// Bug 1343843
{file: "chrome://global/content/url-classifier/unittests.xul"},
// Bug 1348362
{file: "chrome://global/skin/icons/warning-64.png", platforms: ["linux", "win"]},
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -98,16 +98,23 @@ this.FormAutofillUtils = {
return "";
}
return array
.map(s => s ? s.trim() : "")
.filter(s => s)
.join(this.getAddressSeparator());
},
+ fmtMaskedCreditCardLabel(maskedCCNum = "") {
+ return {
+ affix: "****",
+ label: maskedCCNum.replace(/^\**/, ""),
+ };
+ },
+
defineLazyLogGetter(scope, logPrefix) {
XPCOMUtils.defineLazyGetter(scope, "log", () => {
let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
return new ConsoleAPI({
maxLogLevelPref: "extensions.formautofill.loglevel",
prefix: logPrefix,
});
});
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -269,23 +269,16 @@ class AddressResult extends ProfileAutoC
}
}
class CreditCardResult extends ProfileAutoCompleteResult {
constructor(...args) {
super(...args);
}
- _fmtMaskedCreditCardLabel(maskedCCNum = "") {
- return {
- affix: "****",
- label: maskedCCNum.replace(/^\**/, ""),
- };
- }
-
_getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
const GROUP_FIELDS = {
"cc-name": [
"cc-name",
"cc-given-name",
"cc-additional-name",
"cc-family-name",
],
@@ -315,17 +308,17 @@ class CreditCardResult extends ProfileAu
}
let matching = GROUP_FIELDS[currentFieldName] ?
allFieldNames.some(fieldName => GROUP_FIELDS[currentFieldName].includes(fieldName)) :
allFieldNames.includes(currentFieldName);
if (matching) {
if (currentFieldName == "cc-number") {
- let {affix, label} = this._fmtMaskedCreditCardLabel(profile[currentFieldName]);
+ let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(profile[currentFieldName]);
return affix + label;
}
return profile[currentFieldName];
}
}
return ""; // Nothing matched.
}
@@ -343,17 +336,17 @@ class CreditCardResult extends ProfileAu
// Skip results without a primary label.
let labels = profiles.filter(profile => {
return !!profile[focusedFieldName];
}).map(profile => {
let primaryAffix;
let primary = profile[focusedFieldName];
if (focusedFieldName == "cc-number") {
- let {affix, label} = this._fmtMaskedCreditCardLabel(primary);
+ let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(primary);
primaryAffix = affix;
primary = label;
}
return {
primaryAffix,
primary,
secondary: this._getSecondaryLabel(focusedFieldName,
allFieldNames,
--- a/browser/extensions/formautofill/content/manageAddresses.xhtml
+++ b/browser/extensions/formautofill/content/manageAddresses.xhtml
@@ -1,29 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title data-localization="manageDialogTitle"/>
+ <title data-localization="manageAddressesTitle"/>
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
<link rel="stylesheet" href="chrome://formautofill/content/manageDialog.css" />
<script src="chrome://formautofill/content/manageDialog.js"></script>
</head>
<body>
<fieldset>
- <legend data-localization="addressListHeader"/>
+ <legend data-localization="addressesListHeader"/>
<select id="addresses" size="9" multiple="multiple"/>
</fieldset>
<div id="controls-container">
<button id="remove" disabled="disabled" data-localization="remove"/>
<button id="add" data-localization="add"/>
<button id="edit" disabled="disabled" data-localization="edit"/>
</div>
<script type="application/javascript">
"use strict";
- // Localize strings before DOMContentLoaded to prevent flash
- window.dialog.localizeDocument();
+ /* global ManageAddresses */
+ new ManageAddresses({
+ records: document.getElementById("addresses"),
+ controlsContainer: document.getElementById("controls-container"),
+ remove: document.getElementById("remove"),
+ add: document.getElementById("add"),
+ edit: document.getElementById("edit"),
+ });
</script>
</body>
</html>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/manageCreditCards.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title data-localization="manageCreditCardsTitle"/>
+ <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
+ <link rel="stylesheet" href="chrome://formautofill/content/manageDialog.css" />
+ <script src="chrome://formautofill/content/manageDialog.js"></script>
+</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="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"),
+ add: document.getElementById("add"),
+ edit: document.getElementById("edit"),
+ });
+ </script>
+</body>
+</html>
--- a/browser/extensions/formautofill/content/manageDialog.css
+++ b/browser/extensions/formautofill/content/manageDialog.css
@@ -27,25 +27,27 @@ fieldset > legend {
border-radius: 2px 2px 0 0;
-moz-user-select: none;
}
option:nth-child(even) {
background-color: -moz-oddtreerow;
}
-#addresses {
+#addresses,
+#credit-cards {
font-size: 0.85em;
width: 100%;
height: 16.6em;
border-top: none;
border-radius: 0 0 2px 2px;
}
-#addresses > option {
+#addresses > option,
+#credit-cards > option {
padding-inline-start: 0.7em;
}
#controls-container {
flex: 0 1 100%;
justify-content: end;
font-size: 0.9em;
margin-top: 1em;
@@ -53,9 +55,18 @@ option:nth-child(even) {
#remove {
margin-inline-start: 0;
margin-inline-end: auto;
}
#edit {
margin-inline-end: 0;
-}
\ No newline at end of file
+}
+
+#credit-cards > option::before {
+ content: "";
+ background: url("icon-credit-card-generic.svg") no-repeat;
+ float: left;
+ width: 16px;
+ height: 16px;
+ padding-inline-end: 10px;
+}
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -1,161 +1,275 @@
/* 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/. */
+/* exported ManageAddresses, ManageCreditCards */
+
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml";
+const EDIT_CREDIT_CARD_URL = "chrome://formautofill/content/editCreditCard.xhtml";
const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://formautofill/FormAutofillUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "profileStorage",
+ "resource://formautofill/ProfileStorage.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "MasterPassword",
+ "resource://formautofill/MasterPassword.jsm");
+
this.log = null;
FormAutofillUtils.defineLazyLogGetter(this, "manageAddresses");
-function ManageAddressDialog() {
- this.prefWin = window.opener;
- window.addEventListener("DOMContentLoaded", this, {once: true});
-}
-
-ManageAddressDialog.prototype = {
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+class ManageRecords {
+ constructor(subStorageName, elements) {
+ this._storageInitPromise = profileStorage.initialize();
+ this._subStorageName = subStorageName;
+ this._elements = elements;
+ this._records = [];
+ this.prefWin = window.opener;
+ this.localizeDocument();
+ window.addEventListener("DOMContentLoaded", this, {once: true});
+ }
- _elements: {},
+ async init() {
+ await this.loadRecords();
+ this.attachEventListeners();
+ // For testing only: Notify when the dialog is ready for interaction
+ window.dispatchEvent(new CustomEvent("FormReady"));
+ }
- /**
- * Count the number of "formautofill-storage-changed" events epected to
- * receive to prevent repeatedly loading addresses.
- * @type {number}
- */
- _pendingChangeCount: 0,
+ uninit() {
+ log.debug("uninit");
+ this.detachEventListeners();
+ this._elements = null;
+ }
+
+ localizeDocument() {
+ FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
+ }
/**
* Get the selected options on the addresses element.
*
* @returns {array<DOMElement>}
*/
get _selectedOptions() {
- return Array.from(this._elements.addresses.selectedOptions);
- },
-
- init() {
- this._elements = {
- addresses: document.getElementById("addresses"),
- controlsContainer: document.getElementById("controls-container"),
- remove: document.getElementById("remove"),
- add: document.getElementById("add"),
- edit: document.getElementById("edit"),
- };
- this.attachEventListeners();
- },
-
- uninit() {
- log.debug("uninit");
- this.detachEventListeners();
- this._elements = null;
- },
-
- localizeDocument() {
- FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
- },
+ return Array.from(this._elements.records.selectedOptions);
+ }
/**
- * Load addresses and render them.
- *
- * @returns {promise}
+ * Get storage and ensure it has been initialized.
+ * @returns {object}
*/
- loadAddresses() {
- return this.getRecords({collectionName: "addresses"}).then(addresses => {
- log.debug("addresses:", addresses);
- // Sort by last modified time starting with most recent
- addresses.sort((a, b) => b.timeLastModified - a.timeLastModified);
- this.renderAddressElements(addresses);
- this.updateButtonsStates(this._selectedOptions.length);
- });
- },
+ async getStorage() {
+ await this._storageInitPromise;
+ return profileStorage[this._subStorageName];
+ }
/**
- * Get records from storage.
- *
- * @private
- * @param {Object} data
- * Parameters for querying the corresponding result.
- * @param {string} data.collectionName
- * The name used to specify which collection to retrieve records.
- * @param {string} data.searchString
- * The typed string for filtering out the matched records.
- * @param {string} data.info
- * The input autocomplete property's information.
- * @returns {Promise}
- * Promise that resolves when addresses returned from parent process.
+ * Load records and render them.
*/
- getRecords(data) {
- return new Promise(resolve => {
- Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
- resolve(result.data);
- });
- Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", data);
- });
- },
+ async loadRecords() {
+ let storage = await this.getStorage();
+ let records = storage.getAll();
+ // Sort by last modified time starting with most recent
+ records.sort((a, b) => b.timeLastModified - a.timeLastModified);
+ await this.renderRecordElements(records);
+ this.updateButtonsStates(this._selectedOptions.length);
+ // For testing only: Notify when records are loaded
+ this._elements.records.dispatchEvent(new CustomEvent("RecordsLoaded"));
+ }
/**
- * Render the addresses onto the page while maintaining selected options if
+ * Render the records onto the page while maintaining selected options if
* they still exist.
*
- * @param {array<object>} addresses
+ * @param {array<object>} records
*/
- renderAddressElements(addresses) {
+ async renderRecordElements(records) {
let selectedGuids = this._selectedOptions.map(option => option.value);
- this.clearAddressElements();
- for (let address of addresses) {
- let option = new Option(this.getAddressLabel(address),
- address.guid,
+ this.clearRecordElements();
+ for (let record of records) {
+ let option = new Option(await this.getLabel(record),
+ record.guid,
false,
- selectedGuids.includes(address.guid));
- option.address = address;
- this._elements.addresses.appendChild(option);
+ selectedGuids.includes(record.guid));
+ option.record = record;
+ this._elements.records.appendChild(option);
}
- },
+ }
/**
- * Remove all existing address elements.
+ * Remove all existing record elements.
*/
- clearAddressElements() {
- let parent = this._elements.addresses;
+ clearRecordElements() {
+ let parent = this._elements.records;
while (parent.lastChild) {
parent.removeChild(parent.lastChild);
}
- },
+ }
+
+ /**
+ * Remove records by selected options.
+ *
+ * @param {array<DOMElement>} options
+ */
+ async removeRecords(options) {
+ let storage = await this.getStorage();
+ // 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();
+ }
+
+ // 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"));
+ }
+
+ /**
+ * Enable/disable the Edit and Remove buttons based on number of selected
+ * options.
+ *
+ * @param {number} selectedCount
+ */
+ updateButtonsStates(selectedCount) {
+ log.debug("updateButtonsStates:", selectedCount);
+ if (selectedCount == 0) {
+ this._elements.edit.setAttribute("disabled", "disabled");
+ this._elements.remove.setAttribute("disabled", "disabled");
+ } else if (selectedCount == 1) {
+ this._elements.edit.removeAttribute("disabled");
+ this._elements.remove.removeAttribute("disabled");
+ } else if (selectedCount > 1) {
+ this._elements.edit.setAttribute("disabled", "disabled");
+ this._elements.remove.removeAttribute("disabled");
+ }
+ }
/**
- * Remove addresses by guids.
- * Keep track of the number of "formautofill-storage-changed" events to
- * ignore before loading addresses.
+ * Handle events
+ *
+ * @param {DOMEvent} event
+ */
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded": {
+ this.init();
+ break;
+ }
+ case "click": {
+ this.handleClick(event);
+ break;
+ }
+ case "change": {
+ this.updateButtonsStates(this._selectedOptions.length);
+ break;
+ }
+ case "unload": {
+ this.uninit();
+ break;
+ }
+ case "keypress": {
+ this.handleKeyPress(event);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handle click events
+ *
+ * @param {DOMEvent} event
+ */
+ handleClick(event) {
+ if (event.target == this._elements.remove) {
+ this.removeRecords(this._selectedOptions);
+ } else if (event.target == this._elements.add) {
+ this.openEditDialog();
+ } else if (event.target == this._elements.edit ||
+ event.target.parentNode == this._elements.records && event.detail > 1) {
+ this.openEditDialog(this._selectedOptions[0].record);
+ }
+ }
+
+ /**
+ * Handle key press events
*
- * @param {array<string>} guids
+ * @param {DOMEvent} event
+ */
+ handleKeyPress(event) {
+ if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+ window.close();
+ }
+ }
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "formautofill-storage-changed": {
+ this.loadRecords();
+ }
+ }
+ }
+
+ /**
+ * Attach event listener
*/
- removeAddresses(guids) {
- this._pendingChangeCount += guids.length - 1;
- Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
- },
+ attachEventListeners() {
+ window.addEventListener("unload", this, {once: true});
+ window.addEventListener("keypress", this);
+ this._elements.records.addEventListener("change", this);
+ this._elements.records.addEventListener("click", this);
+ this._elements.controlsContainer.addEventListener("click", this);
+ Services.obs.addObserver(this, "formautofill-storage-changed");
+ }
+
+ /**
+ * Remove event listener
+ */
+ detachEventListeners() {
+ window.removeEventListener("keypress", this);
+ this._elements.records.removeEventListener("change", this);
+ this._elements.records.removeEventListener("click", this);
+ this._elements.controlsContainer.removeEventListener("click", this);
+ Services.obs.removeObserver(this, "formautofill-storage-changed");
+ }
+}
+
+class ManageAddresses extends ManageRecords {
+ constructor(elements) {
+ super("addresses", elements);
+ }
+
+ /**
+ * Open the edit address dialog to create/edit an address.
+ *
+ * @param {object} address [optional]
+ */
+ openEditDialog(address) {
+ this.prefWin.gSubDialog.open(EDIT_ADDRESS_URL, null, address);
+ }
/**
* Get address display label. It should display up to two pieces of
* information, separated by a comma.
*
* @param {object} address
* @returns {string}
*/
- getAddressLabel(address) {
+ getLabel(address) {
// TODO: Implement a smarter way for deciding what to display
// as option text. Possibly improve the algorithm in
// ProfileAutoCompleteResult.jsm and reuse it here.
const fieldOrder = [
"name",
"-moz-street-address-one-line", // Street address
"address-level2", // City/Town
"organization", // Company or organization name
@@ -177,134 +291,75 @@ ManageAddressDialog.prototype = {
if (string) {
parts.push(string);
}
if (parts.length == 2) {
break;
}
}
return parts.join(", ");
- },
+ }
+}
- /**
- * Open the edit address dialog to create/edit an address.
- *
- * @param {object} address [optional]
- */
- openEditDialog(address) {
- this.prefWin.gSubDialog.open(EDIT_ADDRESS_URL, null, address);
- },
+class ManageCreditCards extends ManageRecords {
+ constructor(elements) {
+ super("creditCards", elements);
+ this.hasMasterPassword = MasterPassword.isEnabled;
+ if (this.hasMasterPassword) {
+ elements.showCreditCards.setAttribute("hidden", true);
+ }
+ }
/**
- * Enable/disable the Edit and Remove buttons based on number of selected
- * options.
+ * Open the edit address dialog to create/edit a credit card.
*
- * @param {number} selectedCount
- */
- updateButtonsStates(selectedCount) {
- log.debug("updateButtonsStates:", selectedCount);
- if (selectedCount == 0) {
- this._elements.edit.setAttribute("disabled", "disabled");
- this._elements.remove.setAttribute("disabled", "disabled");
- } else if (selectedCount == 1) {
- this._elements.edit.removeAttribute("disabled");
- this._elements.remove.removeAttribute("disabled");
- } else if (selectedCount > 1) {
- this._elements.edit.setAttribute("disabled", "disabled");
- this._elements.remove.removeAttribute("disabled");
- }
- },
-
- /**
- * Handle events
- *
- * @param {DOMEvent} event
+ * @param {object} creditCard [optional]
*/
- handleEvent(event) {
- switch (event.type) {
- case "DOMContentLoaded": {
- this.init();
- this.loadAddresses();
- break;
- }
- case "click": {
- this.handleClick(event);
- break;
- }
- case "change": {
- this.updateButtonsStates(this._selectedOptions.length);
- break;
- }
- case "unload": {
- this.uninit();
- break;
- }
- case "keypress": {
- this.handleKeyPress(event);
- break;
- }
+ 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()) {
+ this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, null, creditCard);
}
- },
+ }
/**
- * Handle click events
+ * 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.
*
- * @param {DOMEvent} event
+ * @param {object} creditCard
+ * @param {boolean} showCreditCards [optional]
+ * @returns {string}
*/
- handleClick(event) {
- if (event.target == this._elements.remove) {
- this.removeAddresses(this._selectedOptions.map(option => option.value));
- } else if (event.target == this._elements.add) {
- this.openEditDialog();
- } else if (event.target == this._elements.edit ||
- event.target.parentNode == this._elements.addresses && event.detail > 1) {
- this.openEditDialog(this._selectedOptions[0].address);
+ async getLabel(creditCard, showCreditCards = false) {
+ let parts = [];
+ if (creditCard["cc-number"]) {
+ let ccLabel;
+ if (showCreditCards) {
+ ccLabel = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
+ } else {
+ let {affix, label} = FormAutofillUtils.fmtMaskedCreditCardLabel(creditCard["cc-number"]);
+ ccLabel = `${affix} ${label}`;
+ }
+ parts.push(ccLabel);
}
- },
-
- /**
- * Handle key press events
- *
- * @param {DOMEvent} event
- */
- handleKeyPress(event) {
- if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
- window.close();
+ if (creditCard["cc-name"]) {
+ parts.push(creditCard["cc-name"]);
}
- },
+ return parts.join(", ");
+ }
- observe(subject, topic, data) {
- switch (topic) {
- case "formautofill-storage-changed": {
- if (this._pendingChangeCount) {
- this._pendingChangeCount -= 1;
- return;
- }
- this.loadAddresses();
- }
+ async decryptOptions(options) {
+ for (let option of options) {
+ option.text = await this.getLabel(option.record, true);
}
- },
+ // For testing only: Notify when credit cards have been decrypted
+ this._elements.records.dispatchEvent(new CustomEvent("OptionsDecrypted"));
+ }
- /**
- * Attach event listener
- */
- attachEventListeners() {
- window.addEventListener("unload", this, {once: true});
- window.addEventListener("keypress", this);
- this._elements.addresses.addEventListener("change", this);
- this._elements.addresses.addEventListener("click", this);
- this._elements.controlsContainer.addEventListener("click", this);
- Services.obs.addObserver(this, "formautofill-storage-changed");
- },
-
- /**
- * Remove event listener
- */
- detachEventListeners() {
- window.removeEventListener("keypress", this);
- this._elements.addresses.removeEventListener("change", this);
- this._elements.addresses.removeEventListener("click", this);
- this._elements.controlsContainer.removeEventListener("click", this);
- Services.obs.removeObserver(this, "formautofill-storage-changed");
- },
-};
-
-window.dialog = new ManageAddressDialog();
+ handleClick(event) {
+ if (event.target == this._elements.showCreditCards) {
+ this.decryptOptions(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
@@ -28,18 +28,21 @@ category.email = email
fieldNameSeparator = ,\u0020
# LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
# text that is displayed for informing users what categories are about to be filled.
# "%S" will be replaced with a list generated from the pre-defined categories.
# The text would be e.g. Also fill company, phone, email
phishingWarningMessage = Also autofills %S
phishingWarningMessage2 = Autofills %S
-manageDialogTitle = Saved Addresses
-addressListHeader = Addresses
+manageAddressesTitle = Saved Addresses
+manageCreditCardsTitle = Saved Credit Cards
+addressesListHeader = Addresses
+creditCardsListHeader = Credit Cards
+showCreditCards = Show 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_manageAddressesDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageAddressesDialog.js
@@ -1,98 +1,88 @@
"use strict";
const TEST_SELECTORS = {
- selAddresses: "#addresses",
+ selRecords: "#addresses",
btnRemove: "#remove",
btnAdd: "#add",
btnEdit: "#edit",
};
const DIALOG_SIZE = "width=600,height=400";
-function waitForRecords() {
- return new Promise(resolve => {
- Services.cpmm.addMessageListener("FormAutofill:Records", function getResult(result) {
- Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
- // Wait for the next tick for elements to get rendered.
- SimpleTest.executeSoon(resolve.bind(null, result.data));
- });
- });
-}
-
add_task(async function test_manageAddressesInitialState() {
await BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_ADDRESSES_DIALOG_URL}, async function(browser) {
await ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
- let selAddresses = content.document.querySelector(args.selAddresses);
+ let selRecords = content.document.querySelector(args.selRecords);
let btnRemove = content.document.querySelector(args.btnRemove);
let btnEdit = content.document.querySelector(args.btnEdit);
let btnAdd = content.document.querySelector(args.btnAdd);
- is(selAddresses.length, 0, "No address");
+ is(selRecords.length, 0, "No address");
is(btnAdd.disabled, false, "Add button enabled");
is(btnRemove.disabled, true, "Remove button disabled");
is(btnEdit.disabled, true, "Edit button disabled");
});
});
});
add_task(async function test_cancelManageAddressDialogWithESC() {
await new Promise(resolve => {
let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL);
- win.addEventListener("load", () => {
+ win.addEventListener("FormReady", () => {
win.addEventListener("unload", () => {
ok(true, "Manage addresses dialog is closed with ESC key");
resolve();
}, {once: true});
EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
}, {once: true});
});
});
add_task(async function test_removingSingleAndMultipleAddresses() {
await saveAddress(TEST_ADDRESS_1);
await saveAddress(TEST_ADDRESS_2);
await saveAddress(TEST_ADDRESS_3);
let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL, null, DIALOG_SIZE);
- await waitForRecords();
+ await BrowserTestUtils.waitForEvent(win, "FormReady");
- let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
- is(selAddresses.length, 3, "Three addresses");
+ is(selRecords.length, 3, "Three addresses");
- EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
is(btnRemove.disabled, false, "Remove button enabled");
is(btnEdit.disabled, false, "Edit button enabled");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
- await waitForRecords();
- is(selAddresses.length, 2, "Two addresses left");
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 2, "Two addresses left");
- EventUtils.synthesizeMouseAtCenter(selAddresses.children[0], {}, win);
- EventUtils.synthesizeMouseAtCenter(selAddresses.children[1],
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+ EventUtils.synthesizeMouseAtCenter(selRecords.children[1],
{shiftKey: true}, win);
is(btnEdit.disabled, true, "Edit button disabled when multi-select");
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
- await waitForRecords();
- is(selAddresses.length, 0, "All addresses are removed");
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
+ is(selRecords.length, 0, "All addresses are removed");
win.close();
});
add_task(async function test_addressesDialogWatchesStorageChanges() {
let win = window.openDialog(MANAGE_ADDRESSES_DIALOG_URL, null, DIALOG_SIZE);
- await waitForRecords();
+ await BrowserTestUtils.waitForEvent(win, "FormReady");
- let selAddresses = win.document.querySelector(TEST_SELECTORS.selAddresses);
+ let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
await saveAddress(TEST_ADDRESS_1);
- let addresses = await waitForRecords();
- is(selAddresses.length, 1, "One address is shown");
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 1, "One address is shown");
- await removeAddresses([addresses[0].guid]);
- await waitForRecords();
- is(selAddresses.length, 0, "Address is removed");
+ await removeAddresses([selRecords.options[0].value]);
+ await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+ is(selRecords.length, 0, "Address is removed");
win.close();
});