Bug 1387221 - Integrate the autofill storage for shipping addresses & handle UI address change. r=mattn
MozReview-Commit-ID: JNKaVRbjmuZ
--- a/toolkit/components/payments/content/paymentDialog.xhtml
+++ b/toolkit/components/payments/content/paymentDialog.xhtml
@@ -9,12 +9,12 @@
</head>
<body>
<iframe type="content"
id="paymentRequestFrame"
mozbrowser="true"
remote="true"
name="paymentRequestFrame"
src="resource://payments/paymentRequest.xhtml"
- style="border: none;"></iframe>
+ style="border: none; height: 600px;"></iframe>
<script src="chrome://payments/content/paymentDialog.js"></script>
</body>
</html>
--- a/toolkit/components/payments/paymentUIService.js
+++ b/toolkit/components/payments/paymentUIService.js
@@ -11,16 +11,29 @@ const DIALOG_URL = "chrome://payments/co
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyServiceGetter(this,
"paymentSrv",
"@mozilla.org/dom/payments/payment-request-service;1",
"nsIPaymentRequestService");
+XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
+ let _profileStorage;
+
+ try {
+ _profileStorage = Cu.import("resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
+ _profileStorage.initialize();
+ } catch (e) {
+ _profileStorage = null;
+ }
+
+ return _profileStorage;
+});
+
function defineLazyLogGetter(scope, logPrefix) {
XPCOMUtils.defineLazyGetter(scope, "log", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.payments.loglevel",
prefix: logPrefix,
});
});
@@ -28,24 +41,52 @@ function defineLazyLogGetter(scope, logP
function PaymentUIService() {
this.wrappedJSObject = this;
defineLazyLogGetter(this, "Payment UI Service");
this.log.debug("constructor");
}
function getPaymentRequestInfo(requestId) {
- let {paymentDetails} = paymentSrv.getPaymentRequestById(requestId);
+ let {paymentDetails, paymentOptions} = paymentSrv.getPaymentRequestById(requestId);
// get total information first, and structure it
let {totalItem: {label, amount: {value, currency}}} = paymentDetails;
let total = {label, value, currency};
+ // depending on the options, fetch autofillStorage to pre-populate the dialog
+ let selectedOptions = Object.keys(paymentOptions).filter(prop => prop.startsWith("request"))
+ .filter(option => paymentOptions[option]);
+ if (!profileStorage || selectedOptions.length == 0) {
+ return {total};
+ }
+
+ // get storage info, and keep only the fields we need
+ let profiles = profileStorage.addresses.getAll().map(profile => {
+ return {
+ address: profile["street-address"],
+ addressLines: [
+ profile["address-line1"],
+ profile["address-line2"],
+ profile["address-line3"],
+ ],
+ city: profile["address-level2"],
+ country: profile["country-name"],
+ email: profile.email,
+ name: profile.name,
+ organization: profile.organization,
+ phone: profile.tel,
+ postalCode: profile["postal-code"],
+ region: profile["address-level1"],
+ // dependentLocality && sortingCode && languageCode ??
+ };
+ });
+
// return all the required information
- return {total};
+ return {total, selectedOptions, profiles};
}
PaymentUIService.prototype = {
classID: Components.ID("{01f8bd55-9017-438b-85ec-7c15d2b35cdc}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIService]),
REQUEST_ID_PREFIX: "paymentRequest-",
showPayment(requestId) {
--- a/toolkit/components/payments/res/paymentRequest.css
+++ b/toolkit/components/payments/res/paymentRequest.css
@@ -1,20 +1,50 @@
/* 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/. */
+body {
+ background: white;
+}
+
#total {
border: 1px solid black;
margin: 5px;
text-align: center;
}
#total .label {
font-size: 15px;
font-weight: bold;
}
-#cancel {
- position: absolute;
- bottom: 10px;
- left: 10px;
+.shipment-info {
+ display: none;
+ padding: 5px;
+ border: 1px solid #979797;
+ cursor: pointer;
+ white-space: pre-line;
+}
+
+.shipment-info.selected:not(.choosing) {
+ display: block;
+ background: #E5E5E5;
+}
+
+.shipment-info.selected.error:not(.choosing) {
+ background: #FFEEF0;
+ border-color: #F68B98;
+}
+
+.shipment-info.selected.error:not(.choosing)::after {
+ content: "\AInvalid shipping address!";
+ color: #D51C33;
+ white-space: pre;
+}
+
+.shipment-info.choosing {
+ display: block;
+}
+
+.shipment-info.choosing:hover {
+ background: #E5E5E5;
}
\ No newline at end of file
--- a/toolkit/components/payments/res/paymentRequest.js
+++ b/toolkit/components/payments/res/paymentRequest.js
@@ -1,16 +1,22 @@
/* 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/. */
"use strict";
let PaymentRequest = {
requestId: null,
+ shippings: {
+ list: [], // list of shipping-info
+ selected: -1, // index of the address chosen in the list
+ status: undefined, // "selected", "choosing", or undefined at first
+ },
+ eventsToRemove: [],
init() {
// listen to content
window.addEventListener("paymentChromeToContent", this);
// listen to user events
window.addEventListener("load", this, {once: true});
},
@@ -56,37 +62,144 @@ let PaymentRequest = {
},
onChromeToContent({detail}) {
let {messageType, requestId} = detail;
switch (messageType) {
case "showPaymentRequest": {
this.requestId = requestId;
- let {prInfo: {total}} = detail;
+ let {prInfo: {total, selectedOptions, profiles}} = detail;
+ // populate total info
let totalElem = document.getElementById("total");
totalElem.querySelector(".value").innerText = total.value;
totalElem.querySelector(".currency").innerText = total.currency;
totalElem.querySelector(".label").innerText = total.label;
+
+ // pre-populate address preview, if any profiles
+ if (selectedOptions.includes("requestShipping") && profiles) {
+ let shipmentInfoElem = document.getElementById("shipment");
+
+ profiles.forEach((profile, i) => {
+ let renderedProfile = "";
+ let shipping = {
+ error: [],
+ data: Object.assign({}, profile),
+ };
+ // XXX: add a "contact" section, like Chrome?
+ if (selectedOptions.includes("requestPayerName")) {
+ if (profile.name) {
+ renderedProfile += `${profile.name}, `;
+ } else {
+ shipping.error.push("requestPayerName");
+ }
+ }
+
+ renderedProfile += (profile.address ? `${profile.address},\n` : "") +
+ (profile.city ? `${profile.city}, ` : "") +
+ (profile.region ? `${profile.region}, ` : "") +
+ (profile.country ? `${profile.country}, ` : "") +
+ (profile.postalCode ? `${profile.postalCode},\n` : "\n");
+
+ if (selectedOptions.includes("requestPayerPhone")) {
+ if (profile.phone) {
+ renderedProfile += `Phone: ${profile.phone}, `;
+ } else {
+ shipping.error.push("requestPayerPhone");
+ }
+ }
+
+ if (selectedOptions.includes("requestPayerEmail")) {
+ if (profile.email) {
+ renderedProfile += `Email: ${profile.email}`;
+ } else {
+ shipping.error.push("requestPayerEmail");
+ }
+ }
+
+ let profileElem = document.createElement("div");
+ profileElem.textContent = renderedProfile;
+ profileElem.classList.add("shipment-info");
+ profileElem.dataset.i = i;
+ // make 1st profile visible, hide others (by default)
+ if (i == 0) {
+ profileElem.classList.add("selected");
+ this.shippings.selected = 0;
+ this.shippings.status = "selected";
+ }
+ if (shipping.error.length > 0) {
+ profileElem.classList.add("error");
+ }
+ this.addBalancedListener(profileElem, "click", this.changeShippingAddress);
+ shipmentInfoElem.appendChild(profileElem);
+ this.shippings.list.push(shipping);
+ });
+ }
break;
}
}
},
+ addBalancedListener: (elem, eventName, callback, options = {}) => {
+ dump(`cb: ${callback}\n\n`)
+ // eslint-disable-next-line mozilla/balanced-listeners
+ elem.addEventListener(eventName, callback, options); // listen
+ this.eventsToRemove.push({elem, eventName, callback}); // remember to removeListener on "unload"
+ },
+
+ changeShippingAddress(e) {
+ if (this.shippings.list.length <= 1) {
+ return;
+ }
+
+ if (this.shippings.status == "selected") {
+ // show all shippings
+ this.shippings.status = "choosing";
+ document.querySelectorAll(".shipment-info")
+ .forEach(shipmentInfo => shipmentInfo.classList.add("choosing"));
+ return;
+ }
+
+ if (this.shippings.status == "choosing") {
+ // an address has been selected
+ this.shippings.status = "selected";
+
+ document.querySelectorAll(".shipment-info")
+ .forEach(shipmentInfo =>
+ shipmentInfo.classList.remove("choosing", "selected")
+ );
+ e.target.classList.add("selected");
+
+ // there has been no change in the address selected, stop here
+ let i = parseInt(e.target.dataset.i, 10);
+ if (i == this.shippings.selected) {
+ return;
+ }
+
+ this.shippings.selected = i;
+ }
+ },
+
onPaymentRequestLoad(requestId) {
let cancelBtn = document.getElementById("cancel");
cancelBtn.addEventListener("click", this, {once: true});
window.addEventListener("unload", this, {once: true});
},
onCancel() {
this.sendMessageToContent("paymentCancel");
},
onPaymentRequestUnload() {
// remove listeners that may be used multiple times here
window.removeEventListener("paymentChromeToContent", this);
+
+ let event = this.eventsToRemove.pop();
+ while (event) {
+ event.elem.removeEventListener(event.eventName, event.callback);
+ event = this.eventsToRemove.pop();
+ }
},
};
PaymentRequest.init();
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -10,13 +10,21 @@
<script src="resource://payments/paymentRequest.js"></script>
</head>
<body>
<div id="total">
<h2 class="label"></h2>
<span class="value"></span>
<span class="currency"></span>
</div>
+ <div id="preview">
+ <div id="shipment">
+ Ship to: <br/>
+ </div>
+ <div id="payment">
+ <!-- Same as #shipment, but for the payment -->
+ </div>
+ </div>
<div id="controls-container">
<button id="cancel">Cancel payment</button>
</div>
</body>
</html>