forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			754 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			754 lines
		
	
	
	
		
			24 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/. */
 | |
| 
 | |
| /**
 | |
|  * Runs in the privileged outer dialog. Each dialog loads this script in its
 | |
|  * own scope.
 | |
|  */
 | |
| 
 | |
| /* exported paymentDialogWrapper */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
 | |
|                      .getService(Ci.nsIPaymentRequestService);
 | |
| 
 | |
| const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
 | |
|                      .getService(Ci.nsIPaymentUIService);
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/Services.jsm");
 | |
| ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
 | |
|                                "resource:///modules/BrowserWindowTracker.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "OSKeyStore",
 | |
|                                "resource://formautofill/OSKeyStore.jsm");
 | |
| ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
 | |
|                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(this, "formAutofillStorage", () => {
 | |
|   let storage;
 | |
|   try {
 | |
|     storage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
 | |
|                          .formAutofillStorage;
 | |
|     storage.initialize();
 | |
|   } catch (ex) {
 | |
|     storage = null;
 | |
|     Cu.reportError(ex);
 | |
|   }
 | |
| 
 | |
|   return storage;
 | |
| });
 | |
| 
 | |
| /**
 | |
|  * Temporary/transient storage for address and credit card records
 | |
|  *
 | |
|  * Implements a subset of the FormAutofillStorage collection class interface, and delegates to
 | |
|  * those classes for some utility methods
 | |
|  */
 | |
| class TempCollection {
 | |
|   constructor(type, data = {}) {
 | |
|     /**
 | |
|      * The name of the collection. e.g. 'addresses' or 'creditCards'
 | |
|      * Used to access methods from the FormAutofillStorage collections
 | |
|      */
 | |
|     this._type = type;
 | |
|     this._data = data;
 | |
|   }
 | |
| 
 | |
|   get _formAutofillCollection() {
 | |
|     // lazy getter for the formAutofill collection - to resolve on first access
 | |
|     Object.defineProperty(this, "_formAutofillCollection", {
 | |
|       value: formAutofillStorage[this._type], writable: false, configurable: true,
 | |
|     });
 | |
|     return this._formAutofillCollection;
 | |
|   }
 | |
| 
 | |
|   get(guid) {
 | |
|     return this._data[guid];
 | |
|   }
 | |
| 
 | |
|   async update(guid, record, preserveOldProperties) {
 | |
|     let recordToSave = Object.assign(preserveOldProperties ? this._data[guid] : {}, record);
 | |
|     await this._formAutofillCollection.computeFields(recordToSave);
 | |
|     return (this._data[guid] = recordToSave);
 | |
|   }
 | |
| 
 | |
|   async add(record) {
 | |
|     let guid = "temp-" + Math.abs(Math.random() * 0xffffffff|0);
 | |
|     let timeLastModified = Date.now();
 | |
|     let recordToSave = Object.assign({guid, timeLastModified}, record);
 | |
|     await this._formAutofillCollection.computeFields(recordToSave);
 | |
|     this._data[guid] = recordToSave;
 | |
|     return guid;
 | |
|   }
 | |
| 
 | |
|   getAll() {
 | |
|     return this._data;
 | |
|   }
 | |
| }
 | |
| 
 | |
