Bug 992357 - c. Add primitive array support to NativeJSObject; r=blassey
authorJim Chen <nchen@mozilla.com>
Fri, 16 May 2014 18:25:29 -0400
changeset 183638 5411ac3426ec36e6193e505d942904eef11ffc1b
parent 183637 6c99b5df88090ca860554ed787c1313054736687
child 183639 19f8e41cb0bf1e35656316cbe1034cdbc1f7de0c
push id6844
push userphilringnalda@gmail.com
push dateSun, 18 May 2014 01:12:08 +0000
treeherderfx-team@41a54c8add09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs992357
milestone32.0a1
Bug 992357 - c. Add primitive array support to NativeJSObject; r=blassey
widget/android/NativeJSContainer.cpp
--- a/widget/android/NativeJSContainer.cpp
+++ b/widget/android/NativeJSContainer.cpp
@@ -67,16 +67,28 @@ public:
         jBundlePutInt = AndroidBridge::GetMethodID(
             env, jBundle, "putInt",
             "(Ljava/lang/String;I)V");
         MOZ_ASSERT(jBundlePutInt);
         jBundlePutString = AndroidBridge::GetMethodID(
             env, jBundle, "putString",
             "(Ljava/lang/String;Ljava/lang/String;)V");
         MOZ_ASSERT(jBundlePutString);
+        jBundlePutBooleanArray = AndroidBridge::GetMethodID(
+            env, jBundle, "putBooleanArray",
+            "(Ljava/lang/String;[Z)V");
+        MOZ_ASSERT(jBundlePutBooleanArray);
+        jBundlePutDoubleArray = AndroidBridge::GetMethodID(
+            env, jBundle, "putDoubleArray",
+            "(Ljava/lang/String;[D)V");
+        MOZ_ASSERT(jBundlePutDoubleArray);
+        jBundlePutIntArray = AndroidBridge::GetMethodID(
+            env, jBundle, "putIntArray",
+            "(Ljava/lang/String;[I)V");
+        MOZ_ASSERT(jBundlePutIntArray);
     }
 
     static jobject CreateInstance(JNIEnv* env, JSContext* cx,
                                   JS::HandleObject object) {
         return CreateInstance(env, new NativeJSContainer(cx, object));
     }
 
     static NativeJSContainer* FromInstance(JNIEnv* env, jobject instance) {
@@ -222,16 +234,19 @@ public:
 
     static jclass jBundle;
     static jmethodID jBundleConstructor;
     static jmethodID jBundlePutBoolean;
     static jmethodID jBundlePutBundle;
     static jmethodID jBundlePutDouble;
     static jmethodID jBundlePutInt;
     static jmethodID jBundlePutString;
+    static jmethodID jBundlePutBooleanArray;
+    static jmethodID jBundlePutDoubleArray;
+    static jmethodID jBundlePutIntArray;
 
 private:
     static jclass jNativeJSContainer;
     static jfieldID jContainerNativeObject;
     static jmethodID jContainerConstructor;
     static jclass jNativeJSObject;
     static jfieldID jObjectContainer;
     static jfieldID jObjectIndex;
@@ -294,16 +309,19 @@ jfieldID NativeJSContainer::jObjectIndex
 jmethodID NativeJSContainer::jObjectConstructor = 0;
 jclass NativeJSContainer::jBundle = 0;
 jmethodID NativeJSContainer::jBundleConstructor = 0;
 jmethodID NativeJSContainer::jBundlePutBoolean = 0;
 jmethodID NativeJSContainer::jBundlePutBundle = 0;
 jmethodID NativeJSContainer::jBundlePutDouble = 0;
 jmethodID NativeJSContainer::jBundlePutInt = 0;
 jmethodID NativeJSContainer::jBundlePutString = 0;
+jmethodID NativeJSContainer::jBundlePutBooleanArray = 0;
+jmethodID NativeJSContainer::jBundlePutDoubleArray = 0;
+jmethodID NativeJSContainer::jBundlePutIntArray = 0;
 
 jobject
 CreateNativeJSContainer(JNIEnv* env, JSContext* cx, JS::HandleObject object)
 {
     return NativeJSContainer::CreateInstance(env, cx, object);
 }
 
 } // namespace widget
