Bug 1383300 - Show payment request total and origin in the dialog. r=MattN

MozReview-Commit-ID: 9taFJYmQnBP

--HG--
extra : rebase_source : 5c522845fa7b436a6dda1b7c873cd209fbaf798d
This commit is contained in:
Jonathan Guillotte-Blouin 2017-10-12 21:31:03 -04:00
parent 1bf7198d39
commit ec9fa2ac68
10 changed files with 133 additions and 56 deletions

View File

@ -0,0 +1,11 @@
/* 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 {
margin: 0;
}
#paymentRequestFrame {
border: none;
}

View File

@ -13,24 +13,37 @@ const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
.getService(Ci.nsIPaymentRequestService);
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let PaymentDialog = {
componentsLoaded: new Map(),
frame: null,
mm: null,
request: null,
init(requestId, frame) {
if (!requestId || typeof(requestId) != "string") {
throw new Error("Invalid PaymentRequest ID");
}
this.request = paymentSrv.getPaymentRequestById(requestId);
if (!this.request) {
throw new Error(`PaymentRequest not found: ${requestId}`);
}
init(frame) {
this.frame = frame;
this.mm = frame.frameLoader.messageManager;
this.mm.addMessageListener("paymentContentToChrome", this);
this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
this.frame.src = "resource://payments/paymentRequest.xhtml";
},
createShowResponse({requestId, acceptStatus, methodName = "", data = null,
createShowResponse({acceptStatus, methodName = "", data = null,
payerName = "", payerEmail = "", payerPhone = ""}) {
let showResponse = this.createComponentInstance(Ci.nsIPaymentShowActionResponse);
let methodData = this.createComponentInstance(Ci.nsIGeneralResponseData);
showResponse.init(requestId,
showResponse.init(this.request.requestId,
acceptStatus,
methodName,
methodData,
@ -62,9 +75,8 @@ let PaymentDialog = {
return component.createInstance(componentInterface);
},
onPaymentCancel(requestId) {
onPaymentCancel() {
const showResponse = this.createShowResponse({
requestId,
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
});
paymentSrv.respondPayment(showResponse);
@ -72,18 +84,30 @@ let PaymentDialog = {
},
receiveMessage({data}) {
let {messageType, requestId} = data;
let {messageType} = data;
switch (messageType) {
case "initializeRequest": {
let requestSerialized = JSON.parse(JSON.stringify(this.request));
// Manually serialize the nsIPrincipal.
let displayHost = this.request.topLevelPrincipal.URI.displayHost;
requestSerialized.topLevelPrincipal = {
URI: {
displayHost,
},
};
this.mm.sendAsyncMessage("paymentChromeToContent", {
messageType: "showPaymentRequest",
data: window.arguments[0],
data: {
request: requestSerialized,
},
});
break;
}
case "paymentCancel": {
this.onPaymentCancel(requestId);
this.onPaymentCancel();
break;
}
}
@ -91,4 +115,5 @@ let PaymentDialog = {
};
let frame = document.getElementById("paymentRequestFrame");
PaymentDialog.init(frame);
let requestId = (new URLSearchParams(window.location.search)).get("requestId");
PaymentDialog.init(requestId, frame);

View File

@ -6,14 +6,15 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<link rel="stylesheet" href="chrome://payments/content/paymentDialog.css"/>
</head>
<body>
<iframe type="content"
id="paymentRequestFrame"
mozbrowser="true"
remote="true"
name="paymentRequestFrame"
src="resource://payments/paymentRequest.xhtml"></iframe>
name="paymentRequestFrame"></iframe>
<script src="chrome://payments/content/paymentDialog.js"></script>
</body>
</html>

View File

@ -24,7 +24,14 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
let PaymentFrameScript = {
init() {
this.defineLazyLogGetter(this, "frameScript");
XPCOMUtils.defineLazyGetter(this, "log", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.payments.loglevel",
prefix: "paymentDialogFrameScript",
});
});
addEventListener("paymentContentToChrome", this, false, true);
addMessageListener("paymentChromeToContent", this);
@ -39,35 +46,22 @@ let PaymentFrameScript = {
},
sendToChrome({detail}) {
let {messageType, requestId} = detail;
this.log.debug(`received message from content: ${messageType} ... ${requestId}`);
this.sendMessageToChrome(messageType, {
requestId,
});
},
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,
});
});
let {messageType} = detail;
this.log.debug("sendToChrome:", messageType, detail);
this.sendMessageToChrome(messageType, detail);
},
sendToContent(messageType, detail = {}) {
this.log.debug(`sendToContent (${messageType})`);
this.log.debug("sendToContent", messageType, detail);
let response = Object.assign({messageType}, detail);
let event = new content.document.defaultView.CustomEvent("paymentChromeToContent", {
bubbles: true,
detail: Cu.cloneInto(response, content.document.defaultView),
let event = new content.CustomEvent("paymentChromeToContent", {
detail: Cu.cloneInto(response, content),
});
content.document.dispatchEvent(event);
content.dispatchEvent(event);
},
sendMessageToChrome(messageType, detail = {}) {
sendAsyncMessage("paymentContentToChrome", Object.assign(detail, {messageType}));
sendMessageToChrome(messageType, data = {}) {
sendAsyncMessage("paymentContentToChrome", Object.assign(data, {messageType}));
},
};

View File

@ -14,6 +14,13 @@ Debugging
Set the pref ``dom.payments.loglevel`` to "Debug".
To open a debugger in the context of the remote payment frame, run the following while the dialog is the most recent window:
``
gDevToolsBrowser.openContentProcessToolbox({
selectedBrowser: Services.wm.getMostRecentWindow(null).document.getElementById("paymentRequestFrame").frameLoader,
})
``
Communication with the DOM
==========================
@ -32,7 +39,7 @@ This is because the unprivileged document cannot access message managers.
Instead, all communication across the privileged/unprivileged boundary is done via custom DOM events:
* A ``paymentContentToChrome`` event is dispatched when the dialog contents want to communicate with the privileged dialog wrapper.
* A ``paymentChromeToContent`` event is dispatched on the ``document`` with the ``detail`` property populated when the privileged dialog wrapper communicates with the unprivileged dialog.
* A ``paymentChromeToContent`` event is dispatched on the ``window`` with the ``detail`` property populated when the privileged dialog wrapper communicates with the unprivileged dialog.
These events are converted to/from message manager messages of the same name to communicate to the other process.
The purpose of `paymentDialogFrameScript.js` is to simply convert unprivileged DOM events to/from messages from the other process.

View File

@ -4,6 +4,7 @@
toolkit.jar:
% content payments %content/payments/
content/payments/paymentDialog.css (content/paymentDialog.css)
content/payments/paymentDialog.js (content/paymentDialog.js)
content/payments/paymentDialogFrameScript.js (content/paymentDialogFrameScript.js)
content/payments/paymentDialog.xhtml (content/paymentDialog.xhtml)

View File

@ -25,19 +25,15 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/dom/payments/payment-request-service;1",
"nsIPaymentRequestService");
function defineLazyLogGetter(scope, logPrefix) {
XPCOMUtils.defineLazyGetter(scope, "log", () => {
function PaymentUIService() {
this.wrappedJSObject = this;
XPCOMUtils.defineLazyGetter(this, "log", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.payments.loglevel",
prefix: logPrefix,
prefix: "Payment UI Service",
});
});
}
function PaymentUIService() {
this.wrappedJSObject = this;
defineLazyLogGetter(this, "Payment UI Service");
this.log.debug("constructor");
}
@ -50,16 +46,15 @@ PaymentUIService.prototype = {
// nsIPaymentUIService implementation:
showPayment(requestId) {
this.log.debug(`showPayment: ${requestId}`);
this.log.debug("showPayment:", requestId);
let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
chromeWindow.openDialog(this.DIALOG_URL,
chromeWindow.openDialog(`${this.DIALOG_URL}?requestId=${requestId}`,
`${this.REQUEST_ID_PREFIX}${requestId}`,
"modal,dialog,centerscreen",
{requestId});
"modal,dialog,centerscreen,resizable=no");
},
abortPayment(requestId) {
this.log.debug(`abortPayment: ${requestId}`);
this.log.debug("abortPayment:", requestId);
let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"]
.createInstance(Ci.nsIPaymentAbortActionResponse);
@ -84,7 +79,7 @@ PaymentUIService.prototype = {
},
completePayment(requestId) {
this.log.debug(`completePayment: ${requestId}`);
this.log.debug("completePayment:", requestId);
let completeResponse = Cc["@mozilla.org/dom/payments/payment-complete-action-response;1"]
.createInstance(Ci.nsIPaymentCompleteActionResponse);
completeResponse.init(requestId, Ci.nsIPaymentActionResponse.COMPLTETE_SUCCEEDED);
@ -92,7 +87,7 @@ PaymentUIService.prototype = {
},
updatePayment(requestId) {
this.log.debug(`updatePayment: ${requestId}`);
this.log.debug("updatePayment:", requestId);
},
// other helper methods

View File

@ -2,3 +2,23 @@
* 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/. */
html {
background: -moz-dialog;
}
#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;
}

