Bug 815148. r=fabrice, a=blocking-basecamp
authorFernando Jiménez <ferjmoreno@gmail.com>
Mon, 03 Dec 2012 21:44:58 +0100
changeset 118581 21c9aecc314f171b9127499291e3e4845627baed
parent 118580 92fdcff7bde4803b675e418de7719bfd5976d443
child 118582 55e7b7ee8d30ff7decfbf1bd5d2849d9baa930cc
push idunknown
push userunknown
push dateunknown
reviewersfabrice, blocking-basecamp
bugs815148
milestone19.0a2
Bug 815148. r=fabrice, a=blocking-basecamp
b2g/chrome/content/payment.js
b2g/components/PaymentGlue.js
dom/payment/Payment.jsm
dom/payment/interfaces/nsIPaymentUIGlue.idl
--- a/b2g/chrome/content/payment.js
+++ b/b2g/chrome/content/payment.js
@@ -19,25 +19,35 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 const kClosePaymentFlowEvent = "close-payment-flow-dialog";
 
+let requestId;
+
 function paymentSuccess(aResult) {
   closePaymentFlowDialog(function notifySuccess() {
-    cpmm.sendAsyncMessage("Payment:Success", { result: aResult });
+    if (!requestId) {
+      return;
+    }
+    cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
+                                               requestId: requestId });
   });
 }
 
 function paymentFailed(aErrorMsg) {
   closePaymentFlowDialog(function notifyError() {
-    cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg });
+    if (!requestId) {
+      return;
+    }
+    cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
+                                              requestId: requestId });
   });
 }
 
 function closePaymentFlowDialog(aCallback) {
   // After receiving the payment provider confirmation about the
   // successful or failed payment flow, we notify the UI to close the
   // payment flow dialog and return to the caller application.
   let randomId = uuidgen.generateUUID().toString();
@@ -70,12 +80,18 @@ function closePaymentFlowDialog(aCallbac
     let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                  .createInstance(Ci.nsIPaymentUIGlue);
     glue.cleanup();
   });
 
   browser.shell.sendChromeEvent(detail);
 }
 
+// We save the identifier of the DOM request, so we can dispatch the results
+// of the payment flow to the appropriate content process.
+addMessageListener("Payment:LoadShim", function receiveMessage(aMessage) {
+  requestId = aMessage.json.requestId;
+});
+
 addEventListener("DOMContentLoaded", function(e) {
   content.wrappedJSObject.paymentSuccess = paymentSuccess;
   content.wrappedJSObject.paymentFailed = paymentFailed;
 });
