Bug 1018320 - RequestSync API - Patch 5 - mozSetMessageHandlerPromise. r=fabrice, a=bajaj
authorAndrea Marchesini <amarchesini@mozilla.com>
Tue, 13 Jan 2015 09:53:22 +0000
changeset 237139 bfc99f8c73b62e51334c2cfd5e952f34e37e7573
parent 237138 dda5de2f1e9fdf270f6cb79b0c60ed69b239e08f
child 237140 d283ce0f09967d1c5296c34e135fa8d5a8622596
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 5 - mozSetMessageHandlerPromise. r=fabrice, a=bajaj
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/media/test/test_info_leak.html
dom/messages/SystemMessageManager.js
dom/messages/interfaces/nsIDOMNavigatorSystemMessages.idl
dom/requestsync/RequestSyncService.jsm
dom/requestsync/tests/mochitest.ini
dom/requestsync/tests/test_basic_app.html
dom/requestsync/tests/test_promise.html
dom/requestsync/tests/test_wakeUp.html
dom/webidl/Navigator.webidl
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1843,16 +1843,43 @@ Navigator::MozHasPendingMessage(const ns
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return false;
   }
   return result;
 }
 
 void
+Navigator::MozSetMessageHandlerPromise(Promise& aPromise,
+                                       ErrorResult& aRv)
+{
+  // The WebIDL binding is responsible for the pref check here.
+  aRv = EnsureMessagesManager();
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  bool result = false;
+  aRv = mMessagesManager->MozIsHandlingMessage(&result);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  if (!result) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+    return;
+  }
+
+  aRv = mMessagesManager->MozSetMessageHandlerPromise(&aPromise);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+}
+
+void
 Navigator::MozSetMessageHandler(const nsAString& aType,
                                 systemMessageCallback* aCallback,
                                 ErrorResult& aRv)
 {
   // The WebIDL binding is responsible for the pref check here.
   nsresult rv = EnsureMessagesManager();
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -230,16 +230,18 @@ public:
   TVManager* GetTv();
   network::Connection* GetConnection(ErrorResult& aRv);
   nsDOMCameraManager* GetMozCameras(ErrorResult& aRv);
   MediaDevices* GetMediaDevices(ErrorResult& aRv);
   void MozSetMessageHandler(const nsAString& aType,
                             systemMessageCallback* aCallback,
                             ErrorResult& aRv);
   bool MozHasPendingMessage(const nsAString& aType, ErrorResult& aRv);
+  void MozSetMessageHandlerPromise(Promise& aPromise, ErrorResult& aRv);
+
 #ifdef MOZ_B2G
   already_AddRefed<Promise> GetMobileIdAssertion(const MobileIdOptions& options,
                                                  ErrorResult& aRv);
 #endif
 #ifdef MOZ_B2G_RIL
   MobileConnectionArray* GetMozMobileConnections(ErrorResult& aRv);
   IccManager* GetMozIccManager(ErrorResult& aRv);
 #endif // MOZ_B2G_RIL
--- a/dom/media/test/test_info_leak.html
+++ b/dom/media/test/test_info_leak.html
@@ -89,17 +89,17 @@ function listener(evt) {
     // other events to come in. Note: we don't expect any events to come
     // in, unless we've leaked some info, and 1 second should be enough time
     // for the leak to show up.
     setTimeout(function() {finish(v);}, 1000); 
   }
 }
 
 function createMedia(type, src, token) {
-  var tag = getMajorMimeType(test.type);
+  var tag = getMajorMimeType(type);
   var v = document.createElement(tag);
   for (var i=0; i<gEventTypes.length; i++) {
     v.addEventListener(gEventTypes[i], listener, false);
   }
   v.preload = "metadata";
   v.src = src;
   v.name = src;
   document.body.appendChild(v);
--- a/dom/messages/SystemMessageManager.js
+++ b/dom/messages/SystemMessageManager.js
@@ -37,16 +37,20 @@ function SystemMessageManager() {
   this._dispatchers = {};
 
   // Pending messages for this page, keyed by message type.
   this._pendings = {};
 
   // Flag to specify if this process has already registered the manifest URL.
   this._registerManifestURLReady = false;
 
+  // Used to know if the promise has to be accepted or not.
+  this._isHandling = false;
+  this._promise = null;
+
   // Flag to determine this process is a parent or child process.
   let appInfo = Cc["@mozilla.org/xre/app-info;1"];
   this._isParentProcess =
     !appInfo || appInfo.getService(Ci.nsIXULRuntime)
                   .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
 
   // An oberver to listen to whether the |SystemMessageInternal| is ready.
   if (this._isParentProcess) {
@@ -66,16 +70,17 @@ SystemMessageManager.prototype = {
       // 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({ message: aMessage, messageID: aMessageID });
       return;
     }
 
     aDispatcher.isHandling = true;
+    this._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
     // nsISystemMessageWrapper.
     debug("Dispatching " + JSON.stringify(aMessage) + "\n");
     let contractID = "@mozilla.org/dom/system-messages/wrapper/" + aType + ";1";
     let wrapped = false;
@@ -89,25 +94,39 @@ SystemMessageManager.prototype = {
         debug("wrapped = " + aMessage);
       }
     }
 
     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:HandleMessageDone",
-                          { type: aType,
-                            manifestURL: this._manifestURL,
-                            pageURL: this._pageURL,
-                            msgID: aMessageID });
+    this._isHandling = false;
+    aDispatcher.isHandling = false;
 
-    aDispatcher.isHandling = false;
+    let self = this;
+    function sendResponse() {
+      // 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:HandleMessageDone",
+                            { type: aType,
+                              manifestURL: self._manifestURL,
+                              pageURL: self._pageURL,
+                              msgID: aMessageID });
+    }
+
+    if (!this._promise) {
+      debug("No promise set, sending the response immediately.");
+      sendResponse();
+    } else {
+      debug("Using the promise to postpone the response.");
+      this._promise.then(sendResponse, sendResponse);
+      this._promise = null;
+    }
 
     if (aDispatcher.messages.length > 0) {
       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.
@@ -166,19 +185,39 @@ SystemMessageManager.prototype = {
     }
 
     return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
                                 { type: aType,
                                   pageURL: this._pageURL,
                                   manifestURL: this._manifestURL })[0];
   },
 