View File

@ -11,14 +11,16 @@
"use strict";
let PaymentRequest = {
requestId: null,
request: null,
domReadyPromise: null,
init() {
// listen to content
window.addEventListener("paymentChromeToContent", this);
// listen to user events
window.addEventListener("DOMContentLoaded", this, {once: true});
this.domReadyPromise = new Promise(function dcl(resolve) {
window.addEventListener("DOMContentLoaded", resolve, {once: true});
}).then(this.handleEvent.bind(this));
// This scope is now ready to listen to the initialization data
this.sendMessageToChrome("initializeRequest");
@ -57,7 +59,6 @@ let PaymentRequest = {
let event = new CustomEvent("paymentContentToChrome", {
bubbles: true,
detail: Object.assign({
requestId: this.requestId,
messageType,
}, detail),
});
@ -65,11 +66,12 @@ let PaymentRequest = {
},
onChromeToContent({detail}) {
let {messageType, requestId} = detail;
let {messageType} = detail;
switch (messageType) {
case "showPaymentRequest": {
this.requestId = requestId;
this.request = detail.request;
this.onShowPaymentRequest();
break;
}
}
@ -83,6 +85,20 @@ let PaymentRequest = {
this.sendMessageToChrome("paymentDialogReady");
},
async onShowPaymentRequest() {
// Handle getting called before the DOM is ready.
await this.domReadyPromise;
let hostNameEl = document.getElementById("host-name");
hostNameEl.textContent = this.request.topLevelPrincipal.URI.displayHost;
let totalItem = this.request.paymentDetails.totalItem;
let totalEl = document.getElementById("total");
totalEl.querySelector(".value").textContent = totalItem.amount.value;
totalEl.querySelector(".currency").textContent = totalItem.amount.currency;
totalEl.querySelector(".label").textContent = totalItem.label;
},
onCancel() {
this.sendMessageToChrome("paymentCancel");
},

View File

@ -10,6 +10,13 @@
<script src="resource://payments/paymentRequest.js"></script>
</head>
<body>
<div id="host-name"></div>
<div id="total">
<h2 class="label"></h2>
<span class="value"></span>
<span class="currency"></span>
</div>
<div id="controls-container">
<button id="cancel">Cancel payment</button>
</div>