| var paymentDialogWrapper = {
 | |
|   componentsLoaded: new Map(),
 | |
|   frameWeakRef: null,
 | |
|   mm: null,
 | |
|   request: null,
 | |
|   temporaryStore: null,
 | |
| 
 | |
|   QueryInterface: ChromeUtils.generateQI([
 | |
|     Ci.nsIObserver,
 | |
|     Ci.nsISupportsWeakReference,
 | |
|   ]),
 | |
| 
 | |
|   /**
 | |
|    * @param {string} guid
 | |
|    * @returns {object} containing only the requested payer values.
 | |
|    */
 | |
|   async _convertProfileAddressToPayerData(guid) {
 | |
|     let addressData = this.temporaryStore.addresses.get(guid) ||
 | |
|                       await formAutofillStorage.addresses.get(guid);
 | |
|     if (!addressData) {
 | |
|       throw new Error(`Payer address not found: ${guid}`);
 | |
|     }
 | |
| 
 | |
|     let {
 | |
|       requestPayerName,
 | |
|       requestPayerEmail,
 | |
|       requestPayerPhone,
 | |
|     } = this.request.paymentOptions;
 | |
| 
 | |
|     let payerData = {
 | |
|       payerName: requestPayerName ? addressData.name : "",
 | |
|       payerEmail: requestPayerEmail ? addressData.email : "",
 | |
|       payerPhone: requestPayerPhone ? addressData.tel : "",
 | |
|     };
 | |
| 
 | |
|     return payerData;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @param {string} guid
 | |
|    * @returns {nsIPaymentAddress}
 | |
|    */
 | |
|   async _convertProfileAddressToPaymentAddress(guid) {
 | |
|     let addressData = this.temporaryStore.addresses.get(guid) ||
 | |
|                       await formAutofillStorage.addresses.get(guid);
 | |
|     if (!addressData) {
 | |
|       throw new Error(`Address not found: ${guid}`);
 | |
|     }
 | |
| 
 | |
|     let address = this.createPaymentAddress({
 | |
|       country: addressData.country,
 | |
|       addressLines: addressData["street-address"].split("\n"),
 | |
|       region: addressData["address-level1"],
 | |
|       city: addressData["address-level2"],
 | |
|       dependentLocality: addressData["address-level3"],
 | |
|       postalCode: addressData["postal-code"],
 | |
|       organization: addressData.organization,
 | |
|       recipient: addressData.name,
 | |
|       phone: addressData.tel,
 | |
|     });
 | |
| 
 | |
|     return address;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @param {string} guid The GUID of the basic card record from storage.
 | |
|    * @param {string} cardSecurityCode The associated card security code (CVV/CCV/etc.)
 | |
|    * @throws If there is an error decrypting
 | |
|    * @returns {nsIBasicCardResponseData?} returns response data or null (if the
 | |
|    *                                      master password dialog was cancelled);
 | |
|    */
 | |
|   async _convertProfileBasicCardToPaymentMethodData(guid, cardSecurityCode) {
 | |
|     let cardData = this.temporaryStore.creditCards.get(guid) ||
 | |
|                    await formAutofillStorage.creditCards.get(guid);
 | |
|     if (!cardData) {
 | |
|       throw new Error(`Basic card not found in storage: ${guid}`);
 | |
|     }
 | |
| 
 | |
|     let cardNumber;
 | |
|     try {
 | |
|       cardNumber = await OSKeyStore.decrypt(cardData["cc-number-encrypted"], true);
 | |
|     } catch (ex) {
 | |
|       if (ex.result != Cr.NS_ERROR_ABORT) {
 | |
|         throw ex;
 | |
|       }
 | |
|       // User canceled master password entry
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     let billingAddressGUID = cardData.billingAddressGUID;
 | |
|     let billingAddress;
 | |
|     try {
 | |
|       billingAddress = await this._convertProfileAddressToPaymentAddress(billingAddressGUID);
 | |
|     } catch (ex) {
 | |
|       // The referenced address may not exist if it was deleted or hasn't yet synced to this profile
 | |
|       Cu.reportError(ex);
 | |
|     }
 | |
|     let methodData = this.createBasicCardResponseData({
 | |
|       cardholderName: cardData["cc-name"],
 | |
|       cardNumber,
 | |
|       expiryMonth: cardData["cc-exp-month"].toString().padStart(2, "0"),
 | |
|       expiryYear: cardData["cc-exp-year"].toString(),
 | |
|       cardSecurityCode,
 | |
|       billingAddress,
 | |
|     });
 | |
| 
 | |
|     return methodData;
 | |
|   },
 | |
| 
 | |
|   init(requestId, frame) {
 | |
|     if (!requestId || typeof(requestId) != "string") {
 | |
|       throw new Error("Invalid PaymentRequest ID");
 | |
|     }
 | |
| 
 | |
|     // The Request object returned by the Payment Service is live and
 | |
|     // will automatically get updated if event.updateWith is used.
 | |
|     this.request = paymentSrv.getPaymentRequestById(requestId);
 | |
| 
 | |
|     if (!this.request) {
 | |
|       throw new Error(`PaymentRequest not found: ${requestId}`);
 | |
|     }
 | |
| 
 | |
|     this._attachToFrame(frame);
 | |
|     this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
 | |
|     // Until we have bug 1446164 and bug 1407418 we use form autofill's temporary
 | |
|     // shim for data-localization* attributes.
 | |
|     this.mm.loadFrameScript("chrome://formautofill/content/l10n.js", true);
 | |
|     frame.setAttribute("src", "resource://payments/paymentRequest.xhtml");
 | |
| 
 | |
|     this.temporaryStore = {
 | |
|       addresses: new TempCollection("addresses"),
 | |
|       creditCards: new TempCollection("creditCards"),
 | |
|     };
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     try {
 | |
|       Services.obs.removeObserver(this, "message-manager-close");
 | |
|       Services.obs.removeObserver(this, "formautofill-storage-changed");
 | |
|     } catch (ex) {
 | |
|       // Observers may not have been added yet
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Code here will be re-run at various times, e.g. initial show and
 | |
|    * when a tab is detached to a different window.
 | |
|    *
 | |
|    * Code that should only run once belongs in `init`.
 | |
|    * Code to only run upon detaching should be in `changeAttachedFrame`.
 | |
|    *
 | |
|    * @param {Element} frame
 | |
|    */
 | |
|   _attachToFrame(frame) {
 | |
|     this.frameWeakRef = Cu.getWeakReference(frame);
 | |
|     this.mm = frame.frameLoader.messageManager;
 | |
|     this.mm.addMessageListener("paymentContentToChrome", this);
 | |
|     Services.obs.addObserver(this, "message-manager-close", true);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Called only when a frame is changed from one to another.
 | |
|    *
 | |
|    * @param {Element} frame
 | |
|    */
 | |
|   changeAttachedFrame(frame) {
 | |
|     this.mm.removeMessageListener("paymentContentToChrome", this);
 | |
|     this._attachToFrame(frame);
 | |
|     // This isn't in `attachToFrame` because we only want to do it once we've sent records.
 | |
|     Services.obs.addObserver(this, "formautofill-storage-changed", true);
 | |
|   },
 | |
| 
 | |
|   createShowResponse({
 | |
|     acceptStatus,
 | |
|     methodName = "",
 | |
|     methodData = null,
 | |
|     payerName = "",
 | |
|     payerEmail = "",
 | |
|     payerPhone = "",
 | |
|   }) {
 | |
|     let showResponse = this.createComponentInstance(Ci.nsIPaymentShowActionResponse);
 | |
| 
 | |
|     showResponse.init(this.request.requestId,
 | |
|                       acceptStatus,
 | |
|                       methodName,
 | |
|                       methodData,
 | |
|                       payerName,
 | |
|                       payerEmail,
 | |
|                       payerPhone);
 | |
|     return showResponse;
 | |
|   },
 | |
| 
 | |
|   createBasicCardResponseData({
 | |
|     cardholderName = "",
 | |
|     cardNumber,
 | |
|     expiryMonth = "",
 | |
|     expiryYear = "",
 | |
|     cardSecurityCode = "",
 | |
|     billingAddress = null,
 | |
|   }) {
 | |
|     const basicCardResponseData = Cc["@mozilla.org/dom/payments/basiccard-response-data;1"]
 | |
|                                   .createInstance(Ci.nsIBasicCardResponseData);
 | |
|     basicCardResponseData.initData(cardholderName,
 | |
|                                    cardNumber,
 | |
|                                    expiryMonth,
 | |
|                                    expiryYear,
 | |
|                                    cardSecurityCode,
 | |
|                                    billingAddress);
 | |
|     return basicCardResponseData;
 | |
|   },
 | |
| 
 | |
|   createPaymentAddress({
 | |
|     country = "",
 | |
|     addressLines = [],
 | |
|     region = "",
 | |
|     regionCode = "",
 | |
|     city = "",
 | |
|     dependentLocality = "",
 | |
|     postalCode = "",
 | |
|     sortingCode = "",
 | |
|     organization = "",
 | |
|     recipient = "",
 | |
|     phone = "",
 | |
|   }) {
 | |
|     const paymentAddress = Cc["@mozilla.org/dom/payments/payment-address;1"]
 | |
|                            .createInstance(Ci.nsIPaymentAddress);
 | |
|     const addressLine = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
 | |
|     for (let line of addressLines) {
 | |
|       const address = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
 | |
|       address.data = line;
 | |
|       addressLine.appendElement(address);
 | |
|     }
 | |
|     paymentAddress.init(country,
 | |
|                         addressLine,
 | |
|                         region,
 | |
|                         regionCode,
 | |
|                         city,
 | |
|                         dependentLocality,
 | |
|                         postalCode,
 | |
|                         sortingCode,
 | |
|                         organization,
 | |
|                         recipient,
 | |
|                         phone);
 | |
|     return paymentAddress;
 | |
|   },
 | |
| 
 | |
|   createComponentInstance(componentInterface) {
 | |
|     let componentName;
 | |
|     switch (componentInterface) {
 | |
|       case Ci.nsIPaymentShowActionResponse: {
 | |
|         componentName = "@mozilla.org/dom/payments/payment-show-action-response;1";
 | |
|         break;
 | |
|       }
 | |
|       case Ci.nsIGeneralResponseData: {
 | |
|         componentName = "@mozilla.org/dom/payments/general-response-data;1";
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     let component = this.componentsLoaded.get(componentName);
 | |
| 
 | |
|     if (!component) {
 | |
|       component = Cc[componentName];
 | |
|       this.componentsLoaded.set(componentName, component);
 | |
|     }
 | |
| 
 | |
|     return component.createInstance(componentInterface);
 | |
|   },
 | |
| 
 | |
|   async fetchSavedAddresses() {
 | |
|     let savedAddresses = {};
 | |
|     for (let address of await formAutofillStorage.addresses.getAll()) {
 | |
|       savedAddresses[address.guid] = address;
 | |
|     }
 | |
|     return savedAddresses;
 | |
|   },
 | |
| 
 | |
|   async fetchSavedPaymentCards() {
 | |
|     let savedBasicCards = {};
 | |
|     for (let card of await formAutofillStorage.creditCards.getAll()) {
 | |
|       savedBasicCards[card.guid] = card;
 | |
|       // Filter out the encrypted card number since the dialog content is
 | |
|       // considered untrusted and runs in a content process.
 | |
|       delete card["cc-number-encrypted"];
 | |
| 
 | |
|       // ensure each card has a methodName property
 | |
|       if (!card.methodName) {
 | |
|         card.methodName = "basic-card";
 | |
|       }
 | |
|     }
 | |
|     return savedBasicCards;
 | |
|   },
 | |
| 
 | |
|   async onAutofillStorageChange() {
 | |
|     let [savedAddresses, savedBasicCards] =
 | |
|       await Promise.all([this.fetchSavedAddresses(), this.fetchSavedPaymentCards()]);
 | |
| 
 | |
|     this.sendMessageToContent("updateState", {
 | |
|       savedAddresses,
 | |
|       savedBasicCards,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   sendMessageToContent(messageType, data = {}) {
 | |
|     this.mm.sendAsyncMessage("paymentChromeToContent", {
 | |
|       data,
 | |
|       messageType,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   updateRequest() {
 | |
|     // There is no need to update this.request since the object is live
 | |
|     // and will automatically get updated if event.updateWith is used.
 | |
|     let requestSerialized = this._serializeRequest(this.request);
 | |
| 
 | |
|     this.sendMessageToContent("updateState", {
 | |
|       request: requestSerialized,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Recursively convert and filter input to the subset of data types supported by JSON
 | |
|    *
 | |
|    * @param {*} value - any type of input to serialize
 | |
|    * @param {string?} name - name or key associated with this input.
 | |
|    *                         E.g. property name or array index.
 | |
|    * @returns {*} serialized deep copy of the value
 | |
|    */
 | |
|   _serializeRequest(value, name = null) {
 | |
|     // Primitives: String, Number, Boolean, null
 | |
|     let type = typeof value;
 | |
|     if (value === null ||
 | |
|         type == "string" ||
 | |
|         type == "number" ||
 | |
|         type == "boolean") {
 | |
|       return value;
 | |
|     }
 | |
|     if (name == "topLevelPrincipal") {
 | |
|       // Manually serialize the nsIPrincipal.
 | |
|       let displayHost = value.URI.displayHost;
 | |
|       return {
 | |
|         URI: {
 | |
|           displayHost,
 | |
|         },
 | |
|       };
 | |
|     }
 | |
|     if (type == "function" || type == "undefined") {
 | |
|       return undefined;
 | |
|     }
 | |
|     // Structures: nsIArray
 | |
|     if (value instanceof Ci.nsIArray) {
 | |
|       let iface;
 | |
|       let items = [];
 | |
|       switch (name) {
 | |
|         case "displayItems": // falls through
 | |
|         case "additionalDisplayItems":
 | |
|           iface = Ci.nsIPaymentItem;
 | |
|           break;
 | |
|         case "shippingOptions":
 | |
|           iface = Ci.nsIPaymentShippingOption;
 | |
|           break;
 | |
|         case "paymentMethods":
 | |
|           iface = Ci.nsIPaymentMethodData;
 | |
|           break;
 | |
|         case "modifiers":
 | |
|           iface = Ci.nsIPaymentDetailsModifier;
 | |
|           break;
 | |
|       }
 | |
|       if (!iface) {
 | |
|         throw new Error(`No interface associated with the members of the ${name} nsIArray`);
 | |
|       }
 | |
|       for (let i = 0; i < value.length; i++) {
 | |
|         let item = value.queryElementAt(i, iface);
 | |
|         let result = this._serializeRequest(item, i);
 | |
|         if (result !== undefined) {
 | |
|           items.push(result);
 | |
|         }
 | |
|       }
 | |
|       return items;
 | |
|     }
 | |
|     // Structures: Arrays
 | |
|     if (Array.isArray(value)) {
 | |
|       let items = value.map(item => this._serializeRequest(item))
 | |
|                        .filter(item => item !== undefined);
 | |
|       return items;
 | |
|     }
 | |
|     // Structures: Objects
 | |
|     let obj = {};
 | |
|     for (let [key, item] of Object.entries(value)) {
 | |
|       let result = this._serializeRequest(item, key);
 | |
|       if (result !== undefined) {
 | |
|         obj[key] = result;
 | |
|       }
 | |
|     }
 | |
|     return obj;
 | |
|   },
 | |
| 
 | |
|   async initializeFrame() {
 | |
|     // We don't do this earlier as it's only necessary once this function sends
 | |
|     // the initial saved records.
 | |
|     Services.obs.addObserver(this, "formautofill-storage-changed", true);
 | |
| 
 | |
|     let requestSerialized = this._serializeRequest(this.request);
 | |
|     let chromeWindow = this.frameWeakRef.get().ownerGlobal;
 | |
|     let isPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWindow);
 | |
| 
 | |
|     let [savedAddresses, savedBasicCards] =
 | |
|       await Promise.all([this.fetchSavedAddresses(), this.fetchSavedPaymentCards()]);
 | |
| 
 | |
|     this.sendMessageToContent("showPaymentRequest", {
 | |
|       request: requestSerialized,
 | |
|       savedAddresses,
 | |
|       tempAddresses: this.temporaryStore.addresses.getAll(),
 | |
|       savedBasicCards,
 | |
|       tempBasicCards: this.temporaryStore.creditCards.getAll(),
 | |
|       isPrivate,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   debugFrame() {
 | |
|     // To avoid self-XSS-type attacks, ensure that Browser Chrome debugging is enabled.
 | |
|     if (!Services.prefs.getBoolPref("devtools.chrome.enabled", false)) {
 | |
|       Cu.reportError("devtools.chrome.enabled must be enabled to debug the frame");
 | |
|       return;
 | |
|     }
 | |
|     let {
 | |
|       gDevToolsBrowser,
 | |
|     } = ChromeUtils.import("resource://devtools/client/framework/gDevTools.jsm", {});
 | |
|     gDevToolsBrowser.openContentProcessToolbox({
 | |
|       selectedBrowser: this.frameWeakRef.get(),
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   onOpenPreferences() {
 | |
|     BrowserWindowTracker.getTopWindow().openPreferences("privacy-form-autofill");
 | |
|   },
 | |
| 
 | |
|   onPaymentCancel() {
 | |
|     const showResponse = this.createShowResponse({
 | |
|       acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
 | |
|     });
 | |
| 
 | |
|     paymentSrv.respondPayment(showResponse);
 | |
|     paymentUISrv.closePayment(this.request.requestId);
 | |
|   },
 | |
| 
 | |
|   async onPay({
 | |
|     selectedPayerAddressGUID: payerGUID,
 | |
|     selectedPaymentCardGUID: paymentCardGUID,
 | |
|     selectedPaymentCardSecurityCode: cardSecurityCode,
 | |
|     selectedShippingAddressGUID: shippingGUID,
 | |
|   }) {
 | |
|     let methodData;
 | |
|     try {
 | |
|       methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
 | |
|                                                                           cardSecurityCode);
 | |
|     } catch (ex) {
 | |
|       // TODO (Bug 1498403): Some kind of "credit card storage error" here, perhaps asking user
 | |
|       // to re-enter credit card # from management UI.
 | |
|       Cu.reportError(ex);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (!methodData) {
 | |
|       // TODO (Bug 1429265/Bug 1429205): Handle when a user hits cancel on the
 | |
|       // Master Password dialog.
 | |
|       Cu.reportError("Bug 1429265/Bug 1429205: User canceled master password entry");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let payerName = "";
 | |
|     let payerEmail = "";
 | |
|     let payerPhone = "";
 | |
|     if (payerGUID) {
 | |
|       let payerData = await this._convertProfileAddressToPayerData(payerGUID);
 | |
|       payerName = payerData.payerName;
 | |
|       payerEmail = payerData.payerEmail;
 | |
|       payerPhone = payerData.payerPhone;
 | |
|     }
 | |
| 
 | |
|     // Update the lastUsedTime for the payerAddress and paymentCard. Check if
 | |
|     // the record exists in formAutofillStorage because it may be temporary.
 | |
|     if (shippingGUID && await formAutofillStorage.addresses.get(shippingGUID)) {
 | |
|       formAutofillStorage.addresses.notifyUsed(shippingGUID);
 | |
|     }
 | |
|     if (payerGUID && await formAutofillStorage.addresses.get(payerGUID)) {
 | |
|       formAutofillStorage.addresses.notifyUsed(payerGUID);
 | |
|     }
 | |
|     if (await formAutofillStorage.creditCards.get(paymentCardGUID)) {
 | |
|       formAutofillStorage.creditCards.notifyUsed(paymentCardGUID);
 | |
|     }
 | |
| 
 | |
|     this.pay({
 | |
|       methodName: "basic-card",
 | |
|       methodData,
 | |
|       payerName,
 | |
|       payerEmail,
 | |
|       payerPhone,
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   pay({
 | |
|     payerName,
 | |
|     payerEmail,
 | |
|     payerPhone,
 | |
|     methodName,
 | |
|     methodData,
 | |
|   }) {
 | |
|     const showResponse = this.createShowResponse({
 | |
|       acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_ACCEPTED,
 | |
|       payerName,
 | |
|       payerEmail,
 | |
|       payerPhone,
 | |
|       methodName,
 | |
|       methodData,
 | |
|     });
 | |
|     paymentSrv.respondPayment(showResponse);
 | |
|     this.sendMessageToContent("responseSent");
 | |
|   },
 | |
| 
 | |
|   async onChangeShippingAddress({shippingAddressGUID}) {
 | |
|     if (shippingAddressGUID) {
 | |
|       // If a shipping address was de-selected e.g. the selected address was deleted, we'll
 | |
|       // just wait to send the address change when the shipping address is eventually selected
 | |
|       // before clicking Pay since it's a required field.
 | |
|       let address = await this._convertProfileAddressToPaymentAddress(shippingAddressGUID);
 | |
|       paymentSrv.changeShippingAddress(this.request.requestId, address);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onChangeShippingOption({optionID}) {
 | |
|     // Note, failing here on browser_host_name.js because the test closes
 | |
|     // the dialog before the onChangeShippingOption is called, thus
 | |
|     // deleting the request and making the requestId invalid. Unclear
 | |
|     // why we aren't seeing the same issue with onChangeShippingAddress.
 | |
|     paymentSrv.changeShippingOption(this.request.requestId, optionID);
 | |
|   },
 | |
| 
 | |
|   onCloseDialogMessage() {
 | |
|     // The PR is complete(), just close the dialog
 | |
|     paymentUISrv.closePayment(this.request.requestId);
 | |
|   },
 | |
| 
 | |
|   async onUpdateAutofillRecord(collectionName, record, guid, messageID) {
 | |
|     let responseMessage = {
 | |
|       guid,
 | |
|       messageID,
 | |
|       stateChange: {},
 | |
|     };
 | |
|     try {
 | |
|       let isTemporary = record.isTemporary;
 | |
|       let collection = isTemporary ? this.temporaryStore[collectionName] :
 | |
|                                      formAutofillStorage[collectionName];
 | |
| 
 | |
|       if (guid) {
 | |
|         // We only care to preserve old properties for credit cards,
 | |
|         // because credit cards don't get their full record sent to the
 | |
|         // unprivileged frame (the cc-number is excluded).
 | |
|         let preserveOldProperties = collectionName == "creditCards";
 | |
|         await collection.update(guid, record, preserveOldProperties);
 | |
|       } else {
 | |
|         responseMessage.guid = await collection.add(record);
 | |
|       }
 | |
| 
 | |
|       if (isTemporary && collectionName == "addresses") {
 | |
|         // there will be no formautofill-storage-changed event to update state
 | |
|         // so add updated collection here
 | |
|         Object.assign(responseMessage.stateChange, {
 | |
|           tempAddresses: this.temporaryStore.addresses.getAll(),
 | |
|         });
 | |
|       }
 | |
|       if (isTemporary && collectionName == "creditCards") {
 | |
|         // there will be no formautofill-storage-changed event to update state
 | |
|         // so add updated collection here
 | |
|         Object.assign(responseMessage.stateChange, {
 | |
|           tempBasicCards: this.temporaryStore.creditCards.getAll(),
 | |
|         });
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       responseMessage.error = true;
 | |
|       Cu.reportError(ex);
 | |
|     } finally {
 | |
|       this.sendMessageToContent("updateAutofillRecord:Response", responseMessage);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * @implements {nsIObserver}
 | |
|    * @param {nsISupports} subject
 | |
|    * @param {string} topic
 | |
|    * @param {string} data
 | |
|    */
 | |
|   observe(subject, topic, data) {
 | |
|     switch (topic) {
 | |
|       case "formautofill-storage-changed": {
 | |
|         if (data == "notifyUsed") {
 | |
|           break;
 | |
|         }
 | |
|         this.onAutofillStorageChange();
 | |
|         break;
 | |
|       }
 | |
|       case "message-manager-close": {
 | |
|         if (this.mm && subject == this.mm) {
 | |
|           // Remove the observer to avoid message manager errors while the dialog
 | |
|           // is closing and tests are cleaning up autofill storage.
 | |
|           Services.obs.removeObserver(this, "formautofill-storage-changed");
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   receiveMessage({data}) {
 | |
|     let {messageType} = data;
 | |
| 
 | |
|     switch (messageType) {
 | |
|       case "debugFrame": {
 | |
|         this.debugFrame();
 | |
|         break;
 | |
|       }
 | |
|       case "initializeRequest": {
 | |
|         this.initializeFrame();
 | |
|         break;
 | |
|       }
 | |
|       case "changeShippingAddress": {
 | |
|         this.onChangeShippingAddress(data);
 | |
|         break;
 | |
|       }
 | |
|       case "changeShippingOption": {
 | |
|         this.onChangeShippingOption(data);
 | |
|         break;
 | |
|       }
 | |
|       case "closeDialog": {
 | |
|         this.onCloseDialogMessage();
 | |
|         break;
 | |
|       }
 | |
|       case "openPreferences": {
 | |
|         this.onOpenPreferences();
 | |
|         break;
 | |
|       }
 | |
|       case "paymentCancel": {
 | |
|         this.onPaymentCancel();
 | |
|         break;
 | |
|       }
 | |
|       case "paymentDialogReady": {
 | |
|         this.frameWeakRef.get().dispatchEvent(new Event("tabmodaldialogready", {
 | |
|           bubbles: true,
 | |
|         }));
 | |
|         break;
 | |
|       }
 | |
|       case "pay": {
 | |
|         this.onPay(data);
 | |
|         break;
 | |
|       }
 | |
|       case "updateAutofillRecord": {
 | |
|         this.onUpdateAutofillRecord(data.collectionName, data.record, data.guid, data.messageID);
 | |
|         break;
 | |
|       }
 | |
|       default: {
 | |
|         throw new Error(`paymentDialogWrapper: Unexpected messageType: ${messageType}`);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| };
 | 
