Bug 1383300 - Show payment request total and origin in the dialog. r=MattN
☠☠ backed out by ab983c341420 ☠ ☠
authorJonathan Guillotte-Blouin <jguillotteblouin@mozilla.com>
Thu, 12 Oct 2017 21:31:03 -0400
changeset 443412 34fa5b4d4d596ae654b3571700afa42c290d7cc7
parent 443411 66a726c06aa7e1e955da5831145a83d0cf753ad1
child 443413 71b84c8137051478ac2e60dea8c42c4863c7188f
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMattN
bugs1383300
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1383300 - Show payment request total and origin in the dialog. r=MattN MozReview-Commit-ID: 9taFJYmQnBP
toolkit/components/payments/content/paymentDialog.css
toolkit/components/payments/content/paymentDialog.js
toolkit/components/payments/content/paymentDialog.xhtml
toolkit/components/payments/content/paymentDialogFrameScript.js
toolkit/components/payments/docs/index.rst
toolkit/components/payments/jar.mn
toolkit/components/payments/paymentUIService.js
toolkit/components/payments/res/paymentRequest.css
toolkit/components/payments/res/paymentRequest.js
toolkit/components/payments/res/paymentRequest.xhtml
new file mode 100644
--- /dev/null
+++ b/toolkit/components/payments/content/paymentDialog.css
@@ -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;
+}
--- a/toolkit/components/payments/content/paymentDialog.js
+++ b/toolkit/components/payments/content/paymentDialog.js
@@ -8,34 +8,57 @@
  */
 
 "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);
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 let PaymentDialog = {
   componentsLoaded: new Map(),
   frame: null,
   mm: null,
+  request: null,
 
-  init(frame) {
+  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}`);
+    }
+
     this.frame = frame;
     this.mm = frame.frameLoader.messageManager;
+
+    XPCOMUtils.defineLazyGetter(this, "log", () => {
+      let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
+      return new ConsoleAPI({
+        maxLogLevelPref: "dom.payments.loglevel",
+        prefix: `paymentDialog (${requestId})`,
+      });
+    });
+
+    this.log.debug("init:", this.request);
     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,
                       payerName,
                       payerEmail,
                       payerPhone);
     return showResponse;
   },
@@ -57,38 +80,50 @@ let PaymentDialog = {
     if (!component) {
       component = Cc[componentName];
       this.componentsLoaded.set(componentName, component);
     }
 
     return component.createInstance(componentInterface);
   },
 
-  onPaymentCancel(requestId) {
+  onPaymentCancel() {
     const showResponse = this.createShowResponse({
-      requestId,
       acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
     });
     paymentSrv.respondPayment(showResponse);
     window.close();
   },
 
   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;
       }
     }
   },
 };
 
 let frame = document.getElementById("paymentRequestFrame");
-PaymentDialog.init(frame);
+let requestId = (new URLSearchParams(window.location.search)).get("requestId");
+PaymentDialog.init(requestId, frame);
--- a/toolkit/components/payments/content/paymentDialog.xhtml
+++ b/toolkit/components/payments/content/paymentDialog.xhtml
@@ -1,19 +1,20 @@
 <?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/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>
--- a/toolkit/components/payments/content/paymentDialogFrameScript.js
+++ b/toolkit/components/payments/content/paymentDialogFrameScript.js
@@ -19,56 +19,50 @@
 
 /* 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");
+    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);
   },
 
   handleEvent(event) {
     this.sendToChrome(event);
   },
 
   receiveMessage({data: {messageType, data}}) {
     this.sendToContent(messageType, data);
   },
 
   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}));
   },
 };
 
 PaymentFrameScript.init();
--- a/toolkit/components/payments/docs/index.rst
+++ b/toolkit/components/payments/docs/index.rst
@@ -9,16 +9,23 @@ JSDoc style comments are used within the
 .. toctree::
    :maxdepth: 5
 
 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
 ==========================
 
 Communication from the DOM to the UI happens via the `paymentUIService.js` (implementing ``nsIPaymentUIService``).
 The UI talks to the DOM code via the ``nsIPaymentRequestService`` interface.
 
 
@@ -27,12 +34,12 @@ Dialog Architecture
 
 Privileged wrapper XHTML document (paymentDialog.xhtml) containing a remote ``<iframe mozbrowser="true" remote="true">`` containing unprivileged XHTML (paymentRequest.xhtml).
 Keeping the dialog contents unprivileged is useful since the dialog will render payment line items and shipping options that are provided by web developers and should therefore be considered untrusted.
 In order to communicate across the process boundary a privileged frame script (`paymentDialogFrameScript.js`) is loaded into the iframe to relay messages.
 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.
--- a/toolkit/components/payments/jar.mn
+++ b/toolkit/components/payments/jar.mn
@@ -1,11 +1,12 @@
 # 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/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)
 %   resource payments %res/payments/
     res/payments (res/paymentRequest.*)
--- a/toolkit/components/payments/paymentUIService.js
+++ b/toolkit/components/payments/paymentUIService.js
@@ -20,51 +20,46 @@ const { classes: Cc, interfaces: Ci, res
 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");
 
-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");
 }
 
 PaymentUIService.prototype = {
   classID: Components.ID("{01f8bd55-9017-438b-85ec-7c15d2b35cdc}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIService]),
   DIALOG_URL: "chrome://payments/content/paymentDialog.xhtml",
   REQUEST_ID_PREFIX: "paymentRequest-",
 
   // 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);
 
     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}`);