+  mozIsHandlingMessage: function() {
+    debug("is handling message: " + this._isHandling);
+    return this._isHandling;
+  },
+
+  mozSetMessageHandlerPromise: function(aPromise) {
+    debug("setting a promise");
+
+    if (!this._isHandling) {
+      throw "Not in a handleMessage method";
+    }
+
+    if (this._promise) {
+      throw "Promise already set";
+    }
+
+    this._promise = aPromise;
+  },
+
   uninit: function()  {
     this._dispatchers = null;
     this._pendings = null;
+    this._promise = null;
 
     if (this._isParentProcess) {
       Services.obs.removeObserver(this, kSystemMessageInternalReady);
     }
 
     if (this._isInBrowserElement) {
       debug("the app loaded in the browser doesn't need to unregister " +
             "the manifest URL for listening to the system messages");
--- a/dom/messages/interfaces/nsIDOMNavigatorSystemMessages.idl
+++ b/dom/messages/interfaces/nsIDOMNavigatorSystemMessages.idl
@@ -5,15 +5,20 @@
 #include "domstubs.idl"
 
 [scriptable, function, uuid(42692976-57fd-4bb4-ab95-2b97ebdc5056)]
 interface nsIDOMSystemMessageCallback : nsISupports
 {
     void handleMessage(in jsval message);
 };
 
-[scriptable, uuid(091e90dd-0e8b-463d-8cdc-9225d3a6ff90)]
+[scriptable, uuid(d04d3c11-26aa-46eb-a981-353af101f9cf)]
 interface nsIDOMNavigatorSystemMessages : nsISupports
 {
     void mozSetMessageHandler(in DOMString type, in nsIDOMSystemMessageCallback callback);
 
     boolean mozHasPendingMessage(in DOMString type); 
+
+    // the parameter is a promise object.
+    void mozSetMessageHandlerPromise(in nsISupports promise);
+
+    bool mozIsHandlingMessage();
 };
--- a/dom/requestsync/RequestSyncService.jsm
+++ b/dom/requestsync/RequestSyncService.jsm
@@ -582,20 +582,26 @@ this.RequestSyncService = {
         done = true;
         self.operationCompleted();
       }
 
       timer.cancel();
       timer = null;
     }
 
+    let timeout = RSYNC_OPERATION_TIMEOUT;
+    try {
+      let tmp = Services.prefs.getIntPref("dom.requestSync.maxTaskTimeout");
+      timeout = tmp;
+    } catch(e) {}
+
     timer.initWithCallback(function() {
       debug("Task is taking too much, let's ignore the promise.");
       taskCompleted();
-    }, RSYNC_OPERATION_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+    }, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
 
     // Sending the message.
     let promise =
       systemMessenger.sendMessage('request-sync',
                                   this.createPartialTaskObject(aObj.data),
                                   pageURL, manifestURL);
 
     promise.then(function() {
--- a/dom/requestsync/tests/mochitest.ini
+++ b/dom/requestsync/tests/mochitest.ini
@@ -9,8 +9,9 @@ support-files =
 
 [test_webidl.html]
 [test_minInterval.html]
 [test_basic.html]
 [test_basic_app.html]
 run-if = buildapp != 'b2g'
 [test_wakeUp.html]
 run-if = buildapp == 'b2g' && toolkit == 'gonk'
+[test_promise.html]
--- a/dom/requestsync/tests/test_basic_app.html
+++ b/dom/requestsync/tests/test_basic_app.html
@@ -67,17 +67,17 @@
   }
 
   var tests = [
     // Permissions
     function() {
       SpecialPowers.pushPermissions(
         [{ "type": "browser", "allow": 1, "context": document },
          { "type": "embed-apps", "allow": 1, "context": document },
-         {"type": "requestsync-manager", "allow": 1, "context": document },
+         { "type": "requestsync-manager", "allow": 1, "context": document },
          { "type": "webapps-manage", "allow": 1, "context": document }], runTests);
     },
 
     // Preferences
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
                                          ["dom.requestSync.minInterval", 1],
                                          ["dom.ignore_webidl_scope_checks", true],
new file mode 100644
--- /dev/null
+++ b/dom/requestsync/tests/test_promise.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for requestSync - promise</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common_basic.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="container"></div>
+  <script type="application/javascript;version=1.7">
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [["dom.sysmsg.enabled", true]]}, function() {
+
+  ok("mozSetMessageHandlerPromise" in navigator, "mozSetMessageHandlerPromise exists");
+
+  var status = false;
+  try {
+    navigator.mozSetMessageHandlerPromise();
+  } catch(e) {
+    status = true;
+  }
+  ok(status, "mozSetMessageHandlerPromise wants a promise 1");
+
+  status = false;
+  try {
+    navigator.mozSetMessageHandlerPromise(42);
+  } catch(e) {
+    status = true;
+  }
+  ok(status, "mozSetMessageHandlerPromise wants a promise 2");
+
+  status = false;
+  try {
+    navigator.mozSetMessageHandlerPromise("hello world");
+  } catch(e) {
+    status = true;
+  }
+  ok(status, "mozSetMessageHandlerPromise wants a promise 3");
+
+  status = false;
+  try {
+    navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {}));
+  } catch(e) {
+    info(e);
+    status = true;
+  }
+  ok(status, "mozSetMessageHandlerPromise cannot be called outside a messageHandler");
+  SimpleTest.finish();
+});
+
+  </script>
+</body>
+</html>
--- a/dom/requestsync/tests/test_wakeUp.html
+++ b/dom/requestsync/tests/test_wakeUp.html
@@ -44,16 +44,27 @@
           isnot(e.lastSync, 0, "e.lastSync is correct");
         }
 
         is(e.oneShot, false, "e.oneShot is correct");
         is(e.minInterval, 3, "e.minInterval is correct");
         is(e.wifiOnly, false, "e.wifiOnly is correct");
 
         ++multiShotCounter;
