summaryrefslogtreecommitdiffstats
path: root/widget/android/NativeJSContainer.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /widget/android/NativeJSContainer.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'widget/android/NativeJSContainer.cpp')
-rw-r--r--widget/android/NativeJSContainer.cpp881
1 files changed, 881 insertions, 0 deletions
diff --git a/widget/android/NativeJSContainer.cpp b/widget/android/NativeJSContainer.cpp
new file mode 100644
index 000000000..b8c434656
--- /dev/null
+++ b/widget/android/NativeJSContainer.cpp
@@ -0,0 +1,881 @@
+/* -*- 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 <jni.h>
+
+#include "Bundle.h"
+#include "GeneratedJNINatives.h"
+#include "MainThreadUtils.h"
+#include "jsapi.h"
+#include "nsJSUtils.h"
+
+#include <mozilla/Vector.h>
+#include <mozilla/jni/Accessors.h>
+#include <mozilla/jni/Refs.h>
+#include <mozilla/jni/Utils.h>
+
+/**
+ * NativeJSContainer.cpp implements the native methods in both
+ * NativeJSContainer and NativeJSObject, using JSAPI to retrieve values from a
+ * JSObject and using JNI to return those values to Java code.
+ */
+
+namespace mozilla {
+namespace widget {
+
+namespace {
+
+bool CheckThread()
+{
+ if (!NS_IsMainThread()) {
+ jni::ThrowException("java/lang/IllegalThreadStateException",
+ "Not on Gecko thread");
+ return false;
+ }
+ return true;
+}
+
+template<class C, typename T> bool
+CheckJNIArgument(const jni::Ref<C, T>& arg)
+{
+ if (!arg) {
+ jni::ThrowException("java/lang/IllegalArgumentException",
+ "Null argument");
+ }
+ return !!arg;
+}
+
+nsresult
+CheckSDKCall(nsresult rv)
+{
+ if (NS_FAILED(rv)) {
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "SDK JNI call failed");
+ }
+ return rv;
+}
+
+// Convert a JNI string to a char16_t string that JSAPI expects.
+class JSJNIString final
+{
+ JNIEnv* const mEnv;
+ jni::String::Param mJNIString;
+ const char16_t* const mJSString;
+
+public:
+ JSJNIString(JNIEnv* env, jni::String::Param str)
+ : mEnv(env)
+ , mJNIString(str)
+ , mJSString(!str ? nullptr : reinterpret_cast<const char16_t*>(
+ mEnv->GetStringChars(str.Get(), nullptr)))
+ {}
+
+ ~JSJNIString() {
+ if (mJNIString) {
+ mEnv->ReleaseStringChars(mJNIString.Get(),
+ reinterpret_cast<const jchar*>(mJSString));
+ }
+ }
+
+ operator const char16_t*() const {
+ return mJSString;
+ }
+
+ size_t Length() const {
+ return static_cast<size_t>(mEnv->GetStringLength(mJNIString.Get()));
+ }
+};
+
+} // namepsace
+
+class NativeJSContainerImpl final
+ : public NativeJSObject::Natives<NativeJSContainerImpl>
+ , public NativeJSContainer::Natives<NativeJSContainerImpl>
+{
+ typedef NativeJSContainerImpl Self;
+ typedef NativeJSContainer::Natives<NativeJSContainerImpl> ContainerBase;
+ typedef NativeJSObject::Natives<NativeJSContainerImpl> ObjectBase;
+
+ typedef JS::PersistentRooted<JSObject*> PersistentObject;
+
+ JNIEnv* const mEnv;
+ // Context that the object is valid in
+ JSContext* const mJSContext;
+ // Root JS object
+ PersistentObject mJSObject;
+ // Children objects
+ Vector<NativeJSObject::GlobalRef, 0> mChildren;
+
+ bool CheckObject() const
+ {
+ if (!mJSObject) {
+ jni::ThrowException("java/lang/NullPointerException",
+ "Null JSObject");
+ }
+ return !!mJSObject;
+ }
+
+ bool CheckJSCall(bool result) const
+ {
+ if (!result) {
+ JS_ClearPendingException(mJSContext);
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "JSAPI call failed");
+ }
+ return result;
+ }
+
+ // Check that a JS Value contains a particular property type as indicaed by
+ // the property's InValue method (e.g. StringProperty::InValue).
+ bool CheckProperty(bool (Self::*InValue)(JS::HandleValue) const,
+ JS::HandleValue val) const
+ {
+ if (!(this->*InValue)(val)) {
+ // XXX this can happen when converting a double array inside a
+ // Bundle, because double arrays can be misidentified as an int
+ // array. The workaround is to add a dummy first element to the
+ // array that is a floating point value, i.e. [0.5, ...].
+ jni::ThrowException(
+ "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
+ "Property type mismatch");
+ return false;
+ }
+ return true;
+ }
+
+ // Primitive properties
+
+ template<bool (JS::Value::*IsType)() const> bool
+ PrimitiveInValue(JS::HandleValue val) const
+ {
+ return (static_cast<const JS::Value&>(val).*IsType)();
+ }
+
+ template<typename U, U (JS::Value::*ToType)() const> U
+ PrimitiveFromValue(JS::HandleValue val) const
+ {
+ return (static_cast<const JS::Value&>(val).*ToType)();
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ PrimitiveNewArray(JS::HandleObject array, size_t length) const
+ {
+ typedef typename Prop::JNIType JNIType;
+
+ // Fill up a temporary buffer for our array, then use
+ // JNIEnv::Set*ArrayRegion to fill out array in one go.
+
+ UniquePtr<JNIType[]> buffer = MakeUnique<JNIType[]>(length);
+ for (size_t i = 0; i < length; i++) {
+ JS::RootedValue elem(mJSContext);
+ if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
+ !CheckProperty(Prop::InValue, elem)) {
+ return nullptr;
+ }
+ buffer[i] = JNIType((this->*Prop::FromValue)(elem));
+ }
+ auto jarray = Prop::NativeArray::Adopt(
+ mEnv, (mEnv->*Prop::NewJNIArray)(length));
+ if (!jarray) {
+ return nullptr;
+ }
+ (mEnv->*Prop::SetJNIArrayRegion)(
+ jarray.Get(), 0, length, buffer.get());
+ if (mEnv->ExceptionCheck()) {
+ return nullptr;
+ }
+ return jarray;
+ }
+
+ template<typename U, typename UA, typename V, typename VA,
+ bool (JS::Value::*IsType)() const,
+ U (JS::Value::*ToType)() const,
+ VA (JNIEnv::*NewArray_)(jsize),
+ void (JNIEnv::*SetArrayRegion_)(VA, jsize, jsize, const V*)>
+ struct PrimitiveProperty
+ {
+ // C++ type for a primitive property (e.g. bool)
+ typedef U NativeType;
+ // C++ type for the fallback value used in opt* methods
+ typedef U NativeFallback;
+ // Type for an array of the primitive type (e.g. BooleanArray::LocalRef)
+ typedef typename UA::LocalRef NativeArray;
+ // Type for the fallback value used in opt*Array methods
+ typedef const typename UA::Ref ArrayFallback;
+ // JNI type (e.g. jboolean)
+ typedef V JNIType;
+
+ // JNIEnv function to create a new JNI array of the primiive type
+ typedef decltype(NewArray_) NewJNIArray_t;
+ static constexpr NewJNIArray_t NewJNIArray = NewArray_;
+
+ // JNIEnv function to fill a JNI array of the primiive type
+ typedef decltype(SetArrayRegion_) SetJNIArrayRegion_t;
+ static constexpr SetJNIArrayRegion_t SetJNIArrayRegion = SetArrayRegion_;
+
+ // Function to determine if a JS Value contains the primitive type
+ typedef decltype(&Self::PrimitiveInValue<IsType>) InValue_t;
+ static constexpr InValue_t InValue = &Self::PrimitiveInValue<IsType>;
+
+ // Function to convert a JS Value to the primitive type
+ typedef decltype(&Self::PrimitiveFromValue<U, ToType>) FromValue_t;
+ static constexpr FromValue_t FromValue
+ = &Self::PrimitiveFromValue<U, ToType>;
+
+ // Function to convert a JS array to a JNI array
+ typedef decltype(&Self::PrimitiveNewArray<PrimitiveProperty>) NewArray_t;
+ static constexpr NewArray_t NewArray
+ = &Self::PrimitiveNewArray<PrimitiveProperty>;
+ };
+
+ // String properties
+
+ bool StringInValue(JS::HandleValue val) const
+ {
+ return val.isString();
+ }
+
+ jni::String::LocalRef
+ StringFromValue(const JS::HandleString str) const
+ {
+ nsAutoJSString autoStr;
+ if (!CheckJSCall(autoStr.init(mJSContext, str))) {
+ return nullptr;
+ }
+ // StringParam can automatically convert a nsString to jstring.
+ return jni::StringParam(autoStr, mEnv);
+ }
+
+ jni::String::LocalRef
+ StringFromValue(JS::HandleValue val)
+ {
+ const JS::RootedString str(mJSContext, val.toString());
+ return StringFromValue(str);
+ }
+
+ // Bundle properties
+
+ sdk::Bundle::LocalRef
+ BundleFromValue(const JS::HandleObject obj)
+ {
+ JS::Rooted<JS::IdVector> ids(mJSContext, JS::IdVector(mJSContext));
+ if (!CheckJSCall(JS_Enumerate(mJSContext, obj, &ids))) {
+ return nullptr;
+ }
+
+ const size_t length = ids.length();
+ sdk::Bundle::LocalRef newBundle(mEnv);
+ NS_ENSURE_SUCCESS(CheckSDKCall(
+ sdk::Bundle::New(length, &newBundle)), nullptr);
+
+ // Iterate through each property of the JS object. For each property,
+ // determine its type from a list of supported types, and convert that
+ // proeprty to the supported type.
+
+ for (size_t i = 0; i < ids.length(); i++) {
+ const JS::RootedId id(mJSContext, ids[i]);
+ JS::RootedValue idVal(mJSContext);
+ if (!CheckJSCall(JS_IdToValue(mJSContext, id, &idVal))) {
+ return nullptr;
+ }
+
+ const JS::RootedString idStr(mJSContext,
+ JS::ToString(mJSContext, idVal));
+ if (!CheckJSCall(!!idStr)) {
+ return nullptr;
+ }
+
+ jni::String::LocalRef name = StringFromValue(idStr);
+ JS::RootedValue val(mJSContext);
+ if (!name ||
+ !CheckJSCall(JS_GetPropertyById(mJSContext, obj, id, &val))) {
+ return nullptr;
+ }
+
+#define PUT_IN_BUNDLE_IF_TYPE_IS(TYPE) \
+ if ((this->*TYPE##Property::InValue)(val)) { \
+ auto jval = (this->*TYPE##Property::FromValue)(val); \
+ if (mEnv->ExceptionCheck()) { \
+ return nullptr; \
+ } \
+ NS_ENSURE_SUCCESS(CheckSDKCall( \
+ newBundle->Put##TYPE(name, jval)), 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.
+ // XXX empty arrays are treated as boolean arrays. Workaround is
+ // to always have a dummy element to create a non-empty array.
+ 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.
+ // When that happens, the Bundle conversion will fail.
+ 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.
+ jni::ThrowException("java/lang/UnsupportedOperationException",
+ "Unsupported property type");
+ return nullptr;
+ }
+ return jni::Object::LocalRef::Adopt(newBundle.Env(),
+ newBundle.Forget());
+ }
+
+ sdk::Bundle::LocalRef
+ BundleFromValue(JS::HandleValue val)
+ {
+ if (val.isNull()) {
+ return nullptr;
+ }
+ JS::RootedObject object(mJSContext, &val.toObject());
+ return BundleFromValue(object);
+ }
+
+ // Object properties
+
+ bool ObjectInValue(JS::HandleValue val) const
+ {
+ return val.isObjectOrNull();
+ }
+
+ NativeJSObject::LocalRef
+ ObjectFromValue(JS::HandleValue val)
+ {
+ if (val.isNull()) {
+ return nullptr;
+ }
+ JS::RootedObject object(mJSContext, &val.toObject());
+ return CreateChild(object);
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ ObjectNewArray(JS::HandleObject array, size_t length)
+ {
+ auto jarray = Prop::NativeArray::Adopt(mEnv, mEnv->NewObjectArray(
+ length, typename Prop::ClassType::Context().ClassRef(),
+ nullptr));
+ if (!jarray) {
+ return nullptr;
+ }
+
+ // For object arrays, we have to set each element separately.
+ for (size_t i = 0; i < length; i++) {
+ JS::RootedValue elem(mJSContext);
+ if (!CheckJSCall(JS_GetElement(mJSContext, array, i, &elem)) ||
+ !CheckProperty(Prop::InValue, elem)) {
+ return nullptr;
+ }
+ mEnv->SetObjectArrayElement(
+ jarray.Get(), i, (this->*Prop::FromValue)(elem).Get());
+ if (mEnv->ExceptionCheck()) {
+ return nullptr;
+ }
+ }
+ return jarray;
+ }
+
+ template<class Class,
+ bool (Self::*InValue_)(JS::HandleValue) const,
+ typename Class::LocalRef (Self::*FromValue_)(JS::HandleValue)>
+ struct BaseObjectProperty
+ {
+ // JNI class for the object type (e.g. jni::String)
+ typedef Class ClassType;
+
+ // See comments in PrimitiveProperty.
+ typedef typename ClassType::LocalRef NativeType;
+ typedef const typename ClassType::Ref NativeFallback;
+ typedef typename jni::ObjectArray::LocalRef NativeArray;
+ typedef const jni::ObjectArray::Ref ArrayFallback;
+
+ typedef decltype(InValue_) InValue_t;
+ static constexpr InValue_t InValue = InValue_;
+
+ typedef decltype(FromValue_) FromValue_t;
+ static constexpr FromValue_t FromValue = FromValue_;
+
+ typedef decltype(&Self::ObjectNewArray<BaseObjectProperty>) NewArray_t;
+ static constexpr NewArray_t NewArray
+ = &Self::ObjectNewArray<BaseObjectProperty>;
+ };
+
+ // Array properties
+
+ template<class Prop> bool
+ ArrayInValue(JS::HandleValue val) const
+ {
+ if (!val.isObject()) {
+ return false;
+ }
+ JS::RootedObject obj(mJSContext, &val.toObject());
+ bool isArray;
+ uint32_t length = 0;
+ if (!JS_IsArrayObject(mJSContext, obj, &isArray) ||
+ !isArray ||
+ !JS_GetArrayLength(mJSContext, obj, &length)) {
+ JS_ClearPendingException(mJSContext);
+ 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(mJSContext);
+ if (!JS_GetElement(mJSContext, obj, 0, &element)) {
+ JS_ClearPendingException(mJSContext);
+ return false;
+ }
+ return (this->*Prop::InValue)(element);
+ }
+
+ template<class Prop> typename Prop::NativeArray
+ ArrayFromValue(JS::HandleValue val)
+ {
+ JS::RootedObject obj(mJSContext, &val.toObject());
+ uint32_t length = 0;
+ if (!CheckJSCall(JS_GetArrayLength(mJSContext, obj, &length))) {
+ return nullptr;
+ }
+ return (this->*Prop::NewArray)(obj, length);
+ }
+
+ template<class Prop>
+ struct ArrayProperty
+ {
+ // See comments in PrimitiveProperty.
+ typedef typename Prop::NativeArray NativeType;
+ typedef typename Prop::ArrayFallback NativeFallback;
+
+ typedef decltype(&Self::ArrayInValue<Prop>) InValue_t;
+ static constexpr InValue_t InValue
+ = &Self::ArrayInValue<Prop>;
+
+ typedef decltype(&Self::ArrayFromValue<Prop>) FromValue_t;
+ static constexpr FromValue_t FromValue
+ = &Self::ArrayFromValue<Prop>;
+ };
+
+ // "Has" property is a special property type that is used to implement
+ // NativeJSObject.has, by returning true from InValue and FromValue for
+ // every existing property, and having false as the fallback value for
+ // when a property doesn't exist.
+
+ bool HasValue(JS::HandleValue val) const
+ {
+ return true;
+ }
+
+ struct HasProperty
+ {
+ // See comments in PrimitiveProperty.
+ typedef bool NativeType;
+ typedef bool NativeFallback;
+
+ typedef decltype(&Self::HasValue) HasValue_t;
+ static constexpr HasValue_t InValue = &Self::HasValue;
+ static constexpr HasValue_t FromValue = &Self::HasValue;
+ };
+
+ // 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<
+ bool, jni::BooleanArray, jboolean, jbooleanArray,
+ &JS::Value::isBoolean, &JS::Value::toBoolean,
+ &JNIEnv::NewBooleanArray, &JNIEnv::SetBooleanArrayRegion>
+ BooleanProperty;
+
+ typedef PrimitiveProperty<
+ double, jni::DoubleArray, jdouble, jdoubleArray,
+ &JS::Value::isNumber, &JS::Value::toNumber,
+ &JNIEnv::NewDoubleArray, &JNIEnv::SetDoubleArrayRegion>
+ DoubleProperty;
+
+ typedef PrimitiveProperty<
+ int32_t, jni::IntArray, jint, jintArray,
+ &JS::Value::isInt32, &JS::Value::toInt32,
+ &JNIEnv::NewIntArray, &JNIEnv::SetIntArrayRegion>
+ IntProperty;
+
+ typedef BaseObjectProperty<
+ jni::String, &Self::StringInValue, &Self::StringFromValue>
+ StringProperty;
+
+ typedef BaseObjectProperty<
+ sdk::Bundle, &Self::ObjectInValue, &Self::BundleFromValue>
+ BundleProperty;
+
+ typedef BaseObjectProperty<
+ NativeJSObject, &Self::ObjectInValue, &Self::ObjectFromValue>
+ ObjectProperty;
+
+ typedef ArrayProperty<BooleanProperty> BooleanArrayProperty;
+ typedef ArrayProperty<DoubleProperty> DoubleArrayProperty;
+ typedef ArrayProperty<IntProperty> IntArrayProperty;
+ typedef ArrayProperty<StringProperty> StringArrayProperty;
+ typedef ArrayProperty<BundleProperty> BundleArrayProperty;
+ typedef ArrayProperty<ObjectProperty> ObjectArrayProperty;
+
+ template<class Prop>
+ typename Prop::NativeType
+ GetProperty(jni::String::Param name,
+ typename Prop::NativeFallback* fallback = nullptr)
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return typename Prop::NativeType();
+ }
+
+ const JSJNIString nameStr(mEnv, name);
+ JS::RootedValue val(mJSContext);
+
+ if (!CheckJNIArgument(name) ||
+ !CheckJSCall(JS_GetUCProperty(
+ mJSContext, mJSObject, nameStr, nameStr.Length(), &val))) {
+ return typename Prop::NativeType();
+ }
+
+ // Strictly, null is different from undefined in JS. However, in
+ // practice, null is often used to indicate a property doesn't exist in
+ // the same manner as undefined. Therefore, we treat null in the same
+ // way as undefined when checking property existence (bug 1014965).
+ if (val.isUndefined() || val.isNull()) {
+ if (fallback) {
+ return mozilla::Move(*fallback);
+ }
+ jni::ThrowException(
+ "org/mozilla/gecko/util/NativeJSObject$InvalidPropertyException",
+ "Property does not exist");
+ return typename Prop::NativeType();
+ }
+
+ if (!CheckProperty(Prop::InValue, val)) {
+ return typename Prop::NativeType();
+ }
+ return (this->*Prop::FromValue)(val);
+ }
+
+ NativeJSObject::LocalRef CreateChild(JS::HandleObject object)
+ {
+ auto instance = NativeJSObject::New();
+ mozilla::UniquePtr<NativeJSContainerImpl> impl(
+ new NativeJSContainerImpl(instance.Env(), mJSContext, object));
+
+ ObjectBase::AttachNative(instance, mozilla::Move(impl));
+ if (!mChildren.append(NativeJSObject::GlobalRef(instance))) {
+ MOZ_CRASH();
+ }
+ return instance;
+ }
+
+ NativeJSContainerImpl(JNIEnv* env, JSContext* cx, JS::HandleObject object)
+ : mEnv(env)
+ , mJSContext(cx)
+ , mJSObject(cx, object)
+ {}
+
+public:
+ ~NativeJSContainerImpl()
+ {
+ // Dispose of all children on destruction. The children will in turn
+ // dispose any of their children (i.e. our grandchildren) and so on.
+ NativeJSObject::LocalRef child(mEnv);
+ for (size_t i = 0; i < mChildren.length(); i++) {
+ child = mChildren[i];
+ ObjectBase::GetNative(child)->ObjectBase::DisposeNative(child);
+ }
+ }
+
+ static NativeJSContainer::LocalRef
+ CreateInstance(JSContext* cx, JS::HandleObject object)
+ {
+ auto instance = NativeJSContainer::New();
+ mozilla::UniquePtr<NativeJSContainerImpl> impl(
+ new NativeJSContainerImpl(instance.Env(), cx, object));
+
+ ContainerBase::AttachNative(instance, mozilla::Move(impl));
+ return instance;
+ }
+
+ // NativeJSContainer methods
+
+ void DisposeNative(const NativeJSContainer::LocalRef& instance)
+ {
+ if (!CheckThread()) {
+ return;
+ }
+ ContainerBase::DisposeNative(instance);
+ }
+
+ NativeJSContainer::LocalRef Clone()
+ {
+ if (!CheckThread()) {
+ return nullptr;
+ }
+ return CreateInstance(mJSContext, mJSObject);
+ }
+
+ // NativeJSObject methods
+
+ bool GetBoolean(jni::String::Param name)
+ {
+ return GetProperty<BooleanProperty>(name);
+ }
+
+ bool OptBoolean(jni::String::Param name, bool fallback)
+ {
+ return GetProperty<BooleanProperty>(name, &fallback);
+ }
+
+ jni::BooleanArray::LocalRef
+ GetBooleanArray(jni::String::Param name)
+ {
+ return GetProperty<BooleanArrayProperty>(name);
+ }
+
+ jni::BooleanArray::LocalRef
+ OptBooleanArray(jni::String::Param name, jni::BooleanArray::Param fallback)
+ {
+ return GetProperty<BooleanArrayProperty>(name, &fallback);
+ }
+
+ jni::Object::LocalRef
+ GetBundle(jni::String::Param name)
+ {
+ return GetProperty<BundleProperty>(name);
+ }
+
+ jni::Object::LocalRef
+ OptBundle(jni::String::Param name, jni::Object::Param fallback)
+ {
+ // Because the GetProperty expects a sdk::Bundle::Param,
+ // we have to do conversions here from jni::Object::Param.
+ const auto& fb = sdk::Bundle::Ref::From(fallback.Get());
+ return GetProperty<BundleProperty>(name, &fb);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetBundleArray(jni::String::Param name)
+ {
+ return GetProperty<BundleArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptBundleArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<BundleArrayProperty>(name, &fallback);
+ }
+
+ double GetDouble(jni::String::Param name)
+ {
+ return GetProperty<DoubleProperty>(name);
+ }
+
+ double OptDouble(jni::String::Param name, double fallback)
+ {
+ return GetProperty<DoubleProperty>(name, &fallback);
+ }
+
+ jni::DoubleArray::LocalRef
+ GetDoubleArray(jni::String::Param name)
+ {
+ return GetProperty<DoubleArrayProperty>(name);
+ }
+
+ jni::DoubleArray::LocalRef
+ OptDoubleArray(jni::String::Param name, jni::DoubleArray::Param fallback)
+ {
+ jni::DoubleArray::LocalRef fb(fallback);
+ return GetProperty<DoubleArrayProperty>(name, &fb);
+ }
+
+ int GetInt(jni::String::Param name)
+ {
+ return GetProperty<IntProperty>(name);
+ }
+
+ int OptInt(jni::String::Param name, int fallback)
+ {
+ return GetProperty<IntProperty>(name, &fallback);
+ }
+
+ jni::IntArray::LocalRef
+ GetIntArray(jni::String::Param name)
+ {
+ return GetProperty<IntArrayProperty>(name);
+ }
+
+ jni::IntArray::LocalRef
+ OptIntArray(jni::String::Param name, jni::IntArray::Param fallback)
+ {
+ jni::IntArray::LocalRef fb(fallback);
+ return GetProperty<IntArrayProperty>(name, &fb);
+ }
+
+ NativeJSObject::LocalRef
+ GetObject(jni::String::Param name)
+ {
+ return GetProperty<ObjectProperty>(name);
+ }
+
+ NativeJSObject::LocalRef
+ OptObject(jni::String::Param name, NativeJSObject::Param fallback)
+ {
+ return GetProperty<ObjectProperty>(name, &fallback);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetObjectArray(jni::String::Param name)
+ {
+ return GetProperty<ObjectArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptObjectArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<ObjectArrayProperty>(name, &fallback);
+ }
+
+ jni::String::LocalRef
+ GetString(jni::String::Param name)
+ {
+ return GetProperty<StringProperty>(name);
+ }
+
+ jni::String::LocalRef
+ OptString(jni::String::Param name, jni::String::Param fallback)
+ {
+ return GetProperty<StringProperty>(name, &fallback);
+ }
+
+ jni::ObjectArray::LocalRef
+ GetStringArray(jni::String::Param name)
+ {
+ return GetProperty<StringArrayProperty>(name);
+ }
+
+ jni::ObjectArray::LocalRef
+ OptStringArray(jni::String::Param name, jni::ObjectArray::Param fallback)
+ {
+ return GetProperty<StringArrayProperty>(name, &fallback);
+ }
+
+ bool Has(jni::String::Param name)
+ {
+ bool no = false;
+ // Fallback to false indicating no such property.
+ return GetProperty<HasProperty>(name, &no);
+ }
+
+ jni::Object::LocalRef ToBundle()
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return nullptr;
+ }
+ return BundleFromValue(mJSObject);
+ }
+
+private:
+ static bool AppendJSON(const char16_t* buf, uint32_t len, void* data)
+ {
+ static_cast<nsAutoString*>(data)->Append(buf, len);
+ return true;
+ }
+
+public:
+ jni::String::LocalRef ToString()
+ {
+ if (!CheckThread() || !CheckObject()) {
+ return nullptr;
+ }
+
+ JS::RootedValue value(mJSContext, JS::ObjectValue(*mJSObject));
+ nsAutoString json;
+ if (!CheckJSCall(JS_Stringify(mJSContext, &value, nullptr,
+ JS::NullHandleValue, AppendJSON, &json))) {
+ return nullptr;
+ }
+ return jni::StringParam(json, mEnv);
+ }
+};
+
+
+// Define the "static constexpr" members of our property types (e.g.
+// PrimitiveProperty<>::InValue). This is tricky because there are a lot of
+// template parameters, so we use macros to make it simpler.
+
+#define DEFINE_PRIMITIVE_PROPERTY_MEMBER(Name) \
+ template<typename U, typename UA, typename V, typename VA, \
+ bool (JS::Value::*I)() const, \
+ U (JS::Value::*T)() const, \
+ VA (JNIEnv::*N)(jsize), \
+ void (JNIEnv::*S)(VA, jsize, jsize, const V*)> \
+ constexpr typename NativeJSContainerImpl \
+ ::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name##_t \
+ NativeJSContainerImpl::PrimitiveProperty<U, UA, V, VA, I, T, N, S>::Name
+
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewJNIArray);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(SetJNIArrayRegion);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(InValue);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(FromValue);
+DEFINE_PRIMITIVE_PROPERTY_MEMBER(NewArray);
+
+#undef DEFINE_PRIMITIVE_PROPERTY_MEMBER
+
+#define DEFINE_OBJECT_PROPERTY_MEMBER(Name) \
+ template<class C, \
+ bool (NativeJSContainerImpl::*I)(JS::HandleValue) const, \
+ typename C::LocalRef (NativeJSContainerImpl::*F)(JS::HandleValue)> \
+ constexpr typename NativeJSContainerImpl \
+ ::BaseObjectProperty<C, I, F>::Name##_t \
+ NativeJSContainerImpl::BaseObjectProperty<C, I, F>::Name
+
+DEFINE_OBJECT_PROPERTY_MEMBER(InValue);
+DEFINE_OBJECT_PROPERTY_MEMBER(FromValue);
+DEFINE_OBJECT_PROPERTY_MEMBER(NewArray);
+
+#undef DEFINE_OBJECT_PROPERTY_MEMBER
+
+template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
+ ::InValue_t NativeJSContainerImpl::ArrayProperty<P>::InValue;
+template<class P> constexpr typename NativeJSContainerImpl::ArrayProperty<P>
+ ::FromValue_t NativeJSContainerImpl::ArrayProperty<P>::FromValue;
+
+constexpr NativeJSContainerImpl::HasProperty::HasValue_t
+ NativeJSContainerImpl::HasProperty::InValue;
+constexpr NativeJSContainerImpl::HasProperty::HasValue_t
+ NativeJSContainerImpl::HasProperty::FromValue;
+
+
+NativeJSContainer::LocalRef
+CreateNativeJSContainer(JSContext* cx, JS::HandleObject object)
+{
+ return NativeJSContainerImpl::CreateInstance(cx, object);
+}
+
+} // namespace widget
+} // namespace mozilla
+