/* -*- 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