diff --git a/mobile/android/base/util/NativeJSContainer.java b/mobile/android/base/util/NativeJSContainer.java index 770c8a48861d..486d4c29e837 100644 --- a/mobile/android/base/util/NativeJSContainer.java +++ b/mobile/android/base/util/NativeJSContainer.java @@ -10,6 +10,10 @@ import org.mozilla.gecko.mozglue.JNITarget; /** * NativeJSContainer is a wrapper around the SpiderMonkey JSAPI to make it possible to * access Javascript objects in Java. + * + * A container must only be used on the thread it is attached to. To use it on another + * thread, call {@link #clone()} to make a copy, and use the copy on the other thread. + * When a copy is first used, it becomes attached to the thread using it. */ @JNITarget public final class NativeJSContainer extends NativeJSObject @@ -20,6 +24,13 @@ public final class NativeJSContainer extends NativeJSObject mNativeObject = nativeObject; } + /** + * Make a copy of this container for use by another thread. When the copy is first used, + * it becomes attached to the thread using it. + */ + @Override + public native NativeJSContainer clone(); + /** * Dispose all associated native objects. Subsequent use of any objects derived from * this container will throw a NullPointerException. diff --git a/mozglue/android/jni-stubs.inc b/mozglue/android/jni-stubs.inc index 21ea258ab4c9..8db01c7c23d0 100644 --- a/mozglue/android/jni-stubs.inc +++ b/mozglue/android/jni-stubs.inc @@ -571,6 +571,25 @@ Java_org_mozilla_gecko_gfx_NativePanZoomController_getOverScrollMode(JNIEnv * ar #ifdef JNI_STUBS +typedef jobject (*Java_org_mozilla_gecko_util_NativeJSContainer_clone_t)(JNIEnv *, jobject); +static Java_org_mozilla_gecko_util_NativeJSContainer_clone_t f_Java_org_mozilla_gecko_util_NativeJSContainer_clone; +extern "C" NS_EXPORT jobject JNICALL +Java_org_mozilla_gecko_util_NativeJSContainer_clone(JNIEnv * arg0, jobject arg1) { + if (!f_Java_org_mozilla_gecko_util_NativeJSContainer_clone) { + arg0->ThrowNew(arg0->FindClass("java/lang/UnsupportedOperationException"), + "JNI Function called before it was loaded"); + return nullptr; + } + return f_Java_org_mozilla_gecko_util_NativeJSContainer_clone(arg0, arg1); +} +#endif + +#ifdef JNI_BINDINGS + xul_dlsym("Java_org_mozilla_gecko_util_NativeJSContainer_clone", &f_Java_org_mozilla_gecko_util_NativeJSContainer_clone); +#endif + +#ifdef JNI_STUBS + typedef void (*Java_org_mozilla_gecko_util_NativeJSContainer_dispose_t)(JNIEnv *, jobject); static Java_org_mozilla_gecko_util_NativeJSContainer_dispose_t f_Java_org_mozilla_gecko_util_NativeJSContainer_dispose; extern "C" NS_EXPORT void JNICALL diff --git a/widget/android/NativeJSContainer.cpp b/widget/android/NativeJSContainer.cpp index d0071398858e..54c4c18c22f1 100644 --- a/widget/android/NativeJSContainer.cpp +++ b/widget/android/NativeJSContainer.cpp @@ -5,6 +5,7 @@ #include "NativeJSContainer.h" #include "AndroidBridge.h" +#include "prthread.h" using namespace mozilla; using namespace mozilla::widget; @@ -60,6 +61,58 @@ public: } } + static jobject CloneInstance(JNIEnv* env, jobject instance) { + NativeJSContainer* const container = FromInstance(env, instance); + if (!container || !container->EnsureObject(env)) { + return nullptr; + } + JSContext* const cx = container->mThreadContext; + JS::RootedObject object(cx, container->mJSObject); + MOZ_ASSERT(object); + + JSAutoStructuredCloneBuffer buffer; + if (!buffer.write(cx, JS::RootedValue(cx, JS::ObjectValue(*object)))) { + AndroidBridge::ThrowException(env, + "java/lang/UnsupportedOperationException", + "Cannot serialize object"); + return nullptr; + } + return CreateInstance(env, new NativeJSContainer(cx, Move(buffer))); + } + + // 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", + "Using NativeJSObject off its thread"); + return false; + } + return true; + } + if (!SwitchContextToCurrentThread()) { + AndroidBridge::ThrowException(env, + "java/lang/IllegalThreadStateException", + "Not available for this thread"); + return false; + } + + JS::RootedValue value(mThreadContext); + MOZ_ASSERT(mBuffer.data()); + MOZ_ALWAYS_TRUE(mBuffer.read(mThreadContext, &value)); + if (value.isObject()) { + mJSObject = &value.toObject(); + } + if (!mJSObject) { + AndroidBridge::ThrowException(env, + "java/lang/IllegalStateException", "Cannot deserialize data"); + return false; + } + mBuffer.clear(); + return true; + } + private: static jclass jNativeJSContainer; static jfieldID jContainerNativeObject; @@ -77,16 +130,38 @@ private: return newObject; } - JSContext* const mContext; - // Root JS object - const JS::Heap mJSObject; + // 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 mJSObject; + // Serialized object, or empty if object is in deserialized form + JSAutoStructuredCloneBuffer mBuffer; - // Create a new container containing the given object + // Create a new container containing the given deserialized object NativeJSContainer(JSContext* cx, JS::HandleObject object) - : mContext(cx) + : mThread(PR_GetCurrentThread()) + , mThreadContext(cx) , mJSObject(object) { } + + // Create a new container containing the given serialized object + NativeJSContainer(JSContext* cx, JSAutoStructuredCloneBuffer&& buffer) + : mThread(PR_GetCurrentThread()) + , mThreadContext(cx) + , mBuffer(Forward(buffer)) + { + } + + bool SwitchContextToCurrentThread() { + PRThread* const currentThread = PR_GetCurrentThread(); + if (currentThread == mThread) { + return true; + } + return false; + } }; jclass NativeJSContainer::jNativeJSContainer = 0; @@ -111,4 +186,11 @@ Java_org_mozilla_gecko_util_NativeJSContainer_dispose(JNIEnv* env, jobject insta NativeJSContainer::DisposeInstance(env, instance); } +NS_EXPORT jobject JNICALL +Java_org_mozilla_gecko_util_NativeJSContainer_clone(JNIEnv* env, jobject instance) +{ + MOZ_ASSERT(env); + return NativeJSContainer::CloneInstance(env, instance); +} + } // extern "C"