Bug 1018320 - RequestSync API - patch 5 - mozSetMessageHandlerPromise, r=fabrice
☠☠ backed out by 636498d041b5 ☠ ☠
authorAndrea Marchesini <amarchesini@mozilla.com>
Sun, 04 Jan 2015 10:37:11 +0100
changeset 221957 8c01c134e40f0c73b8841e2210b17333944e4804
parent 221956 46353577ef7a88f1b3032e6ea04ac94d14b58f44
child 221958 bce9ed290dddb7492cf3b333751589af21c4a563
push id53474
push useramarchesini@mozilla.com
push dateSun, 04 Jan 2015 09:38:35 +0000
treeherdermozilla-inbound@2ef1c26d77d3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1018320
milestone37.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 5 - mozSetMessageHandlerPromise, r=fabrice
dom/base/Navigator.cpp
dom/base/Navigator.h
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_promise_app.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/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 });
+    aDispatcher.isHandling = false;
+    this._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,35 @@ 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";
+    }
+
+    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,11 @@ 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]
+[test_promise_app.html]
+run-if = buildapp == 'b2g' && toolkit == 'gonk'
--- 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>
new file mode 100644
--- /dev/null
+++ b/dom/requestsync/tests/test_promise_app.html
@@ -0,0 +1,138 @@
+<!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">
+
+  var foobarCounter = 0;
+  var pendingCounter = 0;
+  function setMessageHandler() {
+    navigator.mozSetMessageHandler('request-sync', function(e) {
+
+      if (e.task == 'foobar') {
+        ok(true, "foobar message received:" + foobarCounter);
+
+        if (++foobarCounter == 1) {
+          // The first time we wait 2 seconds.
+          info("Setting a promise object.");
+          navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {
+            setTimeout(a, 2000);
+          }));
+        } else {
+          // The second time we don't reply at all.
+          navigator.mozSetMessageHandlerPromise(new Promise(function(a, b) {}));
+        }
+      }
+
+      else if (e.task  == 'pending') {
+        ok(true, "pending message received: " + pendingCounter);
+        if (++pendingCounter == 2) {
+          runTests();
+        }
+      }
+
+      else {
+      ok(false, "Unknown message");
+      }
+    });
+
+    runTests();
+  }
+
+  function test_register_foobar() {
+    navigator.sync.register('foobar', { minInterval: 5,
+                                        oneShot: false,
+                                        data: 42,
+                                        wifiOnly: false,
+                                        wakeUpPage: location.href }).then(
+    function() {
+      ok(true, "navigator.sync.register() foobar done");
+      runTests();
+    }, genericError);
+  }
+
+  function test_register_pending() {
+    navigator.sync.register('pending', { minInterval: 6,
+                                         oneShot: false,
+                                         data: 'hello world!',
+                                         wifiOnly: false,
+                                         wakeUpPage: location.href }).then(
+    function() {
+      ok(true, "navigator.sync.register() pending done");
+      runTests();
+    }, genericError);
+  }
+
+  function test_unregister_foobar() {
+    navigator.sync.unregister('foobar').then(
+    function() {
+      ok(true, "navigator.sync.unregister() foobar done");
+      runTests();
+    }, genericError);
+  }
+
+  function test_unregister_pending() {
+    navigator.sync.unregister('pending').then(
+    function() {
+      ok(true, "navigator.sync.unregister() pending 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);
+    },
+
+    function() {
+      if (SpecialPowers.isMainProcess()) {
+        SpecialPowers.Cu.import("resource://gre/modules/RequestSyncService.jsm");
+      }
+      runTests();
+    },
+
+    setMessageHandler,
+
+    test_register_foobar,
+    test_register_pending,
+
+    test_wait,
+
+    test_unregister_foobar,
+    test_unregister_pending,
+  ];
+
+  function runTests() {
+    if (!tests.length) {
+      SimpleTest.finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  runTests();
+  </script>
+</body>
+</html>
--- a/dom/requestsync/tests/test_wakeUp.html
+++ b/dom/requestsync/tests/test_wakeUp.html
@@ -79,16 +79,32 @@
                                             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],
@@ -108,16 +124,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;
 };