diff options
Diffstat (limited to 'components/script/dom/bindings/codegen/BindingUtils.h')
-rw-r--r-- | components/script/dom/bindings/codegen/BindingUtils.h | 1151 |
1 files changed, 1151 insertions, 0 deletions
diff --git a/components/script/dom/bindings/codegen/BindingUtils.h b/components/script/dom/bindings/codegen/BindingUtils.h new file mode 100644 index 00000000000..ee9d6c3691c --- /dev/null +++ b/components/script/dom/bindings/codegen/BindingUtils.h @@ -0,0 +1,1151 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* vim: set ts=2 sw=2 et tw=79: */ +/* 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/. */ + +#ifndef mozilla_dom_BindingUtils_h__ +#define mozilla_dom_BindingUtils_h__ + +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/DOMJSProxyHandler.h" +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/ErrorResult.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jswrapper.h" + +#include "nsIXPConnect.h" +#include "qsObjectHelper.h" +#include "xpcpublic.h" +#include "nsTraceRefcnt.h" +#include "nsWrapperCacheInlines.h" +#include "mozilla/Likely.h" + +// nsGlobalWindow implements nsWrapperCache, but doesn't always use it. Don't +// try to use it without fixing that first. +class nsGlobalWindow; + +namespace mozilla { +namespace dom { + +enum ErrNum { +#define MSG_DEF(_name, _argc, _str) \ + _name, +#include "mozilla/dom/Errors.msg" +#undef MSG_DEF + Err_Limit +}; + +bool +ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...); + +template<bool mainThread> +inline bool +Throw(JSContext* cx, nsresult rv) +{ + using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; + + // XXX Introduce exception machinery. + if (mainThread) { + xpc::Throw(cx, rv); + } else { + if (!JS_IsExceptionPending(cx)) { + ThrowDOMExceptionForNSResult(cx, rv); + } + } + return false; +} + +template<bool mainThread> +inline bool +ThrowMethodFailedWithDetails(JSContext* cx, const ErrorResult& rv, + const char* /* ifaceName */, + const char* /* memberName */) +{ + return Throw<mainThread>(cx, rv.ErrorCode()); +} + +inline bool +IsDOMClass(const JSClass* clasp) +{ + return clasp->flags & JSCLASS_IS_DOMJSCLASS; +} + +inline bool +IsDOMClass(const js::Class* clasp) +{ + return IsDOMClass(Jsvalify(clasp)); +} + +// It's ok for eRegularDOMObject and eProxyDOMObject to be the same, but +// eNonDOMObject should always be different from the other two. This enum +// shouldn't be used to differentiate between non-proxy and proxy bindings. +enum DOMObjectSlot { + eNonDOMObject = -1, + eRegularDOMObject = DOM_OBJECT_SLOT, + eProxyDOMObject = DOM_PROXY_OBJECT_SLOT +}; + +template <class T> +inline T* +UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot) +{ + MOZ_ASSERT(slot != eNonDOMObject, + "Don't pass non-DOM objects to this function"); + +#ifdef DEBUG + if (IsDOMClass(js::GetObjectClass(obj))) { + MOZ_ASSERT(slot == eRegularDOMObject); + } else { + MOZ_ASSERT(js::IsObjectProxyClass(js::GetObjectClass(obj)) || + js::IsFunctionProxyClass(js::GetObjectClass(obj))); + MOZ_ASSERT(js::GetProxyHandler(obj)->family() == ProxyFamily()); + MOZ_ASSERT(IsNewProxyBinding(js::GetProxyHandler(obj))); + MOZ_ASSERT(slot == eProxyDOMObject); + } +#endif + + JS::Value val = js::GetReservedSlot(obj, slot); + // XXXbz/khuey worker code tries to unwrap interface objects (which have + // nothing here). That needs to stop. + // XXX We don't null-check UnwrapObject's result; aren't we going to crash + // anyway? + if (val.isUndefined()) { + return NULL; + } + + return static_cast<T*>(val.toPrivate()); +} + +// Only use this with a new DOM binding object (either proxy or regular). +inline const DOMClass* +GetDOMClass(JSObject* obj) +{ + js::Class* clasp = js::GetObjectClass(obj); + if (IsDOMClass(clasp)) { + return &DOMJSClass::FromJSClass(clasp)->mClass; + } + + js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + MOZ_ASSERT(handler->family() == ProxyFamily()); + MOZ_ASSERT(IsNewProxyBinding(handler)); + return &static_cast<DOMProxyHandler*>(handler)->mClass; +} + +inline DOMObjectSlot +GetDOMClass(JSObject* obj, const DOMClass*& result) +{ + js::Class* clasp = js::GetObjectClass(obj); + if (IsDOMClass(clasp)) { + result = &DOMJSClass::FromJSClass(clasp)->mClass; + return eRegularDOMObject; + } + + if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) { + js::BaseProxyHandler* handler = js::GetProxyHandler(obj); + if (handler->family() == ProxyFamily() && IsNewProxyBinding(handler)) { + result = &static_cast<DOMProxyHandler*>(handler)->mClass; + return eProxyDOMObject; + } + } + + return eNonDOMObject; +} + +inline bool +UnwrapDOMObjectToISupports(JSObject* obj, nsISupports*& result) +{ + const DOMClass* clasp; + DOMObjectSlot slot = GetDOMClass(obj, clasp); + if (slot == eNonDOMObject || !clasp->mDOMObjectIsISupports) { + return false; + } + + result = UnwrapDOMObject<nsISupports>(obj, slot); + return true; +} + +inline bool +IsDOMObject(JSObject* obj) +{ + js::Class* clasp = js::GetObjectClass(obj); + return IsDOMClass(clasp) || + ((js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) && + (js::GetProxyHandler(obj)->family() == ProxyFamily() && + IsNewProxyBinding(js::GetProxyHandler(obj)))); +} + +// Some callers don't want to set an exception when unwrapping fails +// (for example, overload resolution uses unwrapping to tell what sort +// of thing it's looking at). +// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>). +template <prototypes::ID PrototypeID, class T, typename U> +inline nsresult +UnwrapObject(JSContext* cx, JSObject* obj, U& value) +{ + /* First check to see whether we have a DOM object */ + const DOMClass* domClass; + DOMObjectSlot slot = GetDOMClass(obj, domClass); + if (slot == eNonDOMObject) { + /* Maybe we have a security wrapper or outer window? */ + if (!js::IsWrapper(obj)) { + /* Not a DOM object, not a wrapper, just bail */ + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + MOZ_ASSERT(!js::IsWrapper(obj)); + slot = GetDOMClass(obj, domClass); + if (slot == eNonDOMObject) { + /* We don't have a DOM object */ + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + } + + /* This object is a DOM object. Double-check that it is safely + castable to T by checking whether it claims to inherit from the + class identified by protoID. */ + if (domClass->mInterfaceChain[PrototypeTraits<PrototypeID>::Depth] == + PrototypeID) { + value = UnwrapDOMObject<T>(obj, slot); + return NS_OK; + } + + /* It's the wrong sort of DOM object */ + return NS_ERROR_XPC_BAD_CONVERT_JS; +} + +inline bool +IsArrayLike(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(obj); + // For simplicity, check for security wrappers up front. In case we + // have a security wrapper, don't forget to enter the compartment of + // the underlying object after unwrapping. + Maybe<JSAutoCompartment> ac; + if (js::IsWrapper(obj)) { + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + // Let's say it's not + return false; + } + + ac.construct(cx, obj); + } + + // XXXbz need to detect platform objects (including listbinding + // ones) with indexGetters here! + return JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj, cx); +} + +inline bool +IsPlatformObject(JSContext* cx, JSObject* obj) +{ + // XXXbz Should be treating list-binding objects as platform objects + // too? The one consumer so far wants non-array-like platform + // objects, so listbindings that have an indexGetter should test + // false from here. Maybe this function should have a different + // name? + MOZ_ASSERT(obj); + // Fast-path the common case + JSClass* clasp = js::GetObjectJSClass(obj); + if (IsDOMClass(clasp)) { + return true; + } + // Now for simplicity check for security wrappers before anything else + if (js::IsWrapper(obj)) { + obj = xpc::Unwrap(cx, obj, false); + if (!obj) { + // Let's say it's not + return false; + } + clasp = js::GetObjectJSClass(obj); + } + return IS_WRAPPER_CLASS(js::Valueify(clasp)) || IsDOMClass(clasp) || + JS_IsArrayBufferObject(obj, cx); +} + +// U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>). +template <class T, typename U> +inline nsresult +UnwrapObject(JSContext* cx, JSObject* obj, U& value) +{ + return UnwrapObject<static_cast<prototypes::ID>( + PrototypeIDMap<T>::PrototypeID), T>(cx, obj, value); +} + +const size_t kProtoOrIfaceCacheCount = + prototypes::id::_ID_Count + constructors::id::_ID_Count; + +inline void +AllocateProtoOrIfaceCache(JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined()); + + // Important: The () at the end ensure zero-initialization + JSObject** protoOrIfaceArray = new JSObject*[kProtoOrIfaceCacheCount](); + + js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, + JS::PrivateValue(protoOrIfaceArray)); +} + +inline void +TraceProtoOrIfaceCache(JSTracer* trc, JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + + if (!HasProtoOrIfaceArray(obj)) + return; + JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); + for (size_t i = 0; i < kProtoOrIfaceCacheCount; ++i) { + JSObject* proto = protoOrIfaceArray[i]; + if (proto) { + JS_CALL_OBJECT_TRACER(trc, proto, "protoOrIfaceArray[i]"); + } + } +} + +inline void +DestroyProtoOrIfaceCache(JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + + JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); + + delete [] protoOrIfaceArray; +} + +struct ConstantSpec +{ + const char* name; + JS::Value value; +}; + +/** + * Add constants to an object. + */ +bool +DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs); + +template<typename T> +struct Prefable { + // A boolean indicating whether this set of specs is enabled + bool enabled; + // Array of specs, terminated in whatever way is customary for T. + // Null to indicate a end-of-array for Prefable, when such an + // indicator is needed. + T* specs; +}; + +/* + * Create a DOM interface object (if constructorClass is non-null) and/or a + * DOM interface prototype object (if protoClass is non-null). + * + * global is used as the parent of the interface object and the interface + * prototype object + * receiver is the object on which we need to define the interface object as a + * property + * protoProto is the prototype to use for the interface prototype object. + * protoClass is the JSClass to use for the interface prototype object. + * This is null if we should not create an interface prototype + * object. + * constructorClass is the JSClass to use for the interface object. + * This is null if we should not create an interface object or + * if it should be a function object. + * constructor is the JSNative to use as a constructor. If this is non-null, it + * should be used as a JSNative to back the interface object, which + * should be a Function. If this is null, then we should create an + * object of constructorClass, unless that's also null, in which + * case we should not create an interface object at all. + * ctorNargs is the length of the constructor function; 0 if no constructor + * instanceClass is the JSClass of instance objects for this class. This can + * be null if this is not a concrete proto. + * methods and properties are to be defined on the interface prototype object; + * these arguments are allowed to be null if there are no + * methods or properties respectively. + * constants are to be defined on the interface object and on the interface + * prototype object; allowed to be null if there are no constants. + * staticMethods are to be defined on the interface object; allowed to be null + * if there are no static methods. + * + * At least one of protoClass and constructorClass should be non-null. + * If constructorClass is non-null, the resulting interface object will be + * defined on the given global with property name |name|, which must also be + * non-null. + * + * returns the interface prototype object if protoClass is non-null, else it + * returns the interface object. + */ +JSObject* +CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject* receiver, + JSObject* protoProto, JSClass* protoClass, + JSClass* constructorClass, JSNative constructor, + unsigned ctorNargs, const DOMClass* domClass, + Prefable<JSFunctionSpec>* methods, + Prefable<JSPropertySpec>* properties, + Prefable<ConstantSpec>* constants, + Prefable<JSFunctionSpec>* staticMethods, const char* name); + +template <class T> +inline bool +WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp) +{ + JSObject* obj = value->GetWrapper(); + if (obj && js::GetObjectCompartment(obj) == js::GetObjectCompartment(scope)) { + *vp = JS::ObjectValue(*obj); + return true; + } + + if (!obj) { + bool triedToWrap; + obj = value->WrapObject(cx, scope, &triedToWrap); + if (!obj) { + // At this point, obj is null, so just return false. We could + // try to communicate triedToWrap to the caller, but in practice + // callers seem to be testing JS_IsExceptionPending(cx) to + // figure out whether WrapObject() threw instead. + return false; + } + } + + // When called via XrayWrapper, we end up here while running in the + // chrome compartment. But the obj we have would be created in + // whatever the content compartment is. So at this point we need to + // make sure it's correctly wrapped for the compartment of |scope|. + // cx should already be in the compartment of |scope| here. + MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); + *vp = JS::ObjectValue(*obj); + return JS_WrapValue(cx, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, class T> +inline bool +WrapNewBindingObject(JSContext* cx, JSObject* scope, const SmartPtr<T>& value, + JS::Value* vp) +{ + return WrapNewBindingObject(cx, scope, value.get(), vp); +} + +template <class T> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, T* value, + JS::Value* vp) +{ + // We try to wrap in the compartment of the underlying object of "scope" + JSObject* obj; + { + // scope for the JSAutoCompartment so that we restore the compartment + // before we call JS_WrapValue. + Maybe<JSAutoCompartment> ac; + if (js::IsWrapper(scope)) { + scope = xpc::Unwrap(cx, scope, false); + if (!scope) + return false; + ac.construct(cx, scope); + } + + obj = value->WrapObject(cx, scope); + } + + // We can end up here in all sorts of compartments, per above. Make + // sure to JS_WrapValue! + *vp = JS::ObjectValue(*obj); + return JS_WrapValue(cx, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, typename T> +inline bool +WrapNewBindingNonWrapperCachedObject(JSContext* cx, JSObject* scope, + const SmartPtr<T>& value, JS::Value* vp) +{ + return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), vp); +} + +/** + * A method to handle new-binding wrap failure, by possibly falling back to + * wrapping as a non-new-binding object. + */ +bool +DoHandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, + nsISupports* value, JS::Value* vp); + +/** + * An easy way to call the above when you have a value which + * multiply-inherits from nsISupports. + */ +template <class T> +bool +HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, T* value, + JS::Value* vp) +{ + nsCOMPtr<nsISupports> val; + CallQueryInterface(value, getter_AddRefs(val)); + return DoHandleNewBindingWrappingFailure(cx, scope, val, vp); +} + +// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). +template <template <typename> class SmartPtr, class T> +MOZ_ALWAYS_INLINE bool +HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, + const SmartPtr<T>& value, JS::Value* vp) +{ + return HandleNewBindingWrappingFailure(cx, scope, value.get(), vp); +} + +struct EnumEntry { + const char* value; + size_t length; +}; + +template<bool Fatal> +inline bool +EnumValueNotFound(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + return false; +} + +template<> +inline bool +EnumValueNotFound<false>(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + // TODO: Log a warning to the console. + return true; +} + +template<> +inline bool +EnumValueNotFound<true>(JSContext* cx, const jschar* chars, size_t length, + const char* type) +{ + NS_LossyConvertUTF16toASCII deflated(static_cast<const PRUnichar*>(chars), + length); + return ThrowErrorMessage(cx, MSG_INVALID_ENUM_VALUE, deflated.get(), type); +} + + +template<bool InvalidValueFatal> +inline int +FindEnumStringIndex(JSContext* cx, JS::Value v, const EnumEntry* values, + const char* type, bool* ok) +{ + // JS_StringEqualsAscii is slow as molasses, so don't use it here. + JSString* str = JS_ValueToString(cx, v); + if (!str) { + *ok = false; + return 0; + } + JS::Anchor<JSString*> anchor(str); + size_t length; + const jschar* chars = JS_GetStringCharsAndLength(cx, str, &length); + if (!chars) { + *ok = false; + return 0; + } + int i = 0; + for (const EnumEntry* value = values; value->value; ++value, ++i) { + if (length != value->length) { + continue; + } + + bool equal = true; + const char* val = value->value; + for (size_t j = 0; j != length; ++j) { + if (unsigned(val[j]) != unsigned(chars[j])) { + equal = false; + break; + } + } + + if (equal) { + *ok = true; + return i; + } + } + + *ok = EnumValueNotFound<InvalidValueFatal>(cx, chars, length, type); + return -1; +} + +inline nsWrapperCache* +GetWrapperCache(nsWrapperCache* cache) +{ + return cache; +} + +inline nsWrapperCache* +GetWrapperCache(nsGlobalWindow* not_allowed); + +inline nsWrapperCache* +GetWrapperCache(void* p) +{ + return NULL; +} + +struct ParentObject { + template<class T> + ParentObject(T* aObject) : + mObject(aObject), + mWrapperCache(GetWrapperCache(aObject)) + {} + + template<class T, template<typename> class SmartPtr> + ParentObject(const SmartPtr<T>& aObject) : + mObject(aObject.get()), + mWrapperCache(GetWrapperCache(aObject.get())) + {} + + ParentObject(nsISupports* aObject, nsWrapperCache* aCache) : + mObject(aObject), + mWrapperCache(aCache) + {} + + nsISupports* const mObject; + nsWrapperCache* const mWrapperCache; +}; + +inline nsWrapperCache* +GetWrapperCache(const ParentObject& aParentObject) +{ + return aParentObject.mWrapperCache; +} + +template<class T> +inline nsISupports* +GetParentPointer(T* aObject) +{ + return ToSupports(aObject); +} + +inline nsISupports* +GetParentPointer(const ParentObject& aObject) +{ + return ToSupports(aObject.mObject); +} + +template<class T> +inline void +ClearWrapper(T* p, nsWrapperCache* cache) +{ + cache->ClearWrapper(); +} + +template<class T> +inline void +ClearWrapper(T* p, void*) +{ + nsWrapperCache* cache; + CallQueryInterface(p, &cache); + ClearWrapper(p, cache); +} + +// Can only be called with the immediate prototype of the instance object. Can +// only be called on the prototype of an object known to be a DOM instance. +JSBool +InstanceClassHasProtoAtDepth(JSHandleObject protoObject, uint32_t protoID, + uint32_t depth); + +// Only set allowNativeWrapper to false if you really know you need it, if in +// doubt use true. Setting it to false disables security wrappers. +bool +XPCOMObjectToJsval(JSContext* cx, JSObject* scope, xpcObjectHelper &helper, + const nsIID* iid, bool allowNativeWrapper, JS::Value* rval); + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, nsWrapperCache* cache, + const nsIID* iid, JS::Value* vp) +{ + if (xpc_FastGetCachedWrapper(cache, scope, vp)) + return true; + qsObjectHelper helper(p, cache); + return XPCOMObjectToJsval(cx, scope, helper, iid, true, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p, GetWrapperCache(p), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, T* p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p.get(), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsCOMPtr<T> &p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, const nsIID* iid, + JS::Value* vp) +{ + return WrapObject(cx, scope, p.get(), iid, vp); +} + +template<class T> +inline bool +WrapObject(JSContext* cx, JSObject* scope, nsRefPtr<T> &p, JS::Value* vp) +{ + return WrapObject(cx, scope, p, NULL, vp); +} + +template<> +inline bool +WrapObject<JSObject>(JSContext* cx, JSObject* scope, JSObject* p, JS::Value* vp) +{ + vp->setObjectOrNull(p); + return true; +} + +template<typename T> +static inline JSObject* +WrapNativeParent(JSContext* cx, JSObject* scope, const T& p) +{ + if (!GetParentPointer(p)) + return scope; + + nsWrapperCache* cache = GetWrapperCache(p); + JSObject* obj; + if (cache && (obj = cache->GetWrapper())) { +#ifdef DEBUG + qsObjectHelper helper(GetParentPointer(p), cache); + JS::Value debugVal; + + bool ok = XPCOMObjectToJsval(cx, scope, helper, NULL, false, &debugVal); + NS_ASSERTION(ok && JSVAL_TO_OBJECT(debugVal) == obj, + "Unexpected object in nsWrapperCache"); +#endif + return obj; + } + + qsObjectHelper helper(GetParentPointer(p), cache); + JS::Value v; + return XPCOMObjectToJsval(cx, scope, helper, NULL, false, &v) ? + JSVAL_TO_OBJECT(v) : + NULL; +} + +static inline bool +InternJSString(JSContext* cx, jsid& id, const char* chars) +{ + if (JSString *str = ::JS_InternString(cx, chars)) { + id = INTERNED_STRING_TO_JSID(cx, str); + return true; + } + return false; +} + +// Spec needs a name property +template <typename Spec> +static bool +InitIds(JSContext* cx, Prefable<Spec>* prefableSpecs, jsid* ids) +{ + MOZ_ASSERT(prefableSpecs); + MOZ_ASSERT(prefableSpecs->specs); + do { + // We ignore whether the set of ids is enabled and just intern all the IDs, + // because this is only done once per application runtime. + Spec* spec = prefableSpecs->specs; + do { + if (!InternJSString(cx, *ids, spec->name)) { + return false; + } + } while (++ids, (++spec)->name); + + // We ran out of ids for that pref. Put a JSID_VOID in on the id + // corresponding to the list terminator for the pref. + *ids = JSID_VOID; + ++ids; + } while ((++prefableSpecs)->specs); + + return true; +} + +JSBool +QueryInterface(JSContext* cx, unsigned argc, JS::Value* vp); +JSBool +ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +GetPropertyOnPrototype(JSContext* cx, JSObject* proxy, jsid id, bool* found, + JS::Value* vp); + +bool +HasPropertyOnPrototype(JSContext* cx, JSObject* proxy, DOMProxyHandler* handler, + jsid id); + +template<class T> +class NonNull +{ +public: + NonNull() +#ifdef DEBUG + : inited(false) +#endif + {} + + operator T&() { + MOZ_ASSERT(inited); + MOZ_ASSERT(ptr, "NonNull<T> was set to null"); + return *ptr; + } + + operator const T&() const { + MOZ_ASSERT(inited); + MOZ_ASSERT(ptr, "NonNull<T> was set to null"); + return *ptr; + } + + void operator=(T* t) { + ptr = t; + MOZ_ASSERT(ptr); +#ifdef DEBUG + inited = true; +#endif + } + + template<typename U> + void operator=(U* t) { + ptr = t->ToAStringPtr(); + MOZ_ASSERT(ptr); +#ifdef DEBUG + inited = true; +#endif + } + + T** Slot() { +#ifdef DEBUG + inited = true; +#endif + return &ptr; + } + +protected: + T* ptr; +#ifdef DEBUG + bool inited; +#endif +}; + +template<class T> +class OwningNonNull +{ +public: + OwningNonNull() +#ifdef DEBUG + : inited(false) +#endif + {} + + operator T&() { + MOZ_ASSERT(inited); + MOZ_ASSERT(ptr, "OwningNonNull<T> was set to null"); + return *ptr; + } + + void operator=(T* t) { + init(t); + } + + void operator=(const already_AddRefed<T>& t) { + init(t); + } + +protected: + template<typename U> + void init(U t) { + ptr = t; + MOZ_ASSERT(ptr); +#ifdef DEBUG + inited = true; +#endif + } + + nsRefPtr<T> ptr; +#ifdef DEBUG + bool inited; +#endif +}; + +// A struct that has the same layout as an nsDependentString but much +// faster constructor and destructor behavior +struct FakeDependentString { + FakeDependentString() : + mFlags(nsDependentString::F_TERMINATED) + { + } + + void SetData(const nsDependentString::char_type* aData, + nsDependentString::size_type aLength) { + MOZ_ASSERT(mFlags == nsDependentString::F_TERMINATED); + mData = aData; + mLength = aLength; + } + + void Truncate() { + mData = nsDependentString::char_traits::sEmptyBuffer; + mLength = 0; + } + + void SetNull() { + Truncate(); + mFlags |= nsDependentString::F_VOIDED; + } + + const nsAString* ToAStringPtr() const { + return reinterpret_cast<const nsDependentString*>(this); + } + + nsAString* ToAStringPtr() { + return reinterpret_cast<nsDependentString*>(this); + } + + operator const nsAString& () const { + return *reinterpret_cast<const nsDependentString*>(this); + } + +private: + const nsDependentString::char_type* mData; + nsDependentString::size_type mLength; + uint32_t mFlags; + + // A class to use for our static asserts to ensure our object layout + // matches that of nsDependentString. + class DependentStringAsserter; + friend class DependentStringAsserter; + + class DepedentStringAsserter : public nsDependentString { + public: + static void StaticAsserts() { + MOZ_STATIC_ASSERT(sizeof(FakeDependentString) == sizeof(nsDependentString), + "Must have right object size"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mData) == + offsetof(DepedentStringAsserter, mData), + "Offset of mData should match"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mLength) == + offsetof(DepedentStringAsserter, mLength), + "Offset of mLength should match"); + MOZ_STATIC_ASSERT(offsetof(FakeDependentString, mFlags) == + offsetof(DepedentStringAsserter, mFlags), + "Offset of mFlags should match"); + } + }; +}; + +enum StringificationBehavior { + eStringify, + eEmpty, + eNull +}; + +// pval must not be null and must point to a rooted JS::Value +static inline bool +ConvertJSValueToString(JSContext* cx, const JS::Value& v, JS::Value* pval, + StringificationBehavior nullBehavior, + StringificationBehavior undefinedBehavior, + FakeDependentString& result) +{ + JSString *s; + if (v.isString()) { + s = v.toString(); + } else { + StringificationBehavior behavior; + if (v.isNull()) { + behavior = nullBehavior; + } else if (v.isUndefined()) { + behavior = undefinedBehavior; + } else { + behavior = eStringify; + } + + if (behavior != eStringify) { + if (behavior == eEmpty) { + result.Truncate(); + } else { + result.SetNull(); + } + return true; + } + + s = JS_ValueToString(cx, v); + if (!s) { + return false; + } + pval->setString(s); // Root the new string. + } + + size_t len; + const jschar *chars = JS_GetStringCharsZAndLength(cx, s, &len); + if (!chars) { + return false; + } + + result.SetData(chars, len); + return true; +} + +// Class for representing optional arguments. +template<typename T> +class Optional { +public: + Optional() {} + + bool WasPassed() const { + return !mImpl.empty(); + } + + void Construct() { + mImpl.construct(); + } + + template <class T1, class T2> + void Construct(const T1 &t1, const T2 &t2) { + mImpl.construct(t1, t2); + } + + const T& Value() const { + return mImpl.ref(); + } + + T& Value() { + return mImpl.ref(); + } + +private: + // Forbid copy-construction and assignment + Optional(const Optional& other) MOZ_DELETE; + const Optional &operator=(const Optional &other) MOZ_DELETE; + + Maybe<T> mImpl; +}; + +// Specialization for strings. +template<> +class Optional<nsAString> { +public: + Optional() : mPassed(false) {} + + bool WasPassed() const { + return mPassed; + } + + void operator=(const nsAString* str) { + MOZ_ASSERT(str); + mStr = str; + mPassed = true; + } + + void operator=(const FakeDependentString* str) { + MOZ_ASSERT(str); + mStr = str->ToAStringPtr(); + mPassed = true; + } + + const nsAString& Value() const { + MOZ_ASSERT(WasPassed()); + return *mStr; + } + +private: + // Forbid copy-construction and assignment + Optional(const Optional& other) MOZ_DELETE; + const Optional &operator=(const Optional &other) MOZ_DELETE; + + bool mPassed; + const nsAString* mStr; +}; + +// Class for representing sequences in arguments. We use an auto array that can +// hold 16 elements, to avoid having to allocate in common cases. This needs to +// be fallible because web content controls the length of the array, and can +// easily try to create very large lengths. +template<typename T> +class Sequence : public AutoFallibleTArray<T, 16> +{ +public: + Sequence() : AutoFallibleTArray<T, 16>() {} +}; + +// Class for holding the type of members of a union. The union type has an enum +// to keep track of which of its UnionMembers has been constructed. +template<class T> +class UnionMember { + AlignedStorage2<T> storage; + +public: + T& SetValue() { + new (storage.addr()) T(); + return *storage.addr(); + } + const T& Value() const { + return *storage.addr(); + } + void Destroy() { + storage.addr()->~T(); + } +}; + +// Implementation of the bits that XrayWrapper needs +bool +XrayResolveProperty(JSContext* cx, JSObject* wrapper, jsid id, + JSPropertyDescriptor* desc, + // And the things we need to determine the descriptor + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount); + +bool +XrayEnumerateProperties(JS::AutoIdVector& props, + Prefable<JSFunctionSpec>* methods, + jsid* methodIds, + JSFunctionSpec* methodSpecs, + size_t methodCount, + Prefable<JSPropertySpec>* attributes, + jsid* attributeIds, + JSPropertySpec* attributeSpecs, + size_t attributeCount, + Prefable<ConstantSpec>* constants, + jsid* constantIds, + ConstantSpec* constantSpecs, + size_t constantCount); + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_BindingUtils_h__ */ |