+
+        if (multiShotCounter == 1) {
+          info("Setting a promise object.");
+          navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {
+            setTimeout(a, 0);
+          }));
+        } else if (multiShotCounter == 2) {
+          // The second time we don't reply at all.
+          navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {}));
+        }
+
         maybeDone();
       }
 
       else {
         ok(false, "Unknown event has been received!");
       }
     });
 
@@ -79,24 +90,41 @@
                                             wifiOnly: false,
                                             wakeUpPage: location.href }).then(
     function() {
       ok(true, "navigator.sync.register() multiShots done");
       runTests();
     }, genericError);
   }
 
+  function test_unregister_oneShot() {
+    navigator.sync.unregister('oneShot').then(
+    function() {
+      ok(true, "navigator.sync.unregister() oneShot done");
+      runTests();
+    }, genericError);
+  }
+
+  function test_unregister_multiShots() {
+    navigator.sync.unregister('multiShots').then(
+    function() {
+      ok(true, "navigator.sync.unregister() multiShots done");
+      runTests();
+    }, genericError);
+  }
+
   function test_wait() {
     // nothing to do here.
   }
 
   var tests = [
     function() {
       SpecialPowers.pushPrefEnv({"set": [["dom.requestSync.enabled", true],
                                          ["dom.requestSync.minInterval", 1],
+                                         ["dom.requestSync.maxTaskTimeout", 10000 /* 10 seconds */],
                                          ["dom.ignore_webidl_scope_checks", true]]}, runTests);
     },
 
     function() {
       SpecialPowers.pushPermissions(
         [{ "type": "requestsync-manager", "allow": 1, "context": document } ], runTests);
     },
 
@@ -108,16 +136,19 @@
     },
 
     setMessageHandler,
 
     test_register_oneShot,
     test_register_multiShots,
 
     test_wait,