@@ -355,59 +373,103 @@ CheckJNIArgument(JNIEnv* env, jobject ar
     if (!arg) {
         AndroidBridge::ThrowException(env,
             "java/lang/IllegalArgumentException", "Null argument");
         return false;
     }
     return true;
 }
 
+template <bool (*InValue)(JSContext*, JS::HandleValue)> bool
+CheckProperty(JNIEnv* env, JSContext* cx, JS::HandleValue val) {
+    if (!(*InValue)(cx, val)) {
+        AndroidBridge::ThrowException(env,
+            "java/lang/IllegalArgumentException",
+            "Property type mismatch");
+        return false;
+    }
+    return true;
+}
+
 bool
 AppendJSON(const jschar* buf, uint32_t len, void* data)
 {
     static_cast<nsAutoString*>(data)->Append(buf, len);
     return true;
 }
 
 template <typename U, typename V,
           bool (JS::Value::*IsMethod)() const,
-          V (JS::Value::*ToMethod)() const>
+          V (JS::Value::*ToMethod)() const,
+          typename UA,
+          UA (JNIEnv::*NewArrayMethod)(jsize),
+          void (JNIEnv::*SetArrayRegionMethod)(UA, jsize, jsize, const U*)>
 struct PrimitiveProperty
 {
     typedef U Type; // JNI type
+    typedef UA ArrayType; // JNI array type
     typedef V NativeType; // JSAPI type
 
-    static bool InValue(JS::HandleValue val) {
+    static bool InValue(JSContext* cx, JS::HandleValue val) {
         return (static_cast<const JS::Value&>(val).*IsMethod)();
     }
 
     static Type FromValue(JNIEnv* env, jobject instance,
                           JSContext* cx, JS::HandleValue val) {
         return static_cast<Type>(
             (static_cast<const JS::Value&>(val).*ToMethod)());
     }
+
+    static ArrayType NewArray(JNIEnv* env, jobject instance, JSContext* cx,
+                              JS::HandleObject array, size_t length) {
+        ScopedDeleteArray<Type> buffer(new Type[length]);
+        for (size_t i = 0; i < length; i++) {
+            JS::RootedValue elem(cx);
+            if (!CheckJSCall(env, JS_GetElement(cx, array, i, &elem)) ||
+                !CheckProperty<InValue>(env, cx, elem)) {
+                return nullptr;
+            }
+            buffer[i] = FromValue(env, instance, cx, elem);
+        }
+        AutoLocalJNIFrame frame(env, 1);
+        ArrayType jarray = (env->*NewArrayMethod)(length);
+        if (!jarray) {
+            return nullptr;
+        }
+        (env->*SetArrayRegionMethod)(jarray, 0, length, buffer);
+        if (env->ExceptionCheck()) {
+            return nullptr;
+        }
+        return frame.Pop(jarray);
+    }
 };
 
 // Statically cast from bool to jboolean (unsigned char); it works
 // since false and JNI_FALSE have the same value (0), and true and
 // JNI_TRUE have the same value (1).
 typedef PrimitiveProperty<jboolean, bool,
-    &JS::Value::isBoolean, &JS::Value::toBoolean> BooleanProperty;
+    &JS::Value::isBoolean, &JS::Value::toBoolean,
+    jbooleanArray, &JNIEnv::NewBooleanArray,
+    &JNIEnv::SetBooleanArrayRegion> BooleanProperty;
 
 typedef PrimitiveProperty<jdouble, double,
-    &JS::Value::isNumber, &JS::Value::toNumber> DoubleProperty;
+    &JS::Value::isNumber, &JS::Value::toNumber,
+    jdoubleArray, &JNIEnv::NewDoubleArray,
+    &JNIEnv::SetDoubleArrayRegion> DoubleProperty;
 
 typedef PrimitiveProperty<jint, int32_t,
-    &JS::Value::isInt32, &JS::Value::toInt32> IntProperty;
+    &JS::Value::isInt32, &JS::Value::toInt32,
+    jintArray, &JNIEnv::NewIntArray,
+    &JNIEnv::SetIntArrayRegion> IntProperty;
 
 struct StringProperty
 {
     typedef jstring Type;
 
-    static bool InValue(JS::HandleValue val) {
+    static bool InValue(JSContext* cx, JS::HandleValue val) {
         return val.isString();
     }
 
     static Type FromValue(JNIEnv* env, jobject instance,
                           JSContext* cx, JS::HandleValue val) {
         const JS::RootedString str(cx, val.toString());
         return FromValue(env, instance, cx, str);
     }
@@ -429,17 +491,17 @@ struct StringProperty
 };
 
 template <jobject (*FactoryMethod)
     (JNIEnv*, jobject, JSContext*, JS::HandleObject)>
 struct BaseObjectProperty
 {
     typedef jobject Type;
 
-    static bool InValue(JS::HandleValue val) {
+    static bool InValue(JSContext* cx, JS::HandleValue val) {
         return val.isObjectOrNull();
     }
 
     static Type FromValue(JNIEnv* env, jobject instance,
                           JSContext* cx, JS::HandleValue val) {
         if (val.isNull()) {
             return nullptr;
         }
@@ -452,21 +514,63 @@ jobject GetBundle(JNIEnv*, jobject, JSCo
 
 // Returns a NativeJSObject from a JSObject
 typedef BaseObjectProperty<
     NativeJSContainer::CreateObjectInstance> ObjectProperty;
 
 // Returns a Bundle from a JSObject
 typedef BaseObjectProperty<GetBundle> BundleProperty;
 
+template <class T>
+struct ArrayProperty
+{
+    typedef T Base;
+    typedef typename T::ArrayType Type;
+
+    static bool InValue(JSContext* cx, JS::HandleValue val) {
+        if (!val.isObject()) {
+            return false;
+        }
+        JS::RootedObject obj(cx, &val.toObject());
+        uint32_t length = 0;
+        if (!JS_IsArrayObject(cx, obj) ||
+            !JS_GetArrayLength(cx, obj, &length)) {
+            return false;
+        }
+        if (!length) {
+            // Empty arrays are always okay.
+            return true;
+        }
+        // We only check to see the first element is the target type. If the
+        // array has mixed types, we'll throw an error during actual conversion.
+        JS::RootedValue element(cx);
+        return JS_GetElement(cx, obj, 0, &element) &&
+               Base::InValue(cx, element);
+    }
+
+    static Type FromValue(JNIEnv* env, jobject instance,
+                               JSContext* cx, JS::HandleValue val) {
+        JS::RootedObject obj(cx, &val.toObject());
+        uint32_t length = 0;
+        if (!CheckJSCall(env, JS_GetArrayLength(cx, obj, &length))) {
+            return nullptr;
+        }
+        return Base::NewArray(env, instance, cx, obj, length);
+    }
+};
+
+typedef ArrayProperty<BooleanProperty> BooleanArrayProperty;
+typedef ArrayProperty<DoubleProperty> DoubleArrayProperty;
+typedef ArrayProperty<IntProperty> IntArrayProperty;
+
 struct HasProperty
 {
     typedef jboolean Type;
 
-    static bool InValue(JS::HandleValue val) {
+    static bool InValue(JSContext* cx, JS::HandleValue val) {
         return true;
     }
 
     static Type FromValue(JNIEnv* env, jobject instance,
                           JSContext* cx, JS::HandleValue val) {
         return JNI_TRUE;
     }
 };
@@ -505,20 +609,17 @@ GetProperty(JNIEnv* env, jobject instanc
     if (val.isUndefined()) {
         if (option == FallbackOption::THROW) {
             AndroidBridge::ThrowException(env,
                 "java/lang/IllegalArgumentException",
                 "Property does not exist");
         }
         return fallback;
     }
-    if (!Property::InValue(val)) {
-        AndroidBridge::ThrowException(env,
-            "java/lang/IllegalArgumentException",
-            "Property type mismatch");
+    if (!CheckProperty<Property::InValue>(env, cx, val)) {
         return fallback;
     }
     return Property::FromValue(env, instance, cx, val);
 }
 
 jobject
 GetBundle(JNIEnv* env, jobject instance, JSContext* cx, JS::HandleObject obj)
 {
@@ -557,32 +658,42 @@ GetBundle(JNIEnv* env, jobject instance,
             StringProperty::FromValue(env, instance, cx, idStr);
         JS::RootedValue val(cx);
         if (!name ||
             !CheckJSCall(env, JS_GetPropertyById(cx, obj, id, &val))) {
             return nullptr;
         }
 
 #define PUT_IN_BUNDLE_IF_TYPE_IS(Type)                              \
-        if (Type##Property::InValue(val)) {                         \
+        if (Type##Property::InValue(cx, val)) {                     \
             env->CallVoidMethod(                                    \
                 newBundle,                                          \
                 NativeJSContainer::jBundlePut##Type,                \
                 name,                                               \
                 Type##Property::FromValue(env, instance, cx, val)); \
             AndroidBridge::HandleUncaughtException(env);            \
             continue;                                               \
         }                                                           \
         ((void) 0) // Accommodate trailing semicolon.
 
+        // Scalar values are faster to check, so check them first.
         PUT_IN_BUNDLE_IF_TYPE_IS(Boolean);
         // Int can be casted to double, so check int first.
         PUT_IN_BUNDLE_IF_TYPE_IS(Int);
         PUT_IN_BUNDLE_IF_TYPE_IS(Double);
         PUT_IN_BUNDLE_IF_TYPE_IS(String);
+        // There's no "putObject", so don't check ObjectProperty
+
+        // Check for array types if scalar checks all failed.
+        PUT_IN_BUNDLE_IF_TYPE_IS(BooleanArray);
+        // XXX because we only check the first element of an array,
+        // a double array can potentially be seen as an int array.
+        PUT_IN_BUNDLE_IF_TYPE_IS(IntArray);
+        PUT_IN_BUNDLE_IF_TYPE_IS(DoubleArray);
+
         // Use Bundle as the default catch-all for objects
         PUT_IN_BUNDLE_IF_TYPE_IS(Bundle);
 
 #undef PUT_IN_BUNDLE_IF_TYPE_IS
 
         // We tried all supported types; just bail.
         AndroidBridge::ThrowException(env,
             "java/lang/UnsupportedOperationException",
@@ -622,24 +733,24 @@ Java_org_mozilla_gecko_util_NativeJSObje
 {
     return GetProperty<BooleanProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jbooleanArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getBooleanArray(
     JNIEnv* env, jobject instance, jstring name)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<BooleanArrayProperty>(env, instance, name);
 }
 
 NS_EXPORT jbooleanArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_optBooleanArray(
     JNIEnv* env, jobject instance, jstring name, jbooleanArray fallback)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<BooleanArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jobject JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getBundle(JNIEnv* env, jobject instance, jstring name)
 {
     return GetProperty<BundleProperty>(env, instance, name);
 }
 
@@ -676,24 +787,24 @@ Java_org_mozilla_gecko_util_NativeJSObje
 {
     return GetProperty<DoubleProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jdoubleArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getDoubleArray(
     JNIEnv* env, jobject instance, jstring name)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<DoubleArrayProperty>(env, instance, name);
 }
 
 NS_EXPORT jdoubleArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_optDoubleArray(
     JNIEnv* env, jobject instance, jstring name, jdoubleArray fallback)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<DoubleArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jint JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getInt(JNIEnv* env, jobject instance, jstring name)
 {
     return GetProperty<IntProperty>(env, instance, name);
 }
 
@@ -703,24 +814,24 @@ Java_org_mozilla_gecko_util_NativeJSObje
 {
     return GetProperty<IntProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jintArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getIntArray(
     JNIEnv* env, jobject instance, jstring name)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<IntArrayProperty>(env, instance, name);
 }
 
 NS_EXPORT jintArray JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_optIntArray(
     JNIEnv* env, jobject instance, jstring name, jintArray fallback)
 {
-    return nullptr; // TODO add implementation
+    return GetProperty<IntArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
 }
 
 NS_EXPORT jobject JNICALL
 Java_org_mozilla_gecko_util_NativeJSObject_getObject(JNIEnv* env, jobject instance, jstring name)
 {
     return GetProperty<ObjectProperty>(env, instance, name);
 }