new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/content/paymentDialog.js
@@ -0,0 +1,89 @@
+/* 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";
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
+ .getService(Ci.nsIPaymentRequestService);
+
+let PaymentDialog = {
+ componentsLoaded: new Map(),
+ frame: null,
+ mm: null,
+
+ init(frame) {
+ this.frame = frame;
+ this.mm = frame.frameLoader.messageManager;
+ this.mm.loadFrameScript("chrome://payments/content/paymentDialogFrameScript.js", true);
+ this.mm.addMessageListener("paymentContentToChrome", this);
+ },
+
+ createShowResponse({requestId, acceptStatus, methodName = "", data = null,
+ payerName = "", payerEmail = "", payerPhone = ""}) {
+ let showResponse = this.createComponentInstance(Ci.nsIPaymentShowActionResponse);
+ let methodData = this.createComponentInstance(Ci.nsIGeneralResponseData);
+
+ showResponse.init(requestId,
+ acceptStatus,
+ methodName,
+ methodData,
+ payerName,
+ payerEmail,
+ payerPhone);
+ return showResponse;
+ },
+
+ 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);
+ },
+
+ onPaymentCancel(requestId) {
+ const showResponse = this.createShowResponse({
+ requestId,
+ acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
+ });
+ paymentSrv.respondPayment(showResponse);
+ window.close();
+ },
+
+ receiveMessage({data}) {
+ let {messageType, requestId} = data;
+
+ switch (messageType) {
+ case "DOMContentLoaded": {
+ this.mm.sendAsyncMessage("paymentChromeToContent", {
+ messageType: "showPaymentRequest",
+ data: window.arguments[0],
+ });
+ break;
+ }
+ case "paymentCancel": {
+ this.onPaymentCancel(requestId);
+ break;
+ }
+ }
+ },
+};
+
+let frame = document.getElementById("paymentRequestFrame");
+PaymentDialog.init(frame);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/content/paymentDialog.xhtml
@@ -0,0 +1,19 @@
+<?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></title>
+</head>
+<body>
+ <iframe type="content"
+ id="paymentRequestFrame"
+ mozbrowser="true"
+ remote="true"
+ name="paymentRequestFrame"
+ src="resource://payments/paymentRequest.xhtml"></iframe>
+ <script src="chrome://payments/content/paymentDialog.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/content/paymentDialogFrameScript.js
@@ -0,0 +1,86 @@
+/* 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";
+
+/* eslint-env mozilla/frame-script */
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let PaymentFrameScript = {
+ init() {
+ this.defineLazyLogGetter(this, "frameScript");
+
+ addEventListener("DOMContentLoaded", this, {once: true});
+ addEventListener("paymentContentToChrome", this, false, true);
+
+ addMessageListener("paymentChromeToContent", this);
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "DOMContentLoaded": {
+ this.sendMessageToChrome("DOMContentLoaded");
+ break;
+ }
+ case "paymentContentToChrome": {
+ this.sendToChrome(event);
+ break;
+ }
+ default: {
+ throw new Error("Unexpected event type");
+ }
+ }
+ },
+
+ receiveMessage({data: {messageType, data}}) {
+ switch (messageType) {
+ case "showPaymentRequest": {
+ this.sendToContent("showPaymentRequest", data);
+ break;
+ }
+ }
+ },
+
+ sendToChrome({detail}) {
+ let {messageType, requestId} = detail;
+ this.log.debug(`received message from content: ${messageType} ... ${requestId}`);
+
+ switch (messageType) {
+ case "paymentCancel": {
+ this.sendMessageToChrome("paymentCancel", {
+ requestId,
+ });
+ break;
+ }
+ }
+ },
+
+ 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,
+ });
+ });
+ },
+
+ sendToContent(messageType, detail = {}) {
+ this.log.debug(`sendToContent (${messageType})`);
+ let response = Object.assign({messageType}, detail);
+ let event = new content.document.defaultView.CustomEvent("paymentChromeToContent", {
+ bubbles: true,
+ detail: Cu.cloneInto(response, content.document.defaultView),
+ });
+ content.document.dispatchEvent(event);
+ },
+
+ sendMessageToChrome(messageType, detail = {}) {
+ sendAsyncMessage("paymentContentToChrome", Object.assign(detail, {messageType}));
+ },
+};
+
+PaymentFrameScript.init();
--- a/toolkit/components/payments/jar.mn
+++ b/toolkit/components/payments/jar.mn
@@ -1,9 +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/.
toolkit.jar:
% content payments %content/payments/
- content/payments/paymentRequest.css (content/paymentRequest.css)
- content/payments/paymentRequest.js (content/paymentRequest.js)
- content/payments/paymentRequest.xhtml (content/paymentRequest.xhtml)
+ content/payments/paymentDialog.js (content/paymentDialog.js)
+ content/payments/paymentDialogFrameScript.js (content/paymentDialogFrameScript.js)
+ content/payments/paymentDialog.xhtml (content/paymentDialog.xhtml)
+% resource payments %res/payments/
+ res/payments (res/paymentRequest.*)
--- a/toolkit/components/payments/paymentUIService.js
+++ b/toolkit/components/payments/paymentUIService.js
@@ -1,17 +1,17 @@
/* 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";
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
// eslint-disable-next-line no-unused-vars
-const DIALOG_URL = "chrome://payments/content/paymentRequest.xhtml";
+const DIALOG_URL = "chrome://payments/content/paymentDialog.xhtml";
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");
@@ -22,56 +22,74 @@ function defineLazyLogGetter(scope, logP
return new ConsoleAPI({
maxLogLevelPref: "dom.payments.loglevel",
prefix: logPrefix,
});
});
}
function PaymentUIService() {
+ this.wrappedJSObject = this;
defineLazyLogGetter(this, "Payment UI Service");
- paymentSrv.setTestingUIService(this);
this.log.debug("constructor");
}
PaymentUIService.prototype = {
classID: Components.ID("{01f8bd55-9017-438b-85ec-7c15d2b35cdc}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIService]),
REQUEST_ID_PREFIX: "paymentRequest-",
showPayment(requestId) {
this.log.debug("showPayment");
let chromeWindow = Services.wm.getMostRecentWindow("navigator:browser");
chromeWindow.openDialog(DIALOG_URL,
`${this.REQUEST_ID_PREFIX}${requestId}`,
- "modal,dialog,centerscreen");
+ "modal,dialog,centerscreen",
+ {requestId});
},
abortPayment(requestId) {
this.log.debug(`abortPayment: ${requestId}`);
let abortResponse = Cc["@mozilla.org/dom/payments/payment-abort-action-response;1"]
.createInstance(Ci.nsIPaymentAbortActionResponse);
- abortResponse.init(requestId, Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED);
let enu = Services.wm.getEnumerator(null);
let win;
while ((win = enu.getNext())) {
if (win.name == `${this.REQUEST_ID_PREFIX}${requestId}`) {
this.log.debug(`closing: ${win.name}`);
win.close();
break;
}
}
- paymentSrv.respondPayment(abortResponse.QueryInterface(Ci.nsIPaymentActionResponse));
+
+ // if `win` is falsy, then we haven't found the dialog, so the abort fails
+ // otherwise, the abort is successful
+ let response = win ?
+ Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED :
+ Ci.nsIPaymentActionResponse.ABORT_FAILED;
+
+ abortResponse.init(requestId, response);
+ paymentSrv.respondPayment(abortResponse);
},
completePayment(requestId) {
let completeResponse = Cc["@mozilla.org/dom/payments/payment-complete-action-response;1"]
.createInstance(Ci.nsIPaymentCompleteActionResponse);
completeResponse.init(requestId, Ci.nsIPaymentActionResponse.COMPLTETE_SUCCEEDED);
paymentSrv.respondPayment(completeResponse.QueryInterface(Ci.nsIPaymentActionResponse));
},
updatePayment(requestId) {
},
+
+ // other helper methods
+
+ requestIdForWindow(window) {
+ let windowName = window.name;
+
+ return windowName.startsWith(this.REQUEST_ID_PREFIX) ?
+ windowName.replace(this.REQUEST_ID_PREFIX, "") : // returns suffix, which is the requestId
+ null;
+ },
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUIService]);
--- a/toolkit/components/payments/payments.manifest
+++ b/toolkit/components/payments/payments.manifest
@@ -1,3 +1,2 @@
component {01f8bd55-9017-438b-85ec-7c15d2b35cdc} paymentUIService.js
contract @mozilla.org/dom/payments/payment-ui-service;1 {01f8bd55-9017-438b-85ec-7c15d2b35cdc}
-category profile-after-change PaymentUIService @mozilla.org/dom/payments/payment-ui-service;1
rename from toolkit/components/payments/content/paymentRequest.css
rename to toolkit/components/payments/res/paymentRequest.css
rename from toolkit/components/payments/content/paymentRequest.js
rename to toolkit/components/payments/res/paymentRequest.js
--- a/toolkit/components/payments/content/paymentRequest.js
+++ b/toolkit/components/payments/res/paymentRequest.js
@@ -1,7 +1,86 @@
/* 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";
-const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+let PaymentRequest = {
+ requestId: null,
+
+ init() {
+ // listen to content
+ window.addEventListener("paymentChromeToContent", this);
+
+ // listen to user events
+ window.addEventListener("load", this, {once: true});
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "load": {
+ this.onPaymentRequestLoad();
+ break;
+ }
+ case "click": {
+ switch (event.target.id) {
+ case "cancel": {
+ this.onCancel();
+ break;
+ }
+ }
+ break;
+ }
+ case "unload": {
+ this.onPaymentRequestLoad();
+ break;
+ }
+ case "paymentChromeToContent": {
+ this.onChromeToContent(event);
+ break;
+ }
+ default: {
+ throw new Error("Unexpected event type");
+ }
+ }
+ },
+
+ sendMessageToContent(messageType, detail = {}) {
+ let event = new CustomEvent("paymentContentToChrome", {
+ bubbles: true,
+ detail: Object.assign({
+ requestId: this.requestId,
+ messageType,
+ }, detail),
+ });
+ document.dispatchEvent(event);
+ },
+
+ onChromeToContent({detail}) {
+ let {messageType, requestId} = detail;
+
+ switch (messageType) {
+ case "showPaymentRequest": {
+ this.requestId = requestId;
+ break;
+ }
+ }
+ },
+
+ 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);
+ },
+};
+
+PaymentRequest.init();
rename from toolkit/components/payments/content/paymentRequest.xhtml
rename to toolkit/components/payments/res/paymentRequest.xhtml
--- a/toolkit/components/payments/content/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -1,17 +1,17 @@
<?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></title>
- <link rel="stylesheet" href="chrome://payments/content/paymentRequest.css" />
- <script src="chrome://payments/content/paymentRequest.js"></script>
+ <link rel="stylesheet" href="resource://payments/paymentRequest.css" />
+ <script src="resource://payments/paymentRequest.js"></script>
</head>
<body>
<div id="controls-container">
- <button id="cancel" onclick="window.close()">Cancel payment</button>
+ <button id="cancel">Cancel payment</button>
</div>
</body>
</html>
--- a/toolkit/components/payments/test/browser/browser_request_summary.js
+++ b/toolkit/components/payments/test/browser/browser_request_summary.js
@@ -1,11 +1,11 @@
"use strict";
add_task(async function test_summary() {
await BrowserTestUtils.withNewTab({
gBrowser,
- url: "chrome://payments/content/paymentRequest.xhtml",
+ url: "resource://payments/paymentRequest.xhtml",
}, async browser => {
// eslint-disable-next-line mozilla/no-cpows-in-tests
ok(browser.contentDocument.getElementById("cancel"), "Cancel button exists");
});
});
--- a/toolkit/components/payments/test/browser/browser_show_dialog.js
+++ b/toolkit/components/payments/test/browser/browser_show_dialog.js
@@ -15,17 +15,17 @@ add_task(async function test_show_abort_
gBrowser,
url: BLANK_PAGE_URL,
}, async browser => {
// start by creating a PaymentRequest, and show it
await ContentTask.spawn(browser, {methodData, details}, ContentTasks.createAndShowRequest);
// get a reference to the UI dialog and the requestId
let win = await getDialogWindow();
- let requestId = requestIdForWindow(win);
+ let requestId = paymentUISrv.requestIdForWindow(win);
ok(requestId, "requestId should be defined");
ok(!win.closed, "dialog should not be closed");
// abort the payment request
await ContentTask.spawn(browser, null, async() => content.rq.abort());
ok(win.closed, "dialog should be closed");
});
});
--- a/toolkit/components/payments/test/browser/head.js
+++ b/toolkit/components/payments/test/browser/head.js
@@ -3,40 +3,33 @@
/* eslint
"no-unused-vars": ["error", {
vars: "local",
args: "none",
varsIgnorePattern: "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$",
}],
*/
-const REQUEST_ID_PREFIX = "paymentRequest-";
const BLANK_PAGE_URL = "https://example.com/browser/toolkit/components/" +
"payments/test/browser/blank_page.html";
const PREF_PAYMENT_ENABLED = "dom.payments.request.enabled";
+const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
+ .getService().wrappedJSObject;
async function getDialogWindow() {
let win;
await BrowserTestUtils.waitForCondition(() => {
win = Services.wm.getMostRecentWindow(null);
- return win.name.startsWith(REQUEST_ID_PREFIX);
+ return win.name.startsWith(paymentUISrv.REQUEST_ID_PREFIX);
}, "payment dialog should be the most recent");
return win;
}
-function requestIdForWindow(window) {
- let windowName = window.name;
-
- return windowName.startsWith(REQUEST_ID_PREFIX) ?
- windowName.replace(REQUEST_ID_PREFIX, "") : // returns suffix, which is the requestId
- null;
-}
-
/**
* Common content tasks functions to be used with ContentTask.spawn.
*/
let ContentTasks = {
createAndShowRequest: async ({methodData, details, options}) => {
let rq = new content.PaymentRequest(methodData, details, options);
content.rq = rq; // assign it so we can retrieve it later
rq.show();