Bug 1055144 - Implement sendRequestForResult. r=wesj
authorBrian Nicholson <bnicholson@mozilla.com>
Tue, 02 Sep 2014 15:53:29 -0700
changeset 203215 b5646b91eff55c17707d654c53d62345b159cecb
parent 203214 4650bedd769150b8d8ecceb9c37cc462a1d19b00
child 203216 29d6f1d247516bb682da30c2e0261aee901967e7
push id27424
push userryanvm@gmail.com
push dateWed, 03 Sep 2014 19:35:53 +0000
treeherdermozilla-central@bfef88becbba [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs1055144
milestone35.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 1055144 - Implement sendRequestForResult. r=wesj sendRequestForResult is a promise-based API for messaging Java. It replaces the callback version of sendMessageToJava.
mobile/android/base/EventDispatcher.java
mobile/android/base/tests/testEventDispatcher.java
mobile/android/base/util/EventCallback.java
mobile/android/modules/Messaging.jsm
testing/mochitest/roboextender/bootstrap.js
--- a/mobile/android/base/EventDispatcher.java
+++ b/mobile/android/base/EventDispatcher.java
@@ -22,17 +22,16 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 @RobocopTarget
 public final class EventDispatcher {
     private static final String LOGTAG = "GeckoEventDispatcher";
     private static final String GUID = "__guid__";
-    private static final String STATUS_CANCEL = "cancel";
     private static final String STATUS_ERROR = "error";
     private static final String STATUS_SUCCESS = "success";
 
     private static final EventDispatcher INSTANCE = new EventDispatcher();
 
     /**
      * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size
      * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to
@@ -195,20 +194,19 @@ public final class EventDispatcher {
 
             List<GeckoEventListener> listeners;
             synchronized (mGeckoThreadJSONListeners) {
                 listeners = mGeckoThreadJSONListeners.get(type);
             }
             if (listeners == null || listeners.size() == 0) {
                 Log.w(LOGTAG, "No listeners for " + type);
 
-                // If there are no listeners, cancel the callback to prevent Gecko-side observers
-                // from being leaked.
+                // If there are no listeners, dispatch an error.
                 if (callback != null) {
-                    callback.sendCancel();
+                    callback.sendError("No listeners for request");
                 }
                 return;
             }
             for (final GeckoEventListener listener : listeners) {
                 listener.handleMessage(type, message);
             }
         } catch (final JSONException e) {
             Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
@@ -253,20 +251,16 @@ public final class EventDispatcher {
         public void sendSuccess(final Object response) {
             sendResponse(STATUS_SUCCESS, response);
         }
 
         public void sendError(final Object response) {
             sendResponse(STATUS_ERROR, response);
         }
 
-        public void sendCancel() {
-            sendResponse(STATUS_CANCEL, null);
-        }
-
         private void sendResponse(final String status, final Object response) {
             if (sent) {
                 throw new IllegalStateException("Callback has already been executed for type=" +
                         type + ", guid=" + guid);
             }
 
             sent = true;
 
--- a/mobile/android/base/tests/testEventDispatcher.java
+++ b/mobile/android/base/tests/testEventDispatcher.java
@@ -63,17 +63,16 @@ public class testEventDispatcher extends
                                          "?path=" + TEST_JS);
 
         js.syncCall("send_test_message", GECKO_EVENT);
         js.syncCall("send_message_for_response", GECKO_RESPONSE_EVENT, "success");
         js.syncCall("send_message_for_response", GECKO_RESPONSE_EVENT, "error");
         js.syncCall("send_test_message", NATIVE_EVENT);
         js.syncCall("send_message_for_response", NATIVE_RESPONSE_EVENT, "success");
         js.syncCall("send_message_for_response", NATIVE_RESPONSE_EVENT, "error");
-        js.syncCall("send_message_for_response", NATIVE_RESPONSE_EVENT, "cancel");
         js.syncCall("send_test_message", NATIVE_EXCEPTION_EVENT);
         js.syncCall("finish_test");
     }
 
     @Override
     public void handleMessage(final String event, final JSONObject message) {
         ThreadUtils.assertOnGeckoThread();
 
@@ -144,18 +143,16 @@ public class testEventDispatcher extends
                 null, message.optBundleArray("nonexistent_objectArray", null));
 
         } else if (NATIVE_RESPONSE_EVENT.equals(event)) {
             final String response = message.getString("response");
             if ("success".equals(response)) {
                 callback.sendSuccess(response);
             } else if ("error".equals(response)) {
                 callback.sendError(response);
-            } else if ("cancel".equals(response)) {
-                callback.sendCancel();
             } else {
                 fFail("Response type should be valid: " + response);
             }
 
         } else if (NATIVE_EXCEPTION_EVENT.equals(event)) {
             // Make sure we throw the right exceptions.
             try {
                 message.getString(null);
--- a/mobile/android/base/util/EventCallback.java
+++ b/mobile/android/base/util/EventCallback.java
@@ -21,14 +21,9 @@ public interface EventCallback {
 
     /**
      * Sends an error response with the given data.
      *
      * @param response The response data to send to Gecko. Can be any of the types accepted by
      *                 JSONObject#put(String, Object).
      */
     public void sendError(Object response);
-
-    /**
-     * Cancels the request, preventing any Gecko-side callbacks from being executed.
-     */
-    public void sendCancel();
 }
--- a/mobile/android/modules/Messaging.jsm
+++ b/mobile/android/modules/Messaging.jsm
@@ -13,17 +13,23 @@ this.EXPORTED_SYMBOLS = ["sendMessageToJ
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 function sendMessageToJava(aMessage, aCallback) {
   Cu.reportError("sendMessageToJava is deprecated. Use Messaging API instead.");
 
-  Messaging.sendRequest(aMessage, aCallback);
+  if (aCallback) {
+    Messaging.sendRequestForResult(aMessage)
+      .then(result => aCallback(result, null),
+            error => aCallback(null, error));
+  } else {
+    Messaging.sendRequest(aMessage);
+  }
 }
 
 let Messaging = {
   /**
    * Add a listener for the given message.
    *
    * Only one request listener can be registered for a given message.
    *
@@ -66,45 +72,52 @@ let Messaging = {
   removeListener: function (aMessage) {
     requestHandler.removeListener(aMessage);
   },
 
   /**
    * Sends a request to Java.
    *
    * @param aMessage  Message to send; must be an object with a "type" property
-   * @param aCallback Callback function, required if this request expects a response.
    */
-  sendRequest: function (aMessage, aCallback) {
-    if (aCallback) {
+  sendRequest: function (aMessage) {
+    Services.androidBridge.handleGeckoMessage(aMessage);
+  },
+
+  /**
+   * Sends a request to Java, returning a Promise that resolves to the response.
+   *
+   * @param aMessage Message to send; must be an object with a "type" property
+   * @returns A Promise resolving to the response
+   */
+  sendRequestForResult: function (aMessage) {
+    return new Promise((resolve, reject) => {
       let id = uuidgen.generateUUID().toString();
       let obs = {
-        observe: function(aSubject, aTopic, aData) {
+        observe: function (aSubject, aTopic, aData) {
           let data = JSON.parse(aData);
           if (data.__guid__ != id) {
             return;
           }
 
-          Services.obs.removeObserver(obs, aMessage.type + ":Response", false);
+          Services.obs.removeObserver(obs, aMessage.type + ":Response");
 
-          if (data.status === "cancel") {
-            // No Java-side listeners handled our callback.
-            return;
+          if (data.status === "success") {
+            resolve(data.response);
+          } else {
+            reject(data.response);
           }
-
-          aCallback(data.status === "success" ? data.response : null,
-                    data.status === "error"   ? data.response : null);
         }
-      }
+      };
 
       aMessage.__guid__ = id;
       Services.obs.addObserver(obs, aMessage.type + ":Response", false);
-    }
 
-    return Services.androidBridge.handleGeckoMessage(aMessage);
+      this.sendRequest(aMessage);
+    });
   },
 };
 
 let requestHandler = {
   _listeners: {},
 
   addListener: function (aListener, aMessage) {
     if (aMessage in this._listeners) {
--- a/testing/mochitest/roboextender/bootstrap.js
+++ b/testing/mochitest/roboextender/bootstrap.js
@@ -24,17 +24,17 @@ var windowListener = {
       if (domWindow) {
         domWindow.addEventListener("scroll", function(e) {
           let message = {
             type: 'robocop:scroll',
             y: XPCNativeWrapper.unwrap(e.target).documentElement.scrollTop,
             height: XPCNativeWrapper.unwrap(e.target).documentElement.scrollHeight,
             cheight: XPCNativeWrapper.unwrap(e.target).documentElement.clientHeight,
           };
-          let retVal = _sendMessageToJava(message);
+          _sendMessageToJava(message);
         });
       }
     }, false);
   },
   onCloseWindow: function(aWindow) { },
   onWindowTitleChange: function(aWindow, aTitle) { }
 };