fune/browser/extensions/formautofill/content/editDialog.js
Scott Wu b3819339c4 Bug 1370764 - (Part 3) Add browser chrome test for adding and editing credit card. r=lchang
MozReview-Commit-ID: Di1GtjknK5E

--HG--
extra : rebase_source : d191ddcbb4aced0447573d88a8af7638dfa1b16e
2017-08-23 17:10:36 +08:00

280 lines
7.5 KiB
JavaScript

/* 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 EditAddress, EditCreditCard */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const AUTOFILL_BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
const REGIONS_BUNDLE_URI = "chrome://global/locale/regionNames.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");
class EditDialog {
constructor(subStorageName, elements, record) {
this._storageInitPromise = profileStorage.initialize();
this._subStorageName = subStorageName;
this._elements = elements;
this._record = record;
this.localizeDocument();
window.addEventListener("DOMContentLoaded", this, {once: true});
}
async init() {
if (this._record) {
await this.loadInitialValues(this._record);
}
this.attachEventListeners();
// For testing only: loadInitialValues for credit card is an async method, and tests
// need to wait until the values have been filled before editing the fields.
window.dispatchEvent(new CustomEvent("FormReady"));
}
uninit() {
this.detachEventListeners();
this._elements = null;
}
/**
* Fill the form with a record object.
* @param {object} record
*/
loadInitialValues(record) {
for (let field in record) {
let input = document.getElementById(field);
if (input) {
input.value = record[field];
}
}
}
/**
* Get inputs from the form.
* @returns {object}
*/
buildFormObject() {
return Array.from(document.forms[0].elements).reduce((obj, input) => {
if (input.value) {
obj[input.id] = input.value;
}
return obj;
}, {});
}
/**
* Get storage and ensure it has been initialized.
* @returns {object}
*/
async getStorage() {
await this._storageInitPromise;
return profileStorage[this._subStorageName];
}
/**
* Asks FormAutofillParent to save or update an record.
* @param {object} record
* @param {string} guid [optional]
*/
async saveRecord(record, guid) {
let storage = await this.getStorage();
if (guid) {
storage.update(guid, record);
} else {
storage.add(record);
}
}
/**
* Handle events
*
* @param {DOMEvent} event
*/
handleEvent(event) {
switch (event.type) {
case "DOMContentLoaded": {
this.init();
break;
}
case "unload": {
this.uninit();
break;
}
case "click": {
this.handleClick(event);
break;
}
case "input": {
this.handleInput(event);
break;
}
case "keypress": {
this.handleKeyPress(event);
break;
}
}
}
/**
* Handle click events
*
* @param {DOMEvent} event
*/
handleClick(event) {
if (event.target == this._elements.cancel) {
window.close();
}
if (event.target == this._elements.save) {
this.handleSubmit();
}
}
/**
* Handle input events
*
* @param {DOMEvent} event
*/
handleInput(event) {
// Toggle disabled attribute on the save button based on
// whether the form is filled or empty.
if (Object.keys(this.buildFormObject()).length == 0) {
this._elements.save.setAttribute("disabled", true);
} else {
this._elements.save.removeAttribute("disabled");
}
}
/**
* Handle key press events
*
* @param {DOMEvent} event
*/
handleKeyPress(event) {
if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
window.close();
}
}
/**
* Attach event listener
*/
attachEventListeners() {
window.addEventListener("keypress", this);
this._elements.controlsContainer.addEventListener("click", this);
document.addEventListener("input", this);
}
/**
* Remove event listener
*/
detachEventListeners() {
window.removeEventListener("keypress", this);
this._elements.controlsContainer.removeEventListener("click", this);
document.removeEventListener("input", this);
}
}
class EditAddress extends EditDialog {
constructor(elements, record) {
super("addresses", elements, record);
this.formatForm(record && record.country);
}
/**
* Format the form based on country. The address-level1 and postal-code labels
* should be specific to the given country.
* @param {string} country
*/
formatForm(country) {
// TODO: Use fmt to show/hide and order fields (Bug 1383687)
const {addressLevel1Label, postalCodeLabel} = FormAutofillUtils.getFormFormat(country);
this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
}
localizeDocument() {
if (this._record) {
this._elements.title.dataset.localization = "editAddressTitle";
}
FormAutofillUtils.localizeMarkup(REGIONS_BUNDLE_URI, this._elements.country);
}
async handleSubmit() {
await this.saveRecord(this.buildFormObject(), this._record ? this._record.guid : null);
window.close();
}
}
class EditCreditCard extends EditDialog {
constructor(elements, record) {
super("creditCards", elements, record);
this.generateYears();
}
generateYears() {
const count = 11;
const currentYear = new Date().getFullYear();
const ccExpYear = this._record && this._record["cc-exp-year"];
if (ccExpYear && ccExpYear < currentYear) {
this._elements.year.appendChild(new Option(ccExpYear));
}
for (let i = 0; i < count; i++) {
let year = currentYear + i;
let option = new Option(year);
this._elements.year.appendChild(option);
}
if (ccExpYear && ccExpYear > currentYear + count) {
this._elements.year.appendChild(new Option(ccExpYear));
}
}
localizeDocument() {
if (this._record) {
this._elements.title.dataset.localization = "editCreditCardTitle";
}
FormAutofillUtils.localizeMarkup(AUTOFILL_BUNDLE_URI, document);
}
/**
* Decrypt cc-number first and fill the form.
* @param {object} creditCard
*/
async loadInitialValues(creditCard) {
let decryptedCC = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
super.loadInitialValues(Object.assign({}, creditCard, {"cc-number": decryptedCC}));
}
async handleSubmit() {
let creditCard = this.buildFormObject();
// Show error on the cc-number field if it's empty or invalid
if (!FormAutofillUtils.isCCNumber(creditCard["cc-number"])) {
this._elements.ccNumber.setCustomValidity(true);
return;
}
let storage = await this.getStorage();
await storage.normalizeCCNumberFields(creditCard);
await this.saveRecord(creditCard, this._record ? this._record.guid : null);
window.close();
}
handleInput(event) {
// Clear the error message if cc-number is valid
if (event.target == this._elements.ccNumber &&
FormAutofillUtils.isCCNumber(this._elements.ccNumber.value)) {
this._elements.ccNumber.setCustomValidity("");
}
super.handleInput(event);
}
}