+
+    test_unregister_oneShot,
+    test_unregister_multiShots,
   ];
 
   function runTests() {
     if (!tests.length) {
       SimpleTest.finish();
       return;
     }
 
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -280,16 +280,22 @@ partial interface Navigator {
 // nsIDOMNavigatorSystemMessages and sort of maybe
 // http://www.w3.org/2012/sysapps/runtime/#extension-to-the-navigator-interface-1
 callback systemMessageCallback = void (optional object message);
 partial interface Navigator {
   [Throws, Pref="dom.sysmsg.enabled"]
   void    mozSetMessageHandler (DOMString type, systemMessageCallback? callback);
   [Throws, Pref="dom.sysmsg.enabled"]
   boolean mozHasPendingMessage (DOMString type);
+
+  // This method can be called only from the systeMessageCallback function and
+  // it allows the page to set a promise to keep alive the app until the
+  // current operation is not fully completed.
+  [Throws, Pref="dom.sysmsg.enabled"]
+  void mozSetMessageHandlerPromise (Promise<any> promise);
 };
 
 #ifdef MOZ_B2G_RIL
 partial interface Navigator {
   [Throws, Pref="dom.mobileconnection.enabled", CheckPermissions="mobileconnection mobilenetwork", UnsafeInPrerendering]
   readonly attribute MozMobileConnectionArray mozMobileConnections;
 };