@@ -79,25 +74,25 @@ PaymentUIService.prototype = {
       Ci.nsIPaymentActionResponse.ABORT_SUCCEEDED :
       Ci.nsIPaymentActionResponse.ABORT_FAILED;
 
     abortResponse.init(requestId, response);
     paymentSrv.respondPayment(abortResponse);
   },
 
   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);
     paymentSrv.respondPayment(completeResponse.QueryInterface(Ci.nsIPaymentActionResponse));
   },
 
   updatePayment(requestId) {
-    this.log.debug(`updatePayment: ${requestId}`);
+    this.log.debug("updatePayment:", requestId);
   },
 
   // other helper methods
 
   requestIdForWindow(window) {
     let windowName = window.name;
 
     return windowName.startsWith(this.REQUEST_ID_PREFIX) ?
--- a/toolkit/components/payments/res/paymentRequest.css
+++ b/toolkit/components/payments/res/paymentRequest.css
@@ -1,4 +1,24 @@
 /* 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/. */
 
+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;
+}
--- a/toolkit/components/payments/res/paymentRequest.js
+++ b/toolkit/components/payments/res/paymentRequest.js
@@ -6,17 +6,17 @@
  * Loaded in the unprivileged frame of each payment dialog.
  *
  * Communicates with privileged code via DOM Events.
  */
 
 "use strict";
 
 let PaymentRequest = {
-  requestId: null,
+  request: null,
 
   init() {
     // listen to content
     window.addEventListener("paymentChromeToContent", this);
 
     // listen to user events
     window.addEventListener("DOMContentLoaded", this, {once: true});
 
@@ -52,29 +52,37 @@ let PaymentRequest = {
       }
     }
   },
 
   sendMessageToChrome(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;
+    let {messageType} = detail;
 
     switch (messageType) {
       case "showPaymentRequest": {
-        this.requestId = requestId;
+        this.request = detail.request;
+
+        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;
         break;
       }
     }
   },
 
   onPaymentRequestLoad(requestId) {
     let cancelBtn = document.getElementById("cancel");
     cancelBtn.addEventListener("click", this, {once: true});
--- a/toolkit/components/payments/res/paymentRequest.xhtml
+++ b/toolkit/components/payments/res/paymentRequest.xhtml
@@ -5,13 +5,20 @@
 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <title></title>
   <link rel="stylesheet" href="resource://payments/paymentRequest.css" />
   <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>
 </body>
 </html>