mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 22:25:30 +00:00
1012 lines
34 KiB
C++
1012 lines
34 KiB
C++
/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
|
* 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/. */
|
|
|
|
#include "NativeJSContainer.h"
|
|
#include "AndroidBridge.h"
|
|
#include "js/StructuredClone.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/Vector.h"
|
|
#include "prthread.h"
|
|
#include "nsJSUtils.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::widget;
|
|
|
|
namespace mozilla {
|
|
namespace widget {
|
|
|
|
class NativeJSContainer
|
|
{
|
|
public:
|
|
static void InitJNI(JNIEnv* env) {
|
|
if (jNativeJSContainer) {
|
|
return;
|
|
}
|
|
jNativeJSContainer = AndroidBridge::GetClassGlobalRef(
|
|
env, "org/mozilla/gecko/util/NativeJSContainer");
|
|
MOZ_ASSERT(jNativeJSContainer);
|
|
jContainerNativeObject = AndroidBridge::GetFieldID(
|
|
env, jNativeJSContainer, "mNativeObject", "J");
|
|
MOZ_ASSERT(jContainerNativeObject);
|
|
jContainerConstructor = AndroidBridge::GetMethodID(
|
|
env, jNativeJSContainer, "<init>", "(J)V");
|
|
MOZ_ASSERT(jContainerConstructor);
|
|
|
|
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);
|
|
|
|
jBundle = AndroidBridge::GetClassGlobalRef(
|
|
env, "android/os/Bundle");
|
|
MOZ_ASSERT(jBundle);
|
|
jBundleConstructor = AndroidBridge::GetMethodID(
|
|
env, jBundle, "<init>", "(I)V");
|
|
MOZ_ASSERT(jBundleConstructor);
|
|
jBundlePutBoolean = AndroidBridge::GetMethodID(
|
|
env, jBundle, "putBoolean",
|
|
"(Ljava/lang/String;Z)V");
|
|
MOZ_ASSERT(jBundlePutBoolean);
|
|
jBundlePutBundle = AndroidBridge::GetMethodID(
|
|
env, jBundle, "putBundle",
|
|
"(Ljava/lang/String;Landroid/os/Bundle;)V");
|
|
MOZ_ASSERT(jBundlePutBundle);
|
|
jBundlePutDouble = AndroidBridge::GetMethodID(
|
|
env, jBundle, "putDouble",
|
|
"(Ljava/lang/String;D)V");
|
|
MOZ_ASSERT(jBundlePutDouble);
|
|
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);
|
|
jBundlePutStringArray = AndroidBridge::GetMethodID(
|
|
env, jBundle, "putStringArray",
|
|
"(Ljava/lang/String;[Ljava/lang/String;)V");
|
|
MOZ_ASSERT(jBundlePutStringArray);
|
|
|
|
jObject = AndroidBridge::GetClassGlobalRef(
|
|
env, "java/lang/Object");
|
|
MOZ_ASSERT(jObject);
|
|
jString = AndroidBridge::GetClassGlobalRef(
|
|
env, "java/lang/String");
|
|
MOZ_ASSERT(jString);
|
|
}
|
|
|
|
static jobject CreateInstance(JNIEnv* env, JSContext* cx,
|
|
JS::HandleObject object) {
|
|
return CreateInstance(env, new NativeJSContainer(cx, object));
|
|
}
|
|
|
|
static NativeJSContainer* FromInstance(JNIEnv* env, jobject instance) {
|
|
MOZ_ASSERT(instance);
|
|
|
|
const jlong fieldValue =
|
|
env->GetLongField(instance, jContainerNativeObject);
|
|
NativeJSContainer* const nativeObject =
|
|
reinterpret_cast<NativeJSContainer* const>(
|
|
static_cast<uintptr_t>(fieldValue));
|
|
if (!nativeObject) {
|
|
AndroidBridge::ThrowException(env,
|
|
"java/lang/NullPointerException",
|
|
"Uninitialized NativeJSContainer");
|
|
}
|
|
return nativeObject;
|
|
}
|
|
|
|
static void DisposeInstance(JNIEnv* env, jobject instance) {
|
|
NativeJSContainer* const container = FromInstance(env, instance);
|
|
if (container) {
|
|
env->SetLongField(instance, jContainerNativeObject,
|
|
static_cast<jlong>(reinterpret_cast<uintptr_t>(nullptr)));
|
|
delete container;
|
|
}
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
static jobject GetInstanceFromObject(JNIEnv* env, jobject object) {
|
|
MOZ_ASSERT(object);
|
|
|
|
const jobject instance = env->GetObjectField(object, jObjectContainer);
|
|
MOZ_ASSERT(instance);
|
|
return instance;
|
|
}
|
|
|
|
static JSContext* GetContextFromObject(JNIEnv* env, jobject object) {
|
|
MOZ_ASSERT(object);
|
|
AutoLocalJNIFrame frame(env, 1);
|
|
|
|
NativeJSContainer* const container =
|
|
FromInstance(env, GetInstanceFromObject(env, object));
|
|
if (!container) {
|
|
return nullptr;
|
|
}
|
|
return container->mThreadContext;
|
|
}
|
|
|
|
static JSObject* GetObjectFromObject(JNIEnv* env, jobject object) {
|
|
MOZ_ASSERT(object);
|
|
AutoLocalJNIFrame frame(env, 1);
|
|
|
|
NativeJSContainer* const container =
|
|
FromInstance(env, GetInstanceFromObject(env, object));
|
|
if (!container ||
|
|
!container->EnsureObject(env)) { // Do thread check
|
|
return nullptr;
|
|
}
|
|
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,
|
|
JSContext* cx,
|
|
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();
|
|
PersistentObjectPtr rootedJSObject =
|
|
MakeUnique<PersistentObject>(cx, jsObject);
|
|
if (!container->mRootedObjects.append(Move(rootedJSObject))) {
|
|
AndroidBridge::ThrowException(env,
|
|
"java/lang/OutOfMemoryError", "Cannot allocate object");
|
|
return nullptr;
|
|
}
|
|
const jobject newObject =
|
|
env->NewObject(jNativeJSObject, jObjectConstructor,
|
|
instance, newIndex);
|
|
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",
|
|
"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 = MakeUnique<PersistentObject>(mThreadContext, &value.toObject());
|
|
}
|
|
if (!mJSObject) {
|
|
AndroidBridge::ThrowException(env,
|
|
"java/lang/IllegalStateException", "Cannot deserialize data");
|
|
return false;
|
|
}
|
|
mBuffer.clear();
|
|
return true;
|
|
}
|
|
|
|
static jclass jBundle;
|
|
static jclass jNativeJSContainer;
|
|
static jclass jNativeJSObject;
|
|
static jclass jObject;
|
|
static jclass jString;
|
|
|
|
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;
|
|
static jmethodID jBundlePutStringArray;
|
|
|
|
private:
|
|
static jfieldID jContainerNativeObject;
|
|
static jmethodID jContainerConstructor;
|
|
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)));
|
|
AndroidBridge::HandleUncaughtException(env);
|
|
MOZ_ASSERT(newObject);
|
|
return newObject;
|
|
}
|
|
|
|
typedef JS::PersistentRooted<JSObject*> PersistentObject;
|
|
typedef UniquePtr<PersistentObject> PersistentObjectPtr;
|
|
|
|
// 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
|
|
PersistentObjectPtr mJSObject;
|
|
// Serialized object, or empty if object is in deserialized form
|
|
JSAutoStructuredCloneBuffer mBuffer;
|
|
// Objects derived from mJSObject
|
|
Vector<PersistentObjectPtr, 0> mRootedObjects;
|
|
|
|
// Create a new container containing the given deserialized object
|
|
NativeJSContainer(JSContext* cx, JS::HandleObject object)
|
|
: mThread(PR_GetCurrentThread())
|
|
, mThreadContext(cx)
|
|
, mJSObject(new PersistentObject(cx, object))
|
|
{
|
|
}
|
|
|
|
// Create a new container containing the given serialized object
|
|
NativeJSContainer(JSContext* cx, JSAutoStructuredCloneBuffer&& buffer)
|
|
: mThread(PR_GetCurrentThread())
|
|
, mThreadContext(cx)
|
|
, mJSObject(nullptr)
|
|
, mBuffer(Forward<JSAutoStructuredCloneBuffer>(buffer))
|
|
{
|
|
}
|
|
|
|
bool SwitchContextToCurrentThread() {
|
|
PRThread* const currentThread = PR_GetCurrentThread();
|
|
if (currentThread == mThread) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
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;
|
|
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;
|
|
jmethodID NativeJSContainer::jBundlePutStringArray = 0;
|
|
jclass NativeJSContainer::jString = 0;
|
|
jclass NativeJSContainer::jObject = 0;
|
|
|
|
jobject
|
|
CreateNativeJSContainer(JNIEnv* env, JSContext* cx, JS::HandleObject object)
|
|
{
|
|
return NativeJSContainer::CreateInstance(env, cx, object);
|
|
}
|
|
|
|
} // namespace widget
|
|
} // namespace mozilla
|
|
|
|
namespace {
|
|
|
|
class JSJNIString
|
|
{
|
|
public:
|
|
JSJNIString(JNIEnv* env, jstring str)
|
|
: mEnv(env)
|
|
, mJNIString(str)
|
|
, mJSString(!str ? nullptr :
|
|
reinterpret_cast<const char16_t*>(env->GetStringChars(str, nullptr)))
|
|
{
|
|
}
|
|
~JSJNIString() {
|
|
if (mJNIString) {
|
|
mEnv->ReleaseStringChars(mJNIString,
|
|
reinterpret_cast<const jchar*>(mJSString));
|
|
}
|
|
}
|
|
operator const char16_t*() const {
|
|
return mJSString;
|
|
}
|
|
size_t Length() const {
|
|
return static_cast<size_t>(mEnv->GetStringLength(mJNIString));
|
|
}
|
|
private:
|
|
JNIEnv* const mEnv;
|
|
const jstring mJNIString;
|
|
const char16_t* const mJSString;
|
|
};
|
|
|
|
bool
|
|
CheckJSCall(JNIEnv* env, bool result)
|
|
{
|
|
if (!result) {
|
|
AndroidBridge::ThrowException(env,
|
|
"java/lang/UnsupportedOperationException", "JSAPI call failed");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
CheckJNIArgument(JNIEnv* env, jobject arg)
|
|
{
|
|
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,
|
|
"org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
|
|
"Property type mismatch");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AppendJSON(const char16_t* 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,
|
|
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(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) {
|
|
UniquePtr<Type[]> buffer = MakeUnique<Type[]>(length);
|
|
for (size_t i = 0; i < length; i++) {
|
|
JS::RootedValue elem(cx);
|
|
if (!CheckJSCall(env, JS_GetElement(cx, array, i, &elem)) ||
|
|
!CheckProperty<PrimitiveProperty::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.get());
|
|
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,
|
|
jbooleanArray, &JNIEnv::NewBooleanArray,
|
|
&JNIEnv::SetBooleanArrayRegion> BooleanProperty;
|
|
|
|
typedef PrimitiveProperty<jdouble, double,
|
|
&JS::Value::isNumber, &JS::Value::toNumber,
|
|
jdoubleArray, &JNIEnv::NewDoubleArray,
|
|
&JNIEnv::SetDoubleArrayRegion> DoubleProperty;
|
|
|
|
typedef PrimitiveProperty<jint, int32_t,
|
|
&JS::Value::isInt32, &JS::Value::toInt32,
|
|
jintArray, &JNIEnv::NewIntArray,
|
|
&JNIEnv::SetIntArrayRegion> IntProperty;
|
|
|
|
struct StringProperty
|
|
{
|
|
typedef jstring Type;
|
|
|
|
static jclass Class() {
|
|
return NativeJSContainer::jString;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static Type FromValue(JNIEnv* env, jobject instance,
|
|
JSContext* cx, const JS::HandleString str) {
|
|
nsAutoJSString autoStr;
|
|
if (!CheckJSCall(env, autoStr.init(cx, str))) {
|
|
return nullptr;
|
|
}
|
|
jstring ret = env->NewString(
|
|
reinterpret_cast<const jchar*>(autoStr.BeginReading()),
|
|
autoStr.Length());
|
|
MOZ_ASSERT(ret);
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
template <jclass& C, jobject (*FactoryMethod)
|
|
(JNIEnv*, jobject, JSContext*, JS::HandleObject)>
|
|
struct BaseObjectProperty
|
|
{
|
|
typedef jobject Type;
|
|
|
|
static jclass Class() {
|
|
return C;
|
|
}
|
|
|
|
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;
|
|
}
|
|
JS::RootedObject object(cx, &val.toObject());
|
|
return FactoryMethod(env, instance, cx, object);
|
|
}
|
|
};
|
|
|
|
jobject GetBundle(JNIEnv*, jobject, JSContext*, JS::HandleObject);
|
|
|
|
// Returns a NativeJSObject from a JSObject
|
|
typedef BaseObjectProperty<NativeJSContainer::jNativeJSObject,
|
|
NativeJSContainer::CreateObjectInstance> ObjectProperty;
|
|
|
|
// Returns a Bundle from a JSObject
|
|
typedef BaseObjectProperty<NativeJSContainer::jBundle,
|
|
GetBundle> BundleProperty;
|
|
|
|
template <class BaseProperty>
|
|
struct ObjectArrayWrapper : public BaseProperty
|
|
{
|
|
typedef jobjectArray ArrayType;
|
|
|
|
static ArrayType NewArray(JNIEnv* env, jobject instance, JSContext* cx,
|
|
JS::HandleObject array, size_t length) {
|
|
AutoLocalJNIFrame frame(env, 2);
|
|
ArrayType jarray = env->NewObjectArray(
|
|
length, BaseProperty::Class(), nullptr);
|
|
if (!jarray) {
|
|
return nullptr;
|
|
}
|
|
for (size_t i = 0; i < length; i++) {
|
|
JS::RootedValue elem(cx);
|
|
if (!CheckJSCall(env, JS_GetElement(cx, array, i, &elem)) ||
|
|
!CheckProperty<BaseProperty::InValue>(env, cx, elem)) {
|
|
return nullptr;
|
|
}
|
|
typename BaseProperty::Type jelem =
|
|
BaseProperty::FromValue(env, instance, cx, elem);
|
|
if (env->ExceptionCheck()) {
|
|
return nullptr;
|
|
}
|
|
env->SetObjectArrayElement(jarray, i, jelem);
|
|
env->DeleteLocalRef(jelem);
|
|
if (env->ExceptionCheck()) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return frame.Pop(jarray);
|
|
}
|
|
};
|
|
|
|
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;
|
|
typedef ArrayProperty<ObjectArrayWrapper<StringProperty>> StringArrayProperty;
|
|
typedef ArrayProperty<ObjectArrayWrapper<ObjectProperty>> ObjectArrayProperty;
|
|
typedef ArrayProperty<ObjectArrayWrapper<BundleProperty>> BundleArrayProperty;
|
|
|
|
struct HasProperty
|
|
{
|
|
typedef jboolean Type;
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
MOZ_BEGIN_ENUM_CLASS(FallbackOption)
|
|
THROW,
|
|
RETURN,
|
|
MOZ_END_ENUM_CLASS(FallbackOption)
|
|
|
|
template <class Property>
|
|
typename Property::Type
|
|
GetProperty(JNIEnv* env, jobject instance, jstring name,
|
|
FallbackOption option = FallbackOption::THROW,
|
|
typename Property::Type fallback = typename Property::Type())
|
|
{
|
|
MOZ_ASSERT(env);
|
|
MOZ_ASSERT(instance);
|
|
|
|
JSContext* const cx =
|
|
NativeJSContainer::GetContextFromObject(env, instance);
|
|
if (!cx) {
|
|
return typename Property::Type();
|
|
}
|
|
|
|
const JS::RootedObject object(cx,
|
|
NativeJSContainer::GetObjectFromObject(env, instance));
|
|
const JSJNIString strName(env, name);
|
|
JS::RootedValue val(cx);
|
|
|
|
if (!object ||
|
|
!CheckJNIArgument(env, name) ||
|
|
!CheckJSCall(env,
|
|
JS_GetUCProperty(cx, object, strName, strName.Length(), &val))) {
|
|
return typename Property::Type();
|
|
}
|
|
if (val.isUndefined() || val.isNull()) {
|
|
if (option == FallbackOption::THROW) {
|
|
AndroidBridge::ThrowException(env,
|
|
"org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
|
|
"Property does not exist");
|
|
}
|
|
return fallback;
|
|
}
|
|
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)
|
|
{
|
|
AutoLocalJNIFrame frame(env, 1);
|
|
|
|
const JS::AutoIdArray ids(cx, JS_Enumerate(cx, obj));
|
|
if (!CheckJSCall(env, !!ids)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const size_t length = ids.length();
|
|
const jobject newBundle = env->NewObject(
|
|
NativeJSContainer::jBundle,
|
|
NativeJSContainer::jBundleConstructor,
|
|
static_cast<jint>(length));
|
|
if (!newBundle) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (size_t i = 0; i < ids.length(); i++) {
|
|
AutoLocalJNIFrame loopFrame(env, 2);
|
|
|
|
const JS::RootedId id(cx, ids[i]);
|
|
JS::RootedValue idVal(cx);
|
|
if (!CheckJSCall(env, JS_IdToValue(cx, id, &idVal))) {
|
|
return nullptr;
|
|
}
|
|
|
|
const JS::RootedString idStr(cx, JS::ToString(cx, idVal));
|
|
if (!CheckJSCall(env, !!idStr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const jstring name =
|
|
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(cx, val)) { \
|
|
const TYPE##Property::Type jval = \
|
|
TYPE##Property::FromValue(env, instance, cx, val); \
|
|
if (env->ExceptionCheck()) { \
|
|
return nullptr; \
|
|
} \
|
|
env->CallVoidMethod(newBundle, \
|
|
NativeJSContainer::jBundlePut##TYPE, name, jval); \
|
|
if (env->ExceptionCheck()) { \
|
|
return nullptr; \
|
|
} \
|
|
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);
|
|
PUT_IN_BUNDLE_IF_TYPE_IS(StringArray);
|
|
// There's no "putObjectArray", so don't check ObjectArrayProperty
|
|
// There's no "putBundleArray", so don't check BundleArrayProperty
|
|
|
|
// 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",
|
|
"Unsupported property type");
|
|
return nullptr;
|
|
}
|
|
return frame.Pop(newBundle);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" {
|
|
|
|
NS_EXPORT void JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSContainer_dispose(JNIEnv* env, jobject instance)
|
|
{
|
|
MOZ_ASSERT(env);
|
|
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);
|
|
}
|
|
|
|
NS_EXPORT jboolean JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_getBoolean(JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<BooleanProperty>(env, instance, name);
|
|
}
|
|
|
|
NS_EXPORT jboolean JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optBoolean(JNIEnv* env, jobject instance,
|
|
jstring name, jboolean fallback)
|
|
{
|
|
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 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 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);
|
|
}
|
|
|
|
NS_EXPORT jobject JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optBundle(JNIEnv* env, jobject instance,
|
|
jstring name, jobject fallback)
|
|
{
|
|
return GetProperty<BundleProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_getBundleArray(
|
|
JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<BundleArrayProperty>(env, instance, name);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optBundleArray(
|
|
JNIEnv* env, jobject instance, jstring name, jobjectArray fallback)
|
|
{
|
|
return GetProperty<BundleArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
NS_EXPORT jdouble JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_getDouble(JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<DoubleProperty>(env, instance, name);
|
|
}
|
|
|
|
NS_EXPORT jdouble JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optDouble(JNIEnv* env, jobject instance,
|
|
jstring name, jdouble fallback)
|
|
{
|
|
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 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 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);
|
|
}
|
|
|
|
NS_EXPORT jint JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optInt(JNIEnv* env, jobject instance,
|
|
jstring name, jint fallback)
|
|
{
|
|
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 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 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);
|
|
}
|
|
|
|
NS_EXPORT jobject JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optObject(JNIEnv* env, jobject instance,
|
|
jstring name, jobject fallback)
|
|
{
|
|
return GetProperty<ObjectProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_getObjectArray(
|
|
JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<ObjectArrayProperty>(env, instance, name);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optObjectArray(
|
|
JNIEnv* env, jobject instance, jstring name, jobjectArray fallback)
|
|
{
|
|
return GetProperty<ObjectArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
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 jstring JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optString(JNIEnv* env, jobject instance,
|
|
jstring name, jstring fallback)
|
|
{
|
|
return GetProperty<StringProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_getStringArray(
|
|
JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<StringArrayProperty>(env, instance, name);
|
|
}
|
|
|
|
NS_EXPORT jobjectArray JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_optStringArray(
|
|
JNIEnv* env, jobject instance, jstring name, jobjectArray fallback)
|
|
{
|
|
return GetProperty<StringArrayProperty>(env, instance, name, FallbackOption::RETURN, fallback);
|
|
}
|
|
|
|
NS_EXPORT jboolean JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_has(JNIEnv* env, jobject instance, jstring name)
|
|
{
|
|
return GetProperty<HasProperty>(env, instance, name, FallbackOption::RETURN, JNI_FALSE);
|
|
}
|
|
|
|
NS_EXPORT jobject JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_toBundle(JNIEnv* env, jobject instance)
|
|
{
|
|
MOZ_ASSERT(env);
|
|
MOZ_ASSERT(instance);
|
|
|
|
JSContext* const cx = NativeJSContainer::GetContextFromObject(env, instance);
|
|
if (!cx) {
|
|
return nullptr;
|
|
}
|
|
|
|
const JS::RootedObject object(cx, NativeJSContainer::GetObjectFromObject(env, instance));
|
|
if (!object) {
|
|
return nullptr;
|
|
}
|
|
return GetBundle(env, instance, cx, object);
|
|
}
|
|
|
|
NS_EXPORT jstring JNICALL
|
|
Java_org_mozilla_gecko_util_NativeJSObject_toString(JNIEnv* env, jobject instance)
|
|
{
|
|
MOZ_ASSERT(env);
|
|
MOZ_ASSERT(instance);
|
|
|
|
JSContext* const cx = NativeJSContainer::GetContextFromObject(env, instance);
|
|
if (!cx) {
|
|
return nullptr;
|
|
}
|
|
|
|
const JS::RootedObject object(cx, NativeJSContainer::GetObjectFromObject(env, instance));
|
|
JS::RootedValue value(cx, JS::ObjectValue(*object));
|
|
nsAutoString json;
|
|
|
|
if (!object ||
|
|
!CheckJSCall(env,
|
|
JS_Stringify(cx, &value, JS::NullPtr(), JS::NullHandleValue, AppendJSON, &json))) {
|
|
return nullptr;
|
|
}
|
|
jstring ret = env->NewString(reinterpret_cast<const jchar*>(json.get()), json.Length());
|
|
MOZ_ASSERT(ret);
|
|
return ret;
|
|
}
|
|
|
|
} // extern "C"
|