Bug 1018320 - RequestSync API - Patch 3 - A Promise return value from sendAsyncMessage. r=fabrice, a=bajaj
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 13 Jan 2015 09:53:18 +0000
changeset 237137 0fd5710ce15e3c46b228a6e4c47dc3277923508a
parent 237136 32d904a7d04c836568a1e915198f449ae1ea5306
child 237138 dda5de2f1e9fdf270f6cb79b0c60ed69b239e08f
push id213
push userryanvm@gmail.com
push dateTue, 24 Feb 2015 00:59:48 +0000
treeherdermozilla-b2g37_v2_2@b5a532c7f606 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, bajaj
bugs1018320
milestone37.0
Bug 1018320 - RequestSync API - Patch 3 - A Promise return value from sendAsyncMessage. r=fabrice, a=bajaj
dom/messages/SystemMessageInternal.js
dom/messages/SystemMessageManager.js
dom/messages/interfaces/nsISystemMessagesInternal.idl
--- a/dom/messages/SystemMessageInternal.js
+++ b/dom/messages/SystemMessageInternal.js
@@ -41,16 +41,17 @@ try {
 
 const kMessages =["SystemMessageManager:GetPendingMessages",
                   "SystemMessageManager:HasPendingMessages",
                   "SystemMessageManager:Register",
                   "SystemMessageManager:Unregister",
                   "SystemMessageManager:Message:Return:OK",
                   "SystemMessageManager:AskReadyToRegister",
                   "SystemMessageManager:HandleMessagesDone",
+                  "SystemMessageManager:HandleMessageDone",
                   "child-process-shutdown"]
 
 function debug(aMsg) {
   // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n");
 }
 
 
 let defaultMessageConfigurator = {
@@ -79,16 +80,18 @@ function SystemMessageInternal() {
 
   this._webappsRegistryReady = false;
   this._bufferedSysMsgs = [];
 
   this._cpuWakeLocks = {};
 
   this._configurators = {};
 
+  this._pendingPromises = new Map();
+
   Services.obs.addObserver(this, "xpcom-shutdown", false);
   Services.obs.addObserver(this, "webapps-registry-start", false);
   Services.obs.addObserver(this, "webapps-registry-ready", false);
   Services.obs.addObserver(this, "webapps-clear-data", false);
   kMessages.forEach(function(aMsg) {
     ppmm.addMessageListener(aMsg, this);
   }, this);
 
@@ -171,33 +174,48 @@ SystemMessageInternal.prototype = {
         page = aPage;
       }
       return page !== null;
     }, this);
     return page;
   },
 
   sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) {
+    return new Promise((aResolve, aReject) => {
+      this.sendMessageInternal(aType, aMessage, aPageURI, aManifestURI, aExtra,
+                               aResolve, aReject);
+    });
+  },
+
+  sendMessageInternal: function(aType, aMessage, aPageURI, aManifestURI,
+                                aExtra, aResolvePromiseCb, aRejectPromiseCb) {
     // Buffer system messages until the webapps' registration is ready,
     // so that we can know the correct pages registered to be sent.
     if (!this._webappsRegistryReady) {
       this._bufferedSysMsgs.push({ how: "send",
                                    type: aType,
                                    msg: aMessage,
                                    pageURI: aPageURI,
                                    manifestURI: aManifestURI,
-                                   extra: aExtra });
+                                   extra: aExtra,
+                                   resolvePromiseCb: aResolvePromiseCb,
+                                   rejectPromiseCb: aRejectPromiseCb });
       return;
     }
 
     // Give this message an ID so that we can identify the message and
     // clean it up from the pending message queue when apps receive it.
     let messageID = gUUIDGenerator.generateUUID().toString();
+    let manifestURL = aManifestURI.spec;
 
-    let manifestURL = aManifestURI.spec;
+    let pendingPromise = { resolvePromiseCb: aResolvePromiseCb,
+                           rejectPromiseCb: aRejectPromiseCb,
+                           manifestURL: manifestURL,
+                           counter: 0 };
+
     let pageURLs = [];
     if (aPageURI) {
       pageURLs.push(aPageURI.spec);
     } else {
       // Send this message to all the registered pages of the app if |aPageURI|
       // is not specified.
       for (let i = 0; i < this._pages.length; i++) {
         let page = this._pages[i];
@@ -221,24 +239,32 @@ SystemMessageInternal.prototype = {
       debug("Returned status of sending message: " + result);
 
       // Don't need to open the pages and queue the system message
       // which was not allowed to be sent.
       if (result === MSG_SENT_FAILURE_PERM_DENIED) {
         return;
       }
 
+      // For each page we must receive a confirm.
+      ++pendingPromise.counter;
+
       let page = this._findPage(aType, aPageURL, manifestURL);
       if (page) {
         // Queue this message in the corresponding pages.
         this._queueMessage(page, aMessage, messageID);
 
         this._openAppPage(page, aMessage, aExtra, result);
       }
+
     }, this);
+
+    if (pendingPromise.counter) {
+      this._pendingPromises.set(messageID, pendingPromise);
+    }
   },
 
   broadcastMessage: function(aType, aMessage, aExtra) {
     // Buffer system messages until the webapps' registration is ready,
     // so that we can know the correct pages registered to be broadcasted.
     if (!this._webappsRegistryReady) {
       this._bufferedSysMsgs.push({ how: "broadcast",
                                    type: aType,
@@ -388,17 +414,18 @@ SystemMessageInternal.prototype = {
     // To prevent the hacked child process from sending commands to parent
     // to manage system messages, we need to check its manifest URL.
     if (["SystemMessageManager:Register",
          // TODO: fix bug 988142 to re-enable.
          // "SystemMessageManager:Unregister",
          "SystemMessageManager:GetPendingMessages",
          "SystemMessageManager:HasPendingMessages",
          "SystemMessageManager:Message:Return:OK",
-         "SystemMessageManager:HandleMessagesDone"].indexOf(aMessage.name) != -1) {
+         "SystemMessageManager:HandleMessagesDone",
+         "SystemMessageManager:HandleMessageDone"].indexOf(aMessage.name) != -1) {
       if (!aMessage.target.assertContainApp(msg.manifestURL)) {
         debug("Got message from a child process containing illegal manifest URL.");
         return null;
       }
     }
 
     switch(aMessage.name) {
       case "SystemMessageManager:AskReadyToRegister":
@@ -437,27 +464,30 @@ SystemMessageInternal.prototype = {
       {
         debug("Got child-process-shutdown from " + aMessage.target);
         for (let manifestURL in this._listeners) {
           // See if any processes in this manifest URL have this target.
           this._removeTargetFromListener(aMessage.target,
                                          manifestURL,
                                          true,
                                          null);
+
+          this._rejectPendingPromises(manifestURL);
         }
         break;
       }
       case "SystemMessageManager:Unregister":
       {
         debug("Got Unregister from " + aMessage.target +
               " innerWinID " + msg.innerWindowID);
         this._removeTargetFromListener(aMessage.target,
                                        msg.manifestURL,
                                        false,
                                        msg.pageURL);
+        this._rejectPendingPromises(msg.manifestURL);
         break;
       }
       case "SystemMessageManager:GetPendingMessages":
       {
         debug("received SystemMessageManager:GetPendingMessages " + msg.type +
           " for " + msg.pageURL + " @ " + msg.manifestURL);
 
         // This is a sync call used to return the pending messages for a page.
@@ -515,16 +545,31 @@ SystemMessageInternal.prototype = {
             if (pendingMessages[i].msgID === msg.msgID) {
               pendingMessages.splice(i, 1);
               break;
             }
           }
         }
         break;
       }
+      case "SystemMessageManager:HandleMessageDone":
+      {
+        debug("received SystemMessageManager:HandleMessageDone " + msg.type +
+          " with msgID " + msg.msgID + " for " + msg.pageURL +
+          " @ " + msg.manifestURL);
+
+        // Maybe this should resolve a pending promise.
+        this._resolvePendingPromises(msg.msgID);
+
+        // A page has finished handling some of its system messages, so we try
+        // to release the CPU wake lock we acquired on behalf of that page.
+        this._releaseCpuWakeLock(this._createKeyForPage(msg), 1);
+        break;
+      }
+
       case "SystemMessageManager:HandleMessagesDone":
       {
         debug("received SystemMessageManager:HandleMessagesDone " + msg.type +
           " with " + msg.handledCount + " for " + msg.pageURL +
           " @ " + msg.manifestURL);
 
         // A page has finished handling some of its system messages, so we try
         // to release the CPU wake lock we acquired on behalf of that page.
@@ -542,30 +587,32 @@ SystemMessageInternal.prototype = {
         }, this);
         Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.obs.removeObserver(this, "webapps-registry-start");
         Services.obs.removeObserver(this, "webapps-registry-ready");
         Services.obs.removeObserver(this, "webapps-clear-data");
         ppmm = null;
         this._pages = null;
         this._bufferedSysMsgs = null;
+        this._pendingPromises.clear();
         break;
       case "webapps-registry-start":
         this._webappsRegistryReady = false;
         break;
       case "webapps-registry-ready":
         // After the webapps' registration has been done for sure,
         // re-fire the buffered system messages if there is any.
         this._webappsRegistryReady = true;
         this._bufferedSysMsgs.forEach(function(aSysMsg) {
           switch (aSysMsg.how) {
             case "send":
-              this.sendMessage(
+              this.sendMessageInternal(
                 aSysMsg.type, aSysMsg.msg,
-                aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra);
+                aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra,
+                aSysMsg.resolvePromiseCb, aSysMsg.rejectPromiseCb);
               break;
             case "broadcast":
               this.broadcastMessage(aSysMsg.type, aSysMsg.msg, aSysMsg.extra);
               break;
           }
         }, this);
         this._bufferedSysMsgs.length = 0;
         break;
@@ -591,16 +638,19 @@ SystemMessageInternal.prototype = {
         for (let i = this._pages.length - 1; i >= 0; i--) {
           let page = this._pages[i];
           if (page.manifestURL === manifestURL) {
             this._pages.splice(i, 1);
             debug("Remove " + page.pageURL + " @ " + page.manifestURL +
                   " from registered pages due to app uninstallation.");
           }
         }
+
+        this._rejectPendingPromises(manifestURL);
+
         debug("Finish updating registered pages for an uninstalled app.");
         break;
     }
   },
 
   _queueMessage: function(aPage, aMessage, aMessageID) {
     // Queue the message for this page because we've never known if an app is
     // opened or not. We'll clean it up when the app has already received it.
@@ -687,19 +737,20 @@ SystemMessageInternal.prototype = {
         // which contain the window page that matches the manifest/page URL of
         // the destination of system message.
         if (target.winCounts[aPageURL] === undefined) {
           continue;
         }
 
         appPageIsRunning = true;
         // We need to acquire a CPU wake lock for that page and expect that
-        // we'll receive a "SystemMessageManager:HandleMessagesDone" message
-        // when the page finishes handling the system message. At that point,
-        // we'll release the lock we acquired.
+        // we'll receive a "SystemMessageManager:HandleMessagesDone" or a
+        // "SystemMessageManager:HandleMessageDone"  message when the page
+        // finishes handling the system message. At that point, we'll release
+        // the lock we acquired.
         this._acquireCpuWakeLock(pageKey);
 
         // Multiple windows can share the same target (process), the content
         // window needs to check if the manifest/page URL is matched. Only
         // *one* window should handle the system message.
         let manager = target.target;
         manager.sendAsyncMessage("SystemMessageManager:Message",
                                  { type: aType,
@@ -709,25 +760,48 @@ SystemMessageInternal.prototype = {
                                    msgID: aMessageID });
       }
     }
 
     if (!appPageIsRunning) {
       // The app page isn't running and relies on the 'open-app' chrome event to
       // wake it up. We still need to acquire a CPU wake lock for that page and
       // expect that we will receive a "SystemMessageManager:HandleMessagesDone"
-      // message when the page finishes handling the system message with other
-      // pending messages. At that point, we'll release the lock we acquired.
+      // or a "SystemMessageManager:HandleMessageDone" message when the page
+      // finishes handling the system message with other pending messages. At
+      // that point, we'll release the lock we acquired.
       this._acquireCpuWakeLock(pageKey);
       return MSG_SENT_FAILURE_APP_NOT_RUNNING;
     } else {
       return MSG_SENT_SUCCESS;
     }
 
   },
 
+  _resolvePendingPromises: function(aMessageID) {
+    if (!this._pendingPromises.has(aMessageID)) {
+      debug("Unknown pendingPromise messageID. This seems a bug!!");
+      return;
+    }
+
+    let obj = this._pendingPromises.get(aMessageID);
+    if (!--obj.counter) {
+      obj.resolvePromiseCb();
+      this._pendingPromises.delete(aMessageID);
+    }
+  },
+
+  _rejectPendingPromises: function(aManifestURL) {
+    for (var [i, obj] of this._pendingPromises) {
+      if (obj.manifestURL == aManifestURL) {
+        obj.rejectPromiseCb();
+        this._pendingPromises.delete(i);
+      }
+    }
+  },
+
   classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal,
                                          Ci.nsIObserver])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]);
--- a/dom/messages/SystemMessageManager.js
+++ b/dom/messages/SystemMessageManager.js
@@ -52,26 +52,26 @@ function SystemMessageManager() {
   if (this._isParentProcess) {
     Services.obs.addObserver(this, kSystemMessageInternalReady, false);
   }
 }
 
 SystemMessageManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
-  _dispatchMessage: function(aType, aDispatcher, aMessage) {
+  _dispatchMessage: function(aType, aDispatcher, aMessage, aMessageID) {
     if (aDispatcher.isHandling) {
       // Queue up the incomming message if we're currently dispatching a
       // message; we'll send the message once we finish with the current one.
       //
       // _dispatchMethod is reentrant because a page can spin up a nested
       // event loop from within a system message handler (e.g. via alert()),
       // and we can then try to send the page another message while it's
       // inside this nested event loop.
-      aDispatcher.messages.push(aMessage);
+      aDispatcher.messages.push({ message: aMessage, messageID: aMessageID });
       return;
     }
 
     aDispatcher.isHandling = true;
 
     // We get a json blob, but in some cases we want another kind of object
     // to be dispatched. To do so, we check if we have a valid contract ID of
     // "@mozilla.org/dom/system-messages/wrapper/TYPE;1" component implementing
@@ -91,26 +91,27 @@ SystemMessageManager.prototype = {
     }
 
     aDispatcher.handler
       .handleMessage(wrapped ? aMessage
                              : Cu.cloneInto(aMessage, this._window));
 
     // We need to notify the parent one of the system messages has been handled,
     // so the parent can release the CPU wake lock it took on our behalf.
-    cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
+    cpmm.sendAsyncMessage("SystemMessageManager:HandleMessageDone",
                           { type: aType,
                             manifestURL: this._manifestURL,
                             pageURL: this._pageURL,
-                            handledCount: 1 });
+                            msgID: aMessageID });
 
     aDispatcher.isHandling = false;
 
     if (aDispatcher.messages.length > 0) {
-      this._dispatchMessage(aType, aDispatcher, aDispatcher.messages.shift());
+      let msg = aDispatcher.messages.shift();
+      this._dispatchMessage(aType, aDispatcher, msg.message, msg.messageID);
     } else {
       // No more messages that need to be handled, we can notify the
       // ContentChild to release the CPU wake lock grabbed by the ContentParent
       // (i.e. NewWakeLockOnBehalfOfProcess()) and reset the process's priority.
       //
       // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
       Services.obs.notifyObservers(/* aSubject */ null,
                                    "handle-system-messages-done",
@@ -231,18 +232,19 @@ SystemMessageManager.prototype = {
         cpmm.sendAsyncMessage("SystemMessageManager:Message:Return:OK",
                               { type: msg.type,
                                 manifestURL: this._manifestURL,
                                 pageURL: this._pageURL,
                                 msgID: msg.msgID });
       }
 
       messages.forEach(function(aMsg) {
-        this._dispatchMessage(msg.type, dispatcher, aMsg);
+        this._dispatchMessage(msg.type, dispatcher, aMsg, msg.msgID);
       }, this);
+
     } else {
       // Since no handlers are registered, we need to notify the parent as if
       // all the queued system messages have been handled (notice |handledCount:
       // messages.length|), so the parent can release the CPU wake lock it took
       // on our behalf.
       cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
                             { type: msg.type,
                               manifestURL: this._manifestURL,
--- a/dom/messages/interfaces/nsISystemMessagesInternal.idl
+++ b/dom/messages/interfaces/nsISystemMessagesInternal.idl
@@ -4,31 +4,34 @@
 
 #include "domstubs.idl"
 
 interface nsIURI;
 interface nsIDOMWindow;
 
 // Implemented by the contract id @mozilla.org/system-message-internal;1
 
-[scriptable, uuid(6296a314-2abf-4cd0-9097-5e81ee6832e2)]
+[scriptable, uuid(54c8e274-decb-4258-9a24-4ebfcbf3d00a)]
 interface nsISystemMessagesInternal : nsISupports
 {
   /*
    * Allow any internal user to send a message of a given type to a given page
    * of an app. The message will be sent to all the registered pages of the app
    * when |pageURI| is not specified.
    * @param type        The type of the message to be sent.
    * @param message     The message payload.
    * @param pageURI     The URI of the page that will be opened. Nullable.
    * @param manifestURI The webapp's manifest URI.
    * @param extra       Extra opaque information that will be passed around in the observer
    *                    notification to open the page.
+   * returns a Promise
    */
-  void sendMessage(in DOMString type, in jsval message, in nsIURI pageURI, in nsIURI manifestURI, [optional] in jsval extra);
+  nsISupports sendMessage(in DOMString type, in jsval message,
+                          in nsIURI pageURI, in nsIURI manifestURI,
+                          [optional] in jsval extra);
 
   /*
    * Allow any internal user to broadcast a message of a given type.
    * The application that registers the message will be launched.
    * @param type        The type of the message to be sent.
    * @param message     The message payload.
    * @param extra       Extra opaque information that will be passed around in the observer
    *                    notification to open the page.