--- a/b2g/components/PaymentGlue.js
+++ b/b2g/components/PaymentGlue.js
@@ -25,22 +25,23 @@ function debug (s) {
   //dump("-*- PaymentGlue: " + s + "\n");
 };
 
 function PaymentUI() {
 }
 
 PaymentUI.prototype = {
 
-  confirmPaymentRequest: function confirmPaymentRequest(aRequests,
+  confirmPaymentRequest: function confirmPaymentRequest(aRequestId,
+                                                        aRequests,
                                                         aSuccessCb,
                                                         aErrorCb) {
     let _error = function _error(errorMsg) {
       if (aErrorCb) {
-        aErrorCb.onresult(errorMsg);
+        aErrorCb.onresult(aRequestId, errorMsg);
       }
     };
 
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
     let content = browser.getContentWindow();
     if (!content) {
       _error("NO_CONTENT_WINDOW");
       return;
@@ -61,33 +62,33 @@ PaymentUI.prototype = {
     // based on the selected payment provider.
     content.addEventListener("mozContentEvent", function handleSelection(evt) {
       let msg = evt.detail;
       if (msg.id != id) {
         return;
       }
 
       if (msg.userSelection && aSuccessCb) {
-        aSuccessCb.onresult(msg.userSelection);
+        aSuccessCb.onresult(aRequestId, msg.userSelection);
       } else if (msg.errorMsg) {
         _error(msg.errorMsg);
       }
 
       content.removeEventListener("mozContentEvent", handleSelection);
     });
 
     browser.shell.sendChromeEvent(detail);
   },
 
-  showPaymentFlow: function showPaymentFlow(aPaymentFlowInfo, aErrorCb) {
-    debug("showPaymentFlow. uri " + aPaymentFlowInfo.uri);
-
+  showPaymentFlow: function showPaymentFlow(aRequestId,
+                                            aPaymentFlowInfo,
+                                            aErrorCb) {
     let _error = function _error(errorMsg) {
       if (aErrorCb) {
-        aErrorCb.onresult(errorMsg);
+        aErrorCb.onresult(aRequestId, errorMsg);
       }
     };
 
     // We ask the UI to browse to the selected payment flow.
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
     let content = browser.getContentWindow();
     if (!content) {
       _error("NO_CONTENT_WINDOW");
@@ -119,16 +120,17 @@ PaymentUI.prototype = {
         return;
       }
       let frame = evt.detail.frame;
       let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
                         .frameLoader;
       let mm = frameLoader.messageManager;
       try {
         mm.loadFrameScript(kPaymentShimFile, true);
+        mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
       } catch (e) {
         debug("Error loading " + kPaymentShimFile + " as a frame script: " + e);
         _error("ERROR_LOADING_PAYMENT_SHIM");
       } finally {
         content.removeEventListener("mozContentEvent", loadPaymentShim);
       }
     }).bind(this));
 
--- a/dom/payment/Payment.jsm
+++ b/dom/payment/Payment.jsm
@@ -26,18 +26,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIPrefService");
 
 function debug (s) {
   //dump("-*- PaymentManager: " + s + "\n");
 };
 
 let PaymentManager =  {
   init: function init() {
-    this.requestId = null;
-
     // Payment providers data are stored as a preference.
     this.registeredProviders = null;
 
     this.messageManagers = {};
 
     for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
       ppmm.addMessageListener(msgname, this);
     }
@@ -48,109 +46,113 @@ let PaymentManager =  {
   /**
    * Process a message from the content process.
    */
   receiveMessage: function receiveMessage(aMessage) {
     let name = aMessage.name;
     let msg = aMessage.json;
     debug("Received '" + name + "' message from content process");
 
-    if (msg.requestId) {
-      this.requestId = msg.requestId;
-    }
-
     switch (name) {
       case "Payment:Pay": {
         // First of all, we register the payment providers.
         if (!this.registeredProviders) {
           this.registeredProviders = {};
           this.registerPaymentProviders();
         }
 
         // We save the message target message manager so we can later dispatch
         // back messages without broadcasting to all child processes.
-        this.messageManagers[this.requestId] = aMessage.target;
+        let requestId = msg.requestId;
+        this.messageManagers[requestId] = aMessage.target;
 
         // We check the jwt type and look for a match within the
         // registered payment providers to get the correct payment request
         // information.
         let paymentRequests = [];
         let jwtTypes = [];
         for (let i in msg.jwts) {
-          let pr = this.getPaymentRequestInfo(msg.jwts[i]);
+          let pr = this.getPaymentRequestInfo(requestId, msg.jwts[i]);
           if (!pr) {
             continue;
           }
           if (!(pr instanceof Ci.nsIDOMPaymentRequestInfo)) {
             return;
           }
           // We consider jwt type repetition an error.
           if (jwtTypes[pr.type]) {
-            this.paymentFailed("PAY_REQUEST_ERROR_DUPLICATED_JWT_TYPE");
+            this.paymentFailed(requestId,
+                               "PAY_REQUEST_ERROR_DUPLICATED_JWT_TYPE");
             return;
           }
           jwtTypes[pr.type] = true;
           paymentRequests.push(pr);
         }
 
         if (!paymentRequests.length) {
-          this.paymentFailed("PAY_REQUEST_ERROR_NO_VALID_REQUEST_FOUND");
+          this.paymentFailed(requestId,
+                             "PAY_REQUEST_ERROR_NO_VALID_REQUEST_FOUND");
           return;
         }
 
         // After getting the list of valid payment requests, we ask the user
         // for confirmation before sending any request to any payment provider.
         // If there is more than one choice, we also let the user select the one
         // that he prefers.
         let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                    .createInstance(Ci.nsIPaymentUIGlue);
         if (!glue) {
           debug("Could not create nsIPaymentUIGlue instance");
-          this.paymentFailed("INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
+          this.paymentFailed(requestId,
+                             "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
           return;
         }
 
-        let confirmPaymentSuccessCb = function successCb(aResult) {
+        let confirmPaymentSuccessCb = function successCb(aRequestId,
+                                                         aResult) {
           // Get the appropriate payment provider data based on user's choice.
           let selectedProvider = this.registeredProviders[aResult];
           if (!selectedProvider || !selectedProvider.uri) {
             debug("Could not retrieve a valid provider based on user's " +
                   "selection");
-            this.paymentFailed("INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
+            this.paymentFailed(aRequestId,
+                               "INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
             return;
           }
 
           let jwt;
           for (let i in paymentRequests) {
             if (paymentRequests[i].type == aResult) {
               jwt = paymentRequests[i].jwt;
               break;
             }
           }
           if (!jwt) {
             debug("The selected request has no JWT information associated");
-            this.paymentFailed("INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
+            this.paymentFailed(aRequestId,
+                               "INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
             return;
           }
 
-          this.showPaymentFlow(selectedProvider, jwt);
+          this.showPaymentFlow(aRequestId, selectedProvider, jwt);
         };
 
         let confirmPaymentErrorCb = this.paymentFailed;
 
-        glue.confirmPaymentRequest(paymentRequests,
+        glue.confirmPaymentRequest(requestId,
+                                   paymentRequests,
                                    confirmPaymentSuccessCb.bind(this),
                                    confirmPaymentErrorCb.bind(this));
         break;
       }
       case "Payment:Success":
       case "Payment:Failed": {
-        let mm = this.messageManagers[this.requestId];
+        let mm = this.messageManagers[msg.requestId];
         mm.sendAsyncMessage(name, {
-          requestId: this.requestId,
+          requestId: msg.requestId,
           result: msg.result,
           errorMsg: msg.errorMsg
         });
         break;
       }
     }
   },
 
@@ -198,86 +200,91 @@ let PaymentManager =  {
         debug("An error ocurred registering a payment provider. " + ex);
       }
     }
   },
 
   /**
    * Helper for sending a Payment:Failed message to the parent process.
    */
-  paymentFailed: function paymentFailed(aErrorMsg) {
-    let mm = this.messageManagers[this.requestId];
+  paymentFailed: function paymentFailed(aRequestId, aErrorMsg) {
+    let mm = this.messageManagers[aRequestId];
     mm.sendAsyncMessage("Payment:Failed", {
-      requestId: this.requestId,
+      requestId: aRequestId,
       errorMsg: aErrorMsg
     });
   },
 
   /**
    * Helper function to get the payment request info according to the jwt
    * type. Payment provider's data is stored as a preference.
    */
-  getPaymentRequestInfo: function getPaymentRequestInfo(aJwt) {
+  getPaymentRequestInfo: function getPaymentRequestInfo(aRequestId, aJwt) {
     if (!aJwt) {
-      this.paymentFailed("INTERNAL_ERROR_CALL_WITH_MISSING_JWT");
+      this.paymentFailed(aRequestId, "INTERNAL_ERROR_CALL_WITH_MISSING_JWT");
       return true;
     }
 
     // First thing, we check that the jwt type is an allowed type and has a
     // payment provider flow information associated.
 
     // A jwt string consists in three parts separated by period ('.'): header,
     // payload and signature.
     let segments = aJwt.split('.');
     if (segments.length !== 3) {
       debug("Error getting payment provider's uri. " +
             "Not enough or too many segments");
-      this.paymentFailed("PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
+      this.paymentFailed(aRequestId,
+                         "PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
       return true;
     }
 
     let payloadObject;
     try {
       // We only care about the payload segment, which contains the jwt type
       // that should match with any of the stored payment provider's data and
       // the payment request information to be shown to the user.
       let payload = atob(segments[1]);
       debug("Payload " + payload);
       if (!payload.length) {
-        this.paymentFailed("PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
+        this.paymentFailed(aRequestId, "PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
         return true;
       }
 
       // We get rid off the quotes and backslashes so we can parse the JSON
       // object.
       if (payload.charAt(0) === '"') {
         payload = payload.substr(1);
       }
       if (payload.charAt(payload.length - 1) === '"') {
         payload = payload.slice(0, -1);
       }
       payload = payload.replace(/\\/g, '');
 
       payloadObject = JSON.parse(payload);
       if (!payloadObject) {
-        this.paymentFailed("PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
+        this.paymentFailed(aRequestId,
+                           "PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
         return true;
       }
     } catch (e) {
-      this.paymentFailed("PAY_REQUEST_ERROR_ERROR_DECODING_JWT");
+      this.paymentFailed(aRequestId,
+                         "PAY_REQUEST_ERROR_ERROR_DECODING_JWT");
       return true;
     }
 
     if (!payloadObject.typ) {
-      this.paymentFailed("PAY_REQUEST_ERROR_NO_TYP_PARAMETER");
+      this.paymentFailed(aRequestId,
+                         "PAY_REQUEST_ERROR_NO_TYP_PARAMETER");
       return true;
     }
 
     if (!payloadObject.request) {
-      this.paymentFailed("PAY_REQUEST_ERROR_NO_REQUEST_PARAMETER");
+      this.paymentFailed(aRequestId,
+                         "PAY_REQUEST_ERROR_NO_REQUEST_PARAMETER");
       return true;
     }
 
     // Once we got the jwt 'typ' value we look for a match within the payment
     // providers stored preferences. If the jwt 'typ' is not recognized as one
     // of the allowed values for registered payment providers, we skip the jwt
     // validation but we don't fire any error. This way developers might have
     // a default set of well formed JWTs that might be used in different B2G
@@ -285,56 +292,64 @@ let PaymentManager =  {
     let provider = this.registeredProviders[payloadObject.typ];
     if (!provider) {
       debug("Not registered payment provider for jwt type: " +
             payloadObject.typ);
       return false;
     }
 
     if (!provider.uri || !provider.name) {
-      this.paymentFailed("INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
+      this.paymentFailed(aRequestId,
+                         "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
       return true;
     }
 
     // We only allow https for payment providers uris.
     if (!/^https/.exec(provider.uri.toLowerCase())) {
       // We should never get this far.
       debug("Payment provider uris must be https: " + provider.uri);
-      this.paymentFailed("INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
+      this.paymentFailed(aRequestId,
+                         "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
       return true;
     }
 
     let pldRequest = payloadObject.request;
     let request = Cc["@mozilla.org/payment/request-info;1"]
                   .createInstance(Ci.nsIDOMPaymentRequestInfo);
     if (!request) {
-      this.paymentFailed("INTERNAL_ERROR_ERROR_CREATING_PAY_REQUEST");
+      this.paymentFailed(aRequestId,
+                         "INTERNAL_ERROR_ERROR_CREATING_PAY_REQUEST");
       return true;
     }
     request.wrappedJSObject.init(aJwt,
                                  payloadObject.typ,
                                  provider.name);
     return request;
   },
 
-  showPaymentFlow: function showPaymentFlow(aPaymentProvider, aJwt) {
+  showPaymentFlow: function showPaymentFlow(aRequestId,
+                                            aPaymentProvider,
+                                            aJwt) {
     let paymentFlowInfo = Cc["@mozilla.org/payment/flow-info;1"]
                           .createInstance(Ci.nsIPaymentFlowInfo);
     paymentFlowInfo.uri = aPaymentProvider.uri;
     paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
     paymentFlowInfo.jwt = aJwt;
 
     let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                .createInstance(Ci.nsIPaymentUIGlue);
     if (!glue) {
       debug("Could not create nsIPaymentUIGlue instance");
-      this.paymentFailed("INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
+      this.paymentFailed(aRequestId,
+                         "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
       return false;
     }
-    glue.showPaymentFlow(paymentFlowInfo, this.paymentFailed.bind(this));
+    glue.showPaymentFlow(aRequestId,
+                         paymentFlowInfo,
+                         this.paymentFailed.bind(this));
   },
 
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     if (topic == "xpcom-shutdown") {
       for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
         ppmm.removeMessageListener(msgname, this);
--- a/dom/payment/interfaces/nsIPaymentUIGlue.idl
+++ b/dom/payment/interfaces/nsIPaymentUIGlue.idl
@@ -1,28 +1,30 @@
 /* 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/. */
 
 #include "nsISupports.idl"
 
 interface nsIPaymentFlowInfo;
 
-[scriptable, function, uuid(ca475754-6852-49a2-97e8-8a94cc7a453f)]
+[scriptable, function, uuid(b9afa678-71a5-4975-bcdb-0c4098730eff)]
 interface nsIPaymentUIGlueCallback : nsISupports
 {
-    void onresult(in DOMString result);
+    void onresult(in DOMString requestId, in DOMString result);
 };
 
-[scriptable, uuid(344e5442-7cc2-4636-bc42-e2249cb67813)]
+[scriptable, uuid(4dda9aa0-df88-4dcd-a583-199e516fa438)]
 interface nsIPaymentUIGlue : nsISupports
 {
     // The 'paymentRequestsInfo' contains the payment request information
     // for each JWT provided via navigator.mozPay call.
-    void confirmPaymentRequest(in jsval paymentRequestsInfo,
+    void confirmPaymentRequest(in DOMString requestId,
+                               in jsval paymentRequestsInfo,
                                in nsIPaymentUIGlueCallback successCb,
                                in nsIPaymentUIGlueCallback errorCb);
 
-    void showPaymentFlow(in nsIPaymentFlowInfo paymentFlowInfo,
+    void showPaymentFlow(in DOMString requestId,
+                         in nsIPaymentFlowInfo paymentFlowInfo,
                          in nsIPaymentUIGlueCallback errorCb);
 
     void cleanup();
 };