widget/android/NativeJSContainer.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "NativeJSContainer.h"
     7 #include "AndroidBridge.h"
     8 #include "mozilla/Vector.h"
     9 #include "prthread.h"
    11 using namespace mozilla;
    12 using namespace mozilla::widget;
    14 namespace mozilla {
    15 namespace widget {
    17 class NativeJSContainer
    18 {
    19 public:
    20     static void InitJNI(JNIEnv* env) {
    21         if (jNativeJSContainer) {
    22             return;
    23         }
    24         jNativeJSContainer = AndroidBridge::GetClassGlobalRef(
    25             env, "org/mozilla/gecko/util/NativeJSContainer");
    26         MOZ_ASSERT(jNativeJSContainer);
    27         jContainerNativeObject = AndroidBridge::GetFieldID(
    28             env, jNativeJSContainer, "mNativeObject", "J");
    29         MOZ_ASSERT(jContainerNativeObject);
    30         jContainerConstructor = AndroidBridge::GetMethodID(
    31             env, jNativeJSContainer, "<init>", "(J)V");
    32         MOZ_ASSERT(jContainerConstructor);
    34         jNativeJSObject = AndroidBridge::GetClassGlobalRef(
    35             env, "org/mozilla/gecko/util/NativeJSObject");
    36         MOZ_ASSERT(jNativeJSObject);
    37         jObjectContainer = AndroidBridge::GetFieldID(
    38             env, jNativeJSObject, "mContainer",
    39             "Lorg/mozilla/gecko/util/NativeJSContainer;");
    40         MOZ_ASSERT(jObjectContainer);
    41         jObjectIndex = AndroidBridge::GetFieldID(
    42             env, jNativeJSObject, "mObjectIndex", "I");
    43         MOZ_ASSERT(jObjectIndex);
    44         jObjectConstructor = AndroidBridge::GetMethodID(
    45             env, jNativeJSObject, "<init>",
    46             "(Lorg/mozilla/gecko/util/NativeJSContainer;I)V");
    47         MOZ_ASSERT(jContainerConstructor);
    48     }
    50     static jobject CreateInstance(JNIEnv* env, JSContext* cx,
    51                                   JS::HandleObject object) {
    52         return CreateInstance(env, new NativeJSContainer(cx, object));
    53     }
    55     static NativeJSContainer* FromInstance(JNIEnv* env, jobject instance) {
    56         MOZ_ASSERT(instance);
    58         const jlong fieldValue =
    59             env->GetLongField(instance, jContainerNativeObject);
    60         NativeJSContainer* const nativeObject =
    61             reinterpret_cast<NativeJSContainer* const>(
    62             static_cast<uintptr_t>(fieldValue));
    63         if (!nativeObject) {
    64             AndroidBridge::ThrowException(env,
    65                 "java/lang/NullPointerException",
    66                 "Uninitialized NativeJSContainer");
    67         }
    68         return nativeObject;
    69     }
    71     static void DisposeInstance(JNIEnv* env, jobject instance) {
    72         NativeJSContainer* const container = FromInstance(env, instance);
    73         if (container) {
    74             env->SetLongField(instance, jContainerNativeObject,
    75                 static_cast<jlong>(reinterpret_cast<uintptr_t>(nullptr)));
    76             delete container;
    77         }
    78     }
    80     static jobject CloneInstance(JNIEnv* env, jobject instance) {
    81         NativeJSContainer* const container = FromInstance(env, instance);
    82         if (!container || !container->EnsureObject(env)) {
    83             return nullptr;
    84         }
    85         JSContext* const cx = container->mThreadContext;
    86         JS::RootedObject object(cx, container->mJSObject);
    87         MOZ_ASSERT(object);
    89         JSAutoStructuredCloneBuffer buffer;
    90         if (!buffer.write(cx, JS::RootedValue(cx, JS::ObjectValue(*object)))) {
    91             AndroidBridge::ThrowException(env,
    92                 "java/lang/UnsupportedOperationException",
    93                 "Cannot serialize object");
    94             return nullptr;
    95         }
    96         return CreateInstance(env, new NativeJSContainer(cx, Move(buffer)));
    97     }
    99     static jobject GetInstanceFromObject(JNIEnv* env, jobject object) {
   100         MOZ_ASSERT(object);
   102         const jobject instance = env->GetObjectField(object, jObjectContainer);
   103         MOZ_ASSERT(instance);
   104         return instance;
   105     }
   107     static JSContext* GetContextFromObject(JNIEnv* env, jobject object) {
   108         MOZ_ASSERT(object);
   109         AutoLocalJNIFrame frame(env, 1);
   111         NativeJSContainer* const container =
   112             FromInstance(env, GetInstanceFromObject(env, object));
   113         if (!container) {
   114             return nullptr;
   115         }
   116         return container->mThreadContext;
   117     }
   119     static JSObject* GetObjectFromObject(JNIEnv* env, jobject object) {
   120         MOZ_ASSERT(object);
   121         AutoLocalJNIFrame frame(env, 1);
   123         NativeJSContainer* const container =
   124             FromInstance(env, GetInstanceFromObject(env, object));
   125         if (!container ||
   126             !container->EnsureObject(env)) { // Do thread check
   127             return nullptr;
   128         }
   129         const jint index = env->GetIntField(object, jObjectIndex);
   130         if (index < 0) {
   131             // -1 for index field means it's the root object of the container
   132             return container->mJSObject;
   133         }
   134         return container->mRootedObjects[index];
   135     }
   137     static jobject CreateObjectInstance(JNIEnv* env, jobject object,
   138                                         JS::HandleObject jsObject) {
   139         MOZ_ASSERT(object);
   140         MOZ_ASSERT(jsObject);
   141         AutoLocalJNIFrame frame(env, 2);
   143         jobject instance = GetInstanceFromObject(env, object);
   144         NativeJSContainer* const container = FromInstance(env, instance);
   145         if (!container) {
   146             return nullptr;
   147         }
   148         size_t newIndex = container->mRootedObjects.length();
   149         if (!container->mRootedObjects.append(jsObject)) {
   150             AndroidBridge::ThrowException(env,
   151                 "java/lang/OutOfMemoryError", "Cannot allocate object");
   152             return nullptr;
   153         }
   154         const jobject newObject =
   155             env->NewObject(jNativeJSObject, jObjectConstructor,
   156                            instance, newIndex);
   157         AndroidBridge::HandleUncaughtException(env);
   158         MOZ_ASSERT(newObject);
   159         return frame.Pop(newObject);
   160     }
   162     // Make sure we have a JSObject and deserialize if necessary/possible
   163     bool EnsureObject(JNIEnv* env) {
   164         if (mJSObject) {
   165             if (PR_GetCurrentThread() != mThread) {
   166                 AndroidBridge::ThrowException(env,
   167                     "java/lang/IllegalThreadStateException",
   168                     "Using NativeJSObject off its thread");
   169                 return false;
   170             }
   171             return true;
   172         }
   173         if (!SwitchContextToCurrentThread()) {
   174             AndroidBridge::ThrowException(env,
   175                 "java/lang/IllegalThreadStateException",
   176                 "Not available for this thread");
   177             return false;
   178         }
   180         JS::RootedValue value(mThreadContext);
   181         MOZ_ASSERT(mBuffer.data());
   182         MOZ_ALWAYS_TRUE(mBuffer.read(mThreadContext, &value));
   183         if (value.isObject()) {
   184             mJSObject = &value.toObject();
   185         }
   186         if (!mJSObject) {
   187             AndroidBridge::ThrowException(env,
   188                 "java/lang/IllegalStateException", "Cannot deserialize data");
   189             return false;
   190         }
   191         mBuffer.clear();
   192         return true;
   193     }
   195 private:
   196     static jclass jNativeJSContainer;
   197     static jfieldID jContainerNativeObject;
   198     static jmethodID jContainerConstructor;
   199     static jclass jNativeJSObject;
   200     static jfieldID jObjectContainer;
   201     static jfieldID jObjectIndex;
   202     static jmethodID jObjectConstructor;
   204     static jobject CreateInstance(JNIEnv* env,
   205                                   NativeJSContainer* nativeObject) {
   206         InitJNI(env);
   207         const jobject newObject =
   208             env->NewObject(jNativeJSContainer, jContainerConstructor,
   209                            static_cast<jlong>(
   210                            reinterpret_cast<uintptr_t>(nativeObject)));
   211         AndroidBridge::HandleUncaughtException(env);
   212         MOZ_ASSERT(newObject);
   213         return newObject;
   214     }
   216     // Thread that the object is valid on
   217     PRThread* mThread;
   218     // Context that the object is valid in
   219     JSContext* mThreadContext;
   220     // Deserialized object, or nullptr if object is in serialized form
   221     JS::Heap<JSObject*> mJSObject;
   222     // Serialized object, or empty if object is in deserialized form
   223     JSAutoStructuredCloneBuffer mBuffer;
   224     // Objects derived from mJSObject
   225     Vector<JS::Heap<JSObject*>, 4> mRootedObjects;
   227     // Create a new container containing the given deserialized object
   228     NativeJSContainer(JSContext* cx, JS::HandleObject object)
   229             : mThread(PR_GetCurrentThread())
   230             , mThreadContext(cx)
   231             , mJSObject(object)
   232     {
   233     }
   235     // Create a new container containing the given serialized object
   236     NativeJSContainer(JSContext* cx, JSAutoStructuredCloneBuffer&& buffer)
   237             : mThread(PR_GetCurrentThread())
   238             , mThreadContext(cx)
   239             , mBuffer(Forward<JSAutoStructuredCloneBuffer>(buffer))
   240     {
   241     }
   243     bool SwitchContextToCurrentThread() {
   244         PRThread* const currentThread = PR_GetCurrentThread();
   245         if (currentThread == mThread) {
   246             return true;
   247         }
   248         return false;
   249     }
   250 };
   252 jclass NativeJSContainer::jNativeJSContainer = 0;
   253 jfieldID NativeJSContainer::jContainerNativeObject = 0;
   254 jmethodID NativeJSContainer::jContainerConstructor = 0;
   255 jclass NativeJSContainer::jNativeJSObject = 0;
   256 jfieldID NativeJSContainer::jObjectContainer = 0;
   257 jfieldID NativeJSContainer::jObjectIndex = 0;
   258 jmethodID NativeJSContainer::jObjectConstructor = 0;
   260 jobject
   261 CreateNativeJSContainer(JNIEnv* env, JSContext* cx, JS::HandleObject object)
   262 {
   263     return NativeJSContainer::CreateInstance(env, cx, object);
   264 }
   266 } // namespace widget
   267 } // namespace mozilla
   269 namespace {
   271 class JSJNIString
   272 {
   273 public:
   274     JSJNIString(JNIEnv* env, jstring str)
   275         : mEnv(env)
   276         , mJNIString(str)
   277         , mJSString(!str ? nullptr :
   278             reinterpret_cast<const jschar*>(env->GetStringChars(str, nullptr)))
   279     {
   280     }
   281     ~JSJNIString() {
   282         if (mJNIString) {
   283             mEnv->ReleaseStringChars(mJNIString,
   284                 reinterpret_cast<const jchar*>(mJSString));
   285         }
   286     }
   287     operator const jschar*() const {
   288         return mJSString;
   289     }
   290     size_t Length() const {
   291         return static_cast<size_t>(mEnv->GetStringLength(mJNIString));
   292     }
   293 private:
   294     JNIEnv* const mEnv;
   295     const jstring mJNIString;
   296     const jschar* const mJSString;
   297 };
   299 bool
   300 CheckJSCall(JNIEnv* env, bool result) {
   301     if (!result) {
   302         AndroidBridge::ThrowException(env,
   303             "java/lang/UnsupportedOperationException", "JSAPI call failed");
   304     }
   305     return result;
   306 }
   308 bool
   309 CheckJNIArgument(JNIEnv* env, jobject arg) {
   310     if (!arg) {
   311         AndroidBridge::ThrowException(env,
   312             "java/lang/IllegalArgumentException", "Null argument");
   313         return false;
   314     }
   315     return true;
   316 }
   318 bool
   319 AppendJSON(const jschar* buf, uint32_t len, void* data)
   320 {
   321     static_cast<nsAutoString*>(data)->Append(buf, len);
   322     return true;
   323 }
   325 template <typename U, typename V,
   326           bool (JS::Value::*IsMethod)() const,
   327           V (JS::Value::*ToMethod)() const>
   328 struct PrimitiveProperty
   329 {
   330     typedef U Type; // JNI type
   331     typedef V NativeType; // JSAPI type
   333     static bool InValue(JS::HandleValue val) {
   334         return (static_cast<const JS::Value&>(val).*IsMethod)();
   335     }
   337     static Type FromValue(JNIEnv* env, jobject instance,
   338                           JSContext* cx, JS::HandleValue val) {
   339         return static_cast<Type>(
   340             (static_cast<const JS::Value&>(val).*ToMethod)());
   341     }
   342 };
   344 // Statically cast from bool to jboolean (unsigned char); it works
   345 // since false and JNI_FALSE have the same value (0), and true and
   346 // JNI_TRUE have the same value (1).
   347 typedef PrimitiveProperty<jboolean, bool,
   348     &JS::Value::isBoolean, &JS::Value::toBoolean> BooleanProperty;
   350 typedef PrimitiveProperty<jdouble, double,
   351     &JS::Value::isNumber, &JS::Value::toNumber> DoubleProperty;
   353 typedef PrimitiveProperty<jint, int32_t,
   354     &JS::Value::isInt32, &JS::Value::toInt32> IntProperty;
   356 struct StringProperty
   357 {
   358     typedef jstring Type;
   360     static bool InValue(JS::HandleValue val) {
   361         return val.isString();
   362     }
   364     static Type FromValue(JNIEnv* env, jobject instance,
   365                           JSContext* cx, JS::HandleValue val) {
   366         JS::RootedString str(cx, val.toString());
   367         size_t strLen = 0;
   368         const jschar* const strChars =
   369             JS_GetStringCharsAndLength(cx, str, &strLen);
   370         if (!CheckJSCall(env, !!strChars)) {
   371             return nullptr;
   372         }
   373         jstring ret = env->NewString(
   374             reinterpret_cast<const jchar*>(strChars), strLen);
   375         AndroidBridge::HandleUncaughtException(env);
   376         MOZ_ASSERT(ret);
   377         return ret;
   378     }
   379 };
   381 struct ObjectProperty
   382 {
   383     typedef jobject Type;
   385     static bool InValue(JS::HandleValue val) {
   386         return val.isObjectOrNull();
   387     }
   389     static Type FromValue(JNIEnv* env, jobject instance,
   390                           JSContext* cx, JS::HandleValue val) {
   391         if (val.isNull()) {
   392             return nullptr;
   393         }
   394         JS::RootedObject object(cx, &val.toObject());
   395         return NativeJSContainer::CreateObjectInstance(env, instance, object);
   396     }
   397 };
   399 struct HasProperty
   400 {
   401     typedef jboolean Type;
   403     static bool InValue(JS::HandleValue val) {
   404         return true;
   405     }
   407     static Type FromValue(JNIEnv* env, jobject instance,
   408                           JSContext* cx, JS::HandleValue val) {
   409         return JNI_TRUE;
   410     }
   411 };
   413 MOZ_BEGIN_ENUM_CLASS(FallbackOption)
   414     THROW,
   415     RETURN,
   416 MOZ_END_ENUM_CLASS(FallbackOption)
   418 template <class Property>
   419 typename Property::Type
   420 GetProperty(JNIEnv* env, jobject instance, jstring name,
   421             FallbackOption option = FallbackOption::THROW,
   422             typename Property::Type fallback = typename Property::Type()) {
   423     MOZ_ASSERT(env);
   424     MOZ_ASSERT(instance);
   426     JSContext* const cx =
   427         NativeJSContainer::GetContextFromObject(env, instance);
   428     const JS::RootedObject object(cx,
   429         NativeJSContainer::GetObjectFromObject(env, instance));
   430     const JSJNIString strName(env, name);
   431     JS::RootedValue val(cx);
   433     if (!object ||
   434         !CheckJNIArgument(env, name) ||
   435         !CheckJSCall(env,
   436             JS_GetUCProperty(cx, object, strName, strName.Length(), &val))) {
   437         return typename Property::Type();
   438     }
   439     if (val.isUndefined()) {
   440         if (option == FallbackOption::THROW) {
   441             AndroidBridge::ThrowException(env,
   442                 "java/lang/IllegalArgumentException",
   443                 "Property does not exist");
   444         }
   445         return fallback;
   446     }
   447     if (!Property::InValue(val)) {
   448         AndroidBridge::ThrowException(env,
   449             "java/lang/IllegalArgumentException",
   450             "Property type mismatch");
   451         return fallback;
   452     }
   453     return Property::FromValue(env, instance, cx, val);
   454 }
   456 } // namespace
   458 extern "C" {
   460 NS_EXPORT void JNICALL
   461 Java_org_mozilla_gecko_util_NativeJSContainer_dispose(JNIEnv* env, jobject instance)
   462 {
   463     MOZ_ASSERT(env);
   464     NativeJSContainer::DisposeInstance(env, instance);
   465 }
   467 NS_EXPORT jobject JNICALL
   468 Java_org_mozilla_gecko_util_NativeJSContainer_clone(JNIEnv* env, jobject instance)
   469 {
   470     MOZ_ASSERT(env);
   471     return NativeJSContainer::CloneInstance(env, instance);
   472 }
   474 NS_EXPORT jboolean JNICALL
   475 Java_org_mozilla_gecko_util_NativeJSObject_getBoolean(JNIEnv* env, jobject instance, jstring name)
   476 {
   477     return GetProperty<BooleanProperty>(env, instance, name);
   478 }
   480 NS_EXPORT jboolean JNICALL
   481 Java_org_mozilla_gecko_util_NativeJSObject_optBoolean(JNIEnv* env, jobject instance,
   482                                                       jstring name, jboolean fallback)
   483 {
   484     return GetProperty<BooleanProperty>(env, instance, name, FallbackOption::RETURN, fallback);
   485 }
   487 NS_EXPORT jdouble JNICALL
   488 Java_org_mozilla_gecko_util_NativeJSObject_getDouble(JNIEnv* env, jobject instance, jstring name)
   489 {
   490     return GetProperty<DoubleProperty>(env, instance, name);
   491 }
   493 NS_EXPORT jdouble JNICALL
   494 Java_org_mozilla_gecko_util_NativeJSObject_optDouble(JNIEnv* env, jobject instance,
   495                                                      jstring name, jdouble fallback)
   496 {
   497     return GetProperty<DoubleProperty>(env, instance, name, FallbackOption::RETURN, fallback);
   498 }
   500 NS_EXPORT jint JNICALL
   501 Java_org_mozilla_gecko_util_NativeJSObject_getInt(JNIEnv* env, jobject instance, jstring name)
   502 {
   503     return GetProperty<IntProperty>(env, instance, name);
   504 }
   506 NS_EXPORT jint JNICALL
   507 Java_org_mozilla_gecko_util_NativeJSObject_optInt(JNIEnv* env, jobject instance,
   508                                                   jstring name, jint fallback)
   509 {
   510     return GetProperty<IntProperty>(env, instance, name, FallbackOption::RETURN, fallback);
   511 }
   513 NS_EXPORT jobject JNICALL
   514 Java_org_mozilla_gecko_util_NativeJSObject_getObject(JNIEnv* env, jobject instance, jstring name)
   515 {
   516     return GetProperty<ObjectProperty>(env, instance, name);
   517 }
   519 NS_EXPORT jobject JNICALL
   520 Java_org_mozilla_gecko_util_NativeJSObject_optObject(JNIEnv* env, jobject instance,
   521                                                      jstring name, jobject fallback)
   522 {
   523     return GetProperty<ObjectProperty>(env, instance, name, FallbackOption::RETURN, fallback);
   524 }
   526 NS_EXPORT jstring JNICALL
   527 Java_org_mozilla_gecko_util_NativeJSObject_getString(JNIEnv* env, jobject instance, jstring name)
   528 {
   529     return GetProperty<StringProperty>(env, instance, name);
   530 }
   532 NS_EXPORT jstring JNICALL
   533 Java_org_mozilla_gecko_util_NativeJSObject_optString(JNIEnv* env, jobject instance,
   534                                                      jstring name, jstring fallback)
   535 {
   536     return GetProperty<StringProperty>(env, instance, name, FallbackOption::RETURN, fallback);
   537 }
   539 NS_EXPORT jboolean JNICALL
   540 Java_org_mozilla_gecko_util_NativeJSObject_has(JNIEnv* env, jobject instance, jstring name)
   541 {
   542     return GetProperty<HasProperty>(env, instance, name, FallbackOption::RETURN, JNI_FALSE);
   543 }
   545 NS_EXPORT jstring JNICALL
   546 Java_org_mozilla_gecko_util_NativeJSObject_toString(JNIEnv* env, jobject instance)
   547 {
   548     MOZ_ASSERT(env);
   549     MOZ_ASSERT(instance);
   551     JSContext* const cx = NativeJSContainer::GetContextFromObject(env, instance);
   552     const JS::RootedObject object(cx, NativeJSContainer::GetObjectFromObject(env, instance));
   553     JS::RootedValue value(cx, JS::ObjectValue(*object));
   554     nsAutoString json;
   556     if (!object ||
   557         !CheckJSCall(env,
   558             JS_Stringify(cx, &value, JS::NullPtr(), JS::NullHandleValue, AppendJSON, &json))) {
   559         return nullptr;
   560     }
   561     jstring ret = env->NewString(reinterpret_cast<const jchar*>(json.get()), json.Length());
   562     AndroidBridge::HandleUncaughtException(env);
   563     MOZ_ASSERT(ret);
   564     return ret;
   565 }
   567 } // extern "C"

mercurial