Bug 1052158 - Forward the GeckoRequest error stack to Java. r=wesj
authorBrian Nicholson <bnicholson@mozilla.com>
Wed, 19 Nov 2014 16:33:56 -0800
changeset 216555 1b946e43e5dc3854d6436ed98e42a6f3a3a80641
parent 216554 dc5206885f8727c30b3b99c268cfbed0b936951f
child 216556 5089a4ce113ba061457a4bd29b90c983c68156c6
push idunknown
push userunknown
push dateunknown
reviewerswesj
bugs1052158
milestone36.0a1
Bug 1052158 - Forward the GeckoRequest error stack to Java. r=wesj
mobile/android/base/GeckoAppShell.java
mobile/android/base/tests/testGeckoRequest.java
mobile/android/base/util/GeckoRequest.java
mobile/android/modules/Messaging.jsm
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -223,16 +223,17 @@ public class GeckoAppShell
     private static Sensor gAccelerometerSensor;
     private static Sensor gLinearAccelerometerSensor;
     private static Sensor gGyroscopeSensor;
     private static Sensor gOrientationSensor;
     private static Sensor gProximitySensor;
     private static Sensor gLightSensor;
 
     private static final String GECKOREQUEST_RESPONSE_KEY = "response";
+    private static final String GECKOREQUEST_ERROR_KEY = "error";
 
     /*
      * Keep in sync with constants found here:
      * http://mxr.mozilla.org/mozilla-central/source/uriloader/base/nsIWebProgressListener.idl
     */
     static public final int WPL_STATE_START = 0x00000001;
     static public final int WPL_STATE_STOP = 0x00000010;
     static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
@@ -429,17 +430,17 @@ public class GeckoAppShell
     public static void sendRequestToGecko(final GeckoRequest request) {
         final String responseMessage = "Gecko:Request" + request.getId();
 
         EventDispatcher.getInstance().registerGeckoThreadListener(new NativeEventListener() {
             @Override
             public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
                 EventDispatcher.getInstance().unregisterGeckoThreadListener(this, event);
                 if (!message.has(GECKOREQUEST_RESPONSE_KEY)) {
-                    request.onError();
+                    request.onError(message.getObject(GECKOREQUEST_ERROR_KEY));
                     return;
                 }
                 request.onResponse(message.getObject(GECKOREQUEST_RESPONSE_KEY));
             }
         }, responseMessage);
 
         sendEventToGecko(GeckoEvent.createBroadcastEvent(request.getName(), request.getData()));
     }
--- a/mobile/android/base/tests/testGeckoRequest.java
+++ b/mobile/android/base/tests/testGeckoRequest.java
@@ -91,17 +91,17 @@ public class testGeckoRequest extends UI
 
         GeckoAppShell.sendRequestToGecko(new GeckoRequest(REQUEST_EXCEPTION_EVENT, null) {
             @Override
             public void onResponse(NativeJSObject nativeJSObject) {
                 responseReceived.set(true);
             }
 
             @Override
-            public void onError() {
+            public void onError(NativeJSObject error) {
                 errorReceived.set(true);
             }
         });
 
         WaitHelper.waitFor("Received error for listener with exception", new Condition() {
             @Override
             public boolean isSatisfied() {
                 return errorReceived.get();
--- a/mobile/android/base/util/GeckoRequest.java
+++ b/mobile/android/base/util/GeckoRequest.java
@@ -1,8 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 package org.mozilla.gecko.util;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import org.mozilla.gecko.mozglue.RobocopTarget;
@@ -72,20 +75,20 @@ public abstract class GeckoRequest {
      * @param nativeJSObject The response data from Gecko
      */
     @RobocopTarget
     public abstract void onResponse(NativeJSObject nativeJSObject);
 
     /**
      * Callback executed when the request fails.
      *
-     * In general, this should not be overridden since there's no way to differentiate between
-     * expected errors and logic errors in JS. If the Gecko-side request handler wants to send a
-     * recoverable error to Java, it should include any error data in the response object that the
-     * {@link #onResponse(NativeJSObject)} callback can handle as necessary.
+     * By default, an exception is thrown. This should be overridden if the
+     * GeckoRequest is able to recover from the error.
      *
      * @throws RuntimeException
      */
     @RobocopTarget
-    public void onError() {
-        throw new RuntimeException("Unhandled error for GeckoRequest: " + name);
+    public void onError(NativeJSObject error) {
+        final String message = error.optString("message", "<no message>");
+        final String stack = error.optString("stack", "<no stack>");
+        throw new RuntimeException("Unhandled error for GeckoRequest " + name + ": " + message + "\nJS stack:\n" + stack);
     }
-}
\ No newline at end of file
+}
--- a/mobile/android/modules/Messaging.jsm
+++ b/mobile/android/modules/Messaging.jsm
@@ -138,29 +138,31 @@ let requestHandler = {
     delete this._listeners[aMessage];
     Services.obs.removeObserver(this, aMessage);
   },
 
   observe: Task.async(function* (aSubject, aTopic, aData) {
     let wrapper = JSON.parse(aData);
     let listener = this._listeners[aTopic];
 
-    // A null response indicates an error. If an error occurs in the callback
-    // below, the response will remain null, and Java will fire onError for
-    // this request.
-    let response = null;
-
     try {
-      let result = yield listener(wrapper.data);
-      if (typeof result !== "object" || result === null) {
+      let response = yield listener(wrapper.data);
+      if (typeof response !== "object" || response === null) {
         throw new Error("Gecko request listener did not return an object");
       }
-      response = result;
+
+      Messaging.sendRequest({
+        type: "Gecko:Request" + wrapper.id,
+        response: response
+      });
     } catch (e) {
-      Cu.reportError(e);
-    }
+      Cu.reportError("Error in Messaging handler for " + aTopic + ": " + e);
 
-    Messaging.sendRequest({
-      type: "Gecko:Request" + wrapper.id,
-      response: response
-    });
+      Messaging.sendRequest({
+        type: "Gecko:Request" + wrapper.id,
+        error: {
+          message: e.message || (e && e.toString()),
+          stack: e.stack || Components.stack.formattedStack,
+        }
+      });
+    }
   })
 };