Bug 984458 - e. Add NativeJSObject object getter; r=blassey
authorJim Chen <nchen@mozilla.com>
Tue, 01 Apr 2014 15:16:55 -0400
changeset 176553 1c7f49db0b096e8a558496d8e2c7e7a5a621b765
parent 176552 a9bea24490d64fd50ffe8eb32b1d4264929f3d94
child 176554 99a8ea57ec6580caf24130c883546e9810e58e26
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersblassey
bugs984458
milestone31.0a1
Bug 984458 - e. Add NativeJSObject object getter; r=blassey
mobile/android/base/util/NativeJSObject.java
mozglue/android/jni-stubs.inc
widget/android/NativeJSContainer.cpp
--- a/mobile/android/base/util/NativeJSObject.java
+++ b/mobile/android/base/util/NativeJSObject.java
@@ -10,19 +10,26 @@ import org.mozilla.gecko.mozglue.JNITarg
 /**
  * NativeJSObject is a wrapper around the SpiderMonkey JSAPI to make it possible to
  * access Javascript objects in Java.
  */
 @JNITarget
 public class NativeJSObject
 {
     private final NativeJSContainer mContainer;
+    private final int mObjectIndex;
 
     protected NativeJSObject() {
         mContainer = (NativeJSContainer)this;
+        mObjectIndex = -1;
+    }
+
+    private NativeJSObject(NativeJSContainer container, int index) {
+        mContainer = container;
+        mObjectIndex = index;
     }
 
     /**
      * Returns the value of a boolean property.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
@@ -64,16 +71,32 @@ public class NativeJSObject
      * @throws IllegalThreadStateException
      *         If not called on the thread this object is attached to
      * @throws UnsupportedOperationException
      *         If an internal JSAPI call failed
      */
     public native int getInt(String name);
 
     /**
+     * Returns the value of an object property.
+     *
+     * @param name
+     *        Property name
+     * @throws IllegalArgumentException
+     *         If the property does not exist or if its type does not match the return type
+     * @throws NullPointerException
+     *         If name is null or if this JS object has been disposed
+     * @throws IllegalThreadStateException
+     *         If not called on the thread this object is attached to
+     * @throws UnsupportedOperationException
+     *         If an internal JSAPI call failed
+     */
+    public native NativeJSObject getObject(String name);
+
+    /**
      * Returns the value of a string property.
      *
      * @param name
      *        Property name
      * @throws IllegalArgumentException
      *         If the property does not exist or if its type does not match the return type
      * @throws NullPointerException
      *         If name is null or if this JS object has been disposed
--- a/mozglue/android/jni-stubs.inc
+++ b/mozglue/android/jni-stubs.inc
@@ -661,16 +661,35 @@ Java_org_mozilla_gecko_util_NativeJSObje
 #endif
 
 #ifdef JNI_BINDINGS
   xul_dlsym("Java_org_mozilla_gecko_util_NativeJSObject_getInt", &f_Java_org_mozilla_gecko_util_NativeJSObject_getInt);
 #endif
 
 #ifdef JNI_STUBS
 
+typedef jobject (*Java_org_mozilla_gecko_util_NativeJSObject_getObject_t)(JNIEnv *, jobject, jstring);
+static Java_org_mozilla_gecko_util_NativeJSObject_getObject_t f_Java_org_mozilla_gecko_util_NativeJSObject_getObject;
+extern "C" NS_EXPORT jobject JNICALL
+Java_org_mozilla_gecko_util_NativeJSObject_getObject(JNIEnv * arg0, jobject arg1, jstring arg2) {
+    if (!f_Java_org_mozilla_gecko_util_NativeJSObject_getObject) {
+        arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
+                       "JNI Function called before it was loaded");
+        return nullptr;
+    }
+    return f_Java_org_mozilla_gecko_util_NativeJSObject_getObject(arg0, arg1, arg2);
+}
+#endif
+
+#ifdef JNI_BINDINGS
+  xul_dlsym("Java_org_mozilla_gecko_util_NativeJSObject_getObject", &f_Java_org_mozilla_gecko_util_NativeJSObject_getObject);
+#endif
+
+#ifdef JNI_STUBS
+
 typedef jstring (*Java_org_mozilla_gecko_util_NativeJSObject_getString_t)(JNIEnv *, jobject, jstring);
 static Java_org_mozilla_gecko_util_NativeJSObject_getString_t f_Java_org_mozilla_gecko_util_NativeJSObject_getString;
 extern "C" NS_EXPORT jstring JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getString(JNIEnv * arg0, jobject arg1, jstring arg2) {
     if (!f_Java_org_mozilla_gecko_util_NativeJSObject_getString) {
         arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"),
                        "JNI Function called before it was loaded");
         return nullptr;
--- a/widget/android/NativeJSContainer.cpp
+++ b/widget/android/NativeJSContainer.cpp
@@ -33,16 +33,23 @@ public:
 
         jNativeJSObject = AndroidBridge::GetClassGlobalRef(
             env, "org/mozilla/gecko/util/NativeJSObject");
         MOZ_ASSERT(jNativeJSObject);
         jObjectContainer = AndroidBridge::GetFieldID(
             env, jNativeJSObject, "mContainer",
             "Lorg/mozilla/gecko/util/NativeJSContainer;");
         MOZ_ASSERT(jObjectContainer);
+        jObjectIndex = AndroidBridge::GetFieldID(
+            env, jNativeJSObject, "mObjectIndex", "I");
+        MOZ_ASSERT(jObjectIndex);
+        jObjectConstructor = AndroidBridge::GetMethodID(
+            env, jNativeJSObject, "<init>",
+            "(Lorg/mozilla/gecko/util/NativeJSContainer;I)V");
+        MOZ_ASSERT(jContainerConstructor);
     }
 
     static jobject CreateInstance(JNIEnv* env, JSContext* cx,
                                   JS::HandleObject object) {
         return CreateInstance(env, new NativeJSContainer(cx, object));
     }
 
     static NativeJSContainer* FromInstance(JNIEnv* env, jobject instance) {
@@ -114,17 +121,47 @@ public:
         AutoLocalJNIFrame frame(env, 1);
 
         NativeJSContainer* const container =
             FromInstance(env, GetInstanceFromObject(env, object));
         if (!container ||
             !container->EnsureObject(env)) { // Do thread check
             return nullptr;
         }
-        return container->mJSObject;
+        const jint index = env->GetIntField(object, jObjectIndex);
+        if (index < 0) {
+            // -1 for index field means it's the root object of the container
+            return container->mJSObject;
+        }
+        return container->mRootedObjects[index];
+    }
+
+    static jobject CreateObjectInstance(JNIEnv* env, jobject object,
+                                        JS::HandleObject jsObject) {
+        MOZ_ASSERT(object);
+        MOZ_ASSERT(jsObject);
+        AutoLocalJNIFrame frame(env, 2);
+
+        jobject instance = GetInstanceFromObject(env, object);
+        NativeJSContainer* const container = FromInstance(env, instance);
+        if (!container) {
+            return nullptr;
+        }
+        size_t newIndex = container->mRootedObjects.length();
+        if (!container->mRootedObjects.append(jsObject)) {
+            AndroidBridge::ThrowException(env,
+                "java/lang/OutOfMemoryError", "Cannot allocate object");
+            return nullptr;
+        }
+        const jobject newObject =
+            env->NewObject(jNativeJSObject, jObjectConstructor,
+                           instance, newIndex);
+        AndroidBridge::HandleUncaughtException(env);
+        MOZ_ASSERT(newObject);
+        return frame.Pop(newObject);
     }
 
     // Make sure we have a JSObject and deserialize if necessary/possible
     bool EnsureObject(JNIEnv* env) {
         if (mJSObject) {
             if (PR_GetCurrentThread() != mThread) {
                 AndroidBridge::ThrowException(env,
                     "java/lang/IllegalThreadStateException",
@@ -156,16 +193,18 @@ public:
     }
 
 private:
     static jclass jNativeJSContainer;
     static jfieldID jContainerNativeObject;
     static jmethodID jContainerConstructor;
     static jclass jNativeJSObject;
     static jfieldID jObjectContainer;
+    static jfieldID jObjectIndex;
+    static jmethodID jObjectConstructor;
 
     static jobject CreateInstance(JNIEnv* env,
                                   NativeJSContainer* nativeObject) {
         InitJNI(env);
         const jobject newObject =
             env->NewObject(jNativeJSContainer, jContainerConstructor,
                            static_cast<jlong>(
                            reinterpret_cast<uintptr_t>(nativeObject)));
@@ -177,16 +216,18 @@ private:
     // Thread that the object is valid on
     PRThread* mThread;
     // Context that the object is valid in
     JSContext* mThreadContext;
     // Deserialized object, or nullptr if object is in serialized form
     JS::Heap<JSObject*> mJSObject;
     // Serialized object, or empty if object is in deserialized form
     JSAutoStructuredCloneBuffer mBuffer;
+    // Objects derived from mJSObject
+    Vector<JS::Heap<JSObject*>, 4> mRootedObjects;
 
     // Create a new container containing the given deserialized object
     NativeJSContainer(JSContext* cx, JS::HandleObject object)
             : mThread(PR_GetCurrentThread())
             , mThreadContext(cx)
             , mJSObject(object)
     {
     }
@@ -208,16 +249,18 @@ private:
     }
 };
 
 jclass NativeJSContainer::jNativeJSContainer = 0;
 jfieldID NativeJSContainer::jContainerNativeObject = 0;
 jmethodID NativeJSContainer::jContainerConstructor = 0;
 jclass NativeJSContainer::jNativeJSObject = 0;
 jfieldID NativeJSContainer::jObjectContainer = 0;
+jfieldID NativeJSContainer::jObjectIndex = 0;
+jmethodID NativeJSContainer::jObjectConstructor = 0;
 
 jobject
 CreateNativeJSContainer(JNIEnv* env, JSContext* cx, JS::HandleObject object)
 {
     return NativeJSContainer::CreateInstance(env, cx, object);
 }
 
 } // namespace widget
@@ -330,16 +373,34 @@ struct StringProperty
         jstring ret = env->NewString(
             reinterpret_cast<const jchar*>(strChars), strLen);
         AndroidBridge::HandleUncaughtException(env);
         MOZ_ASSERT(ret);
         return ret;
     }
 };
 
+struct ObjectProperty
+{
+    typedef jobject Type;
+
+    static bool InValue(JS::HandleValue val) {
+        return val.isObjectOrNull();
+    }
+
+    static Type FromValue(JNIEnv* env, jobject instance,
+                          JSContext* cx, JS::HandleValue val) {
+        if (val.isNull()) {
+            return nullptr;
+        }
+        JS::RootedObject object(cx, &val.toObject());
+        return NativeJSContainer::CreateObjectInstance(env, instance, object);
+    }
+};
+
 struct HasProperty
 {
     typedef jboolean Type;
 
     static bool InValue(JS::HandleValue val) {
         return true;
     }
 
@@ -408,16 +469,22 @@ Java_org_mozilla_gecko_util_NativeJSObje
 }
 
 NS_EXPORT jint JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getInt(JNIEnv* env, jobject instance, jstring name)
 {
     return GetProperty<IntProperty>(env, instance, name);
 }
 
+NS_EXPORT jobject JNICALL
+Java_org_mozilla_gecko_util_NativeJSObject_getObject(JNIEnv* env, jobject instance, jstring name)
+{
+    return GetProperty<ObjectProperty>(env, instance, name);
+}
+
 NS_EXPORT jstring JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getString(JNIEnv* env, jobject instance, jstring name)
 {
     return GetProperty<StringProperty>(env, instance, name);
 }
 
 NS_EXPORT jboolean JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_has(JNIEnv* env, jobject instance, jstring name)