Bug 1018320 - RequestSync API - patch 3 - a Promise return value from sendAsyncMessage, r=fabrice
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 13 Jan 2015 09:53:18 +0000
changeset 223538 b729cdfd5a8cabafbf6ec894fdfc33b47cf81687
parent 223537 2103910871480ce2a8c86ca5f997657b639ce709
child 223539 9706b535ae6483ef4f53b657ed307c69b733b50d
push id53943
push useramarchesini@mozilla.com
push dateTue, 13 Jan 2015 09:53:46 +0000
treeherdermozilla-inbound@f15ef701bdf2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1018320
milestone38.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 1018320 - RequestSync API - patch 3 - a Promise return value from sendAsyncMessage, r=fabrice
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.