michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* vim: set ts=2 sw=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef mozilla_dom_BindingUtils_h__ michael@0: #define mozilla_dom_BindingUtils_h__ michael@0: michael@0: #include "jsfriendapi.h" michael@0: #include "jswrapper.h" michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Alignment.h" michael@0: #include "mozilla/Array.h" michael@0: #include "mozilla/dom/BindingDeclarations.h" michael@0: #include "mozilla/dom/CallbackObject.h" michael@0: #include "mozilla/dom/DOMJSClass.h" michael@0: #include "mozilla/dom/DOMJSProxyHandler.h" michael@0: #include "mozilla/dom/Exceptions.h" michael@0: #include "mozilla/dom/NonRefcountedDOMObject.h" michael@0: #include "mozilla/dom/Nullable.h" michael@0: #include "mozilla/dom/RootedDictionary.h" michael@0: #include "mozilla/dom/workers/Workers.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "nsCycleCollector.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "MainThreadUtils.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "qsObjectHelper.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsIVariant.h" michael@0: #include "pldhash.h" // For PLDHashOperator michael@0: michael@0: #include "nsWrapperCacheInlines.h" michael@0: michael@0: class nsIJSID; michael@0: class nsPIDOMWindow; michael@0: michael@0: extern nsresult michael@0: xpc_qsUnwrapArgImpl(JSContext* cx, JS::Handle v, const nsIID& iid, void** ppArg, michael@0: nsISupports** ppArgRef, JS::MutableHandle vp); michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: template class MozMap; michael@0: michael@0: struct SelfRef michael@0: { michael@0: SelfRef() : ptr(nullptr) {} michael@0: explicit SelfRef(nsISupports *p) : ptr(p) {} michael@0: ~SelfRef() { NS_IF_RELEASE(ptr); } michael@0: michael@0: nsISupports* ptr; michael@0: }; michael@0: michael@0: /** Convert a jsval to an XPCOM pointer. */ michael@0: template michael@0: inline nsresult michael@0: UnwrapArg(JSContext* cx, JS::Handle v, Interface** ppArg, michael@0: StrongRefType** ppArgRef, JS::MutableHandle vp) michael@0: { michael@0: nsISupports* argRef = *ppArgRef; michael@0: nsresult rv = xpc_qsUnwrapArgImpl(cx, v, NS_GET_TEMPLATE_IID(Interface), michael@0: reinterpret_cast(ppArg), &argRef, michael@0: vp); michael@0: *ppArgRef = static_cast(argRef); michael@0: return rv; michael@0: } michael@0: michael@0: inline const ErrNum michael@0: GetInvalidThisErrorForMethod(bool aSecurityError) michael@0: { michael@0: return aSecurityError ? MSG_METHOD_THIS_UNWRAPPING_DENIED : michael@0: MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE; michael@0: } michael@0: michael@0: inline const ErrNum michael@0: GetInvalidThisErrorForGetter(bool aSecurityError) michael@0: { michael@0: return aSecurityError ? MSG_GETTER_THIS_UNWRAPPING_DENIED : michael@0: MSG_GETTER_THIS_DOES_NOT_IMPLEMENT_INTERFACE; michael@0: } michael@0: michael@0: inline const ErrNum michael@0: GetInvalidThisErrorForSetter(bool aSecurityError) michael@0: { michael@0: return aSecurityError ? MSG_SETTER_THIS_UNWRAPPING_DENIED : michael@0: MSG_SETTER_THIS_DOES_NOT_IMPLEMENT_INTERFACE; michael@0: } michael@0: michael@0: bool michael@0: ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs, michael@0: const ErrNum aErrorNumber, michael@0: const char* aInterfaceName); michael@0: michael@0: bool michael@0: ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs, michael@0: const ErrNum aErrorNumber, michael@0: prototypes::ID aProtoId); michael@0: michael@0: inline bool michael@0: ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv, michael@0: const char* ifaceName, michael@0: const char* memberName, michael@0: bool reportJSContentExceptions = false) michael@0: { michael@0: if (rv.IsTypeError()) { michael@0: rv.ReportTypeError(cx); michael@0: return false; michael@0: } michael@0: if (rv.IsJSException()) { michael@0: if (reportJSContentExceptions) { michael@0: rv.ReportJSExceptionFromJSImplementation(cx); michael@0: } else { michael@0: rv.ReportJSException(cx); michael@0: } michael@0: return false; michael@0: } michael@0: if (rv.IsNotEnoughArgsError()) { michael@0: rv.ReportNotEnoughArgsError(cx, ifaceName, memberName); michael@0: return false; michael@0: } michael@0: return Throw(cx, rv.ErrorCode()); michael@0: } michael@0: michael@0: // Returns true if the JSClass is used for DOM objects. michael@0: inline bool michael@0: IsDOMClass(const JSClass* clasp) michael@0: { michael@0: return clasp->flags & JSCLASS_IS_DOMJSCLASS; michael@0: } michael@0: michael@0: inline bool michael@0: IsDOMClass(const js::Class* clasp) michael@0: { michael@0: return IsDOMClass(Jsvalify(clasp)); michael@0: } michael@0: michael@0: // Return true if the JSClass is used for non-proxy DOM objects. michael@0: inline bool michael@0: IsNonProxyDOMClass(const js::Class* clasp) michael@0: { michael@0: return IsDOMClass(clasp) && !clasp->isProxy(); michael@0: } michael@0: michael@0: inline bool michael@0: IsNonProxyDOMClass(const JSClass* clasp) michael@0: { michael@0: return IsNonProxyDOMClass(js::Valueify(clasp)); michael@0: } michael@0: michael@0: // Returns true if the JSClass is used for DOM interface and interface michael@0: // prototype objects. michael@0: inline bool michael@0: IsDOMIfaceAndProtoClass(const JSClass* clasp) michael@0: { michael@0: return clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS; michael@0: } michael@0: michael@0: inline bool michael@0: IsDOMIfaceAndProtoClass(const js::Class* clasp) michael@0: { michael@0: return IsDOMIfaceAndProtoClass(Jsvalify(clasp)); michael@0: } michael@0: michael@0: static_assert(DOM_OBJECT_SLOT == js::PROXY_PRIVATE_SLOT, michael@0: "js::PROXY_PRIVATE_SLOT doesn't match DOM_OBJECT_SLOT. " michael@0: "Expect bad things"); michael@0: template michael@0: inline T* michael@0: UnwrapDOMObject(JSObject* obj) michael@0: { michael@0: MOZ_ASSERT(IsDOMClass(js::GetObjectClass(obj)), michael@0: "Don't pass non-DOM objects to this function"); michael@0: michael@0: JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); michael@0: return static_cast(val.toPrivate()); michael@0: } michael@0: michael@0: inline const DOMClass* michael@0: GetDOMClass(JSObject* obj) michael@0: { michael@0: const js::Class* clasp = js::GetObjectClass(obj); michael@0: if (IsDOMClass(clasp)) { michael@0: return &DOMJSClass::FromJSClass(clasp)->mClass; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: inline nsISupports* michael@0: UnwrapDOMObjectToISupports(JSObject* aObject) michael@0: { michael@0: const DOMClass* clasp = GetDOMClass(aObject); michael@0: if (!clasp || !clasp->mDOMObjectIsISupports) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return UnwrapDOMObject(aObject); michael@0: } michael@0: michael@0: inline bool michael@0: IsDOMObject(JSObject* obj) michael@0: { michael@0: return IsDOMClass(js::GetObjectClass(obj)); michael@0: } michael@0: michael@0: #define UNWRAP_OBJECT(Interface, obj, value) \ michael@0: mozilla::dom::UnwrapObject(obj, value) michael@0: michael@0: // Some callers don't want to set an exception when unwrapping fails michael@0: // (for example, overload resolution uses unwrapping to tell what sort michael@0: // of thing it's looking at). michael@0: // U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr). michael@0: template michael@0: MOZ_ALWAYS_INLINE nsresult michael@0: UnwrapObject(JSObject* obj, U& value, prototypes::ID protoID, michael@0: uint32_t protoDepth) michael@0: { michael@0: /* First check to see whether we have a DOM object */ michael@0: const DOMClass* domClass = GetDOMClass(obj); michael@0: if (!domClass) { michael@0: /* Maybe we have a security wrapper or outer window? */ michael@0: if (!js::IsWrapper(obj)) { michael@0: /* Not a DOM object, not a wrapper, just bail */ michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: michael@0: obj = js::CheckedUnwrap(obj, /* stopAtOuter = */ false); michael@0: if (!obj) { michael@0: return NS_ERROR_XPC_SECURITY_MANAGER_VETO; michael@0: } michael@0: MOZ_ASSERT(!js::IsWrapper(obj)); michael@0: domClass = GetDOMClass(obj); michael@0: if (!domClass) { michael@0: /* We don't have a DOM object */ michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: } michael@0: michael@0: /* This object is a DOM object. Double-check that it is safely michael@0: castable to T by checking whether it claims to inherit from the michael@0: class identified by protoID. */ michael@0: if (domClass->mInterfaceChain[protoDepth] == protoID) { michael@0: value = UnwrapDOMObject(obj); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* It's the wrong sort of DOM object */ michael@0: return NS_ERROR_XPC_BAD_CONVERT_JS; michael@0: } michael@0: michael@0: template michael@0: MOZ_ALWAYS_INLINE nsresult michael@0: UnwrapObject(JSObject* obj, U& value) michael@0: { michael@0: return UnwrapObject(obj, value, PrototypeID, michael@0: PrototypeTraits::Depth); michael@0: } michael@0: michael@0: inline bool michael@0: IsNotDateOrRegExp(JSContext* cx, JS::Handle obj) michael@0: { michael@0: MOZ_ASSERT(obj); michael@0: return !JS_ObjectIsDate(cx, obj) && !JS_ObjectIsRegExp(cx, obj); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsObjectValueConvertibleToDictionary(JSContext* cx, michael@0: JS::Handle objVal) michael@0: { michael@0: JS::Rooted obj(cx, &objVal.toObject()); michael@0: return IsNotDateOrRegExp(cx, obj); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsConvertibleToDictionary(JSContext* cx, JS::Handle val) michael@0: { michael@0: return val.isNullOrUndefined() || michael@0: (val.isObject() && IsObjectValueConvertibleToDictionary(cx, val)); michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: IsConvertibleToCallbackInterface(JSContext* cx, JS::Handle obj) michael@0: { michael@0: return IsNotDateOrRegExp(cx, obj); michael@0: } michael@0: michael@0: // The items in the protoAndIfaceCache are indexed by the prototypes::id::ID and michael@0: // constructors::id::ID enums, in that order. The end of the prototype objects michael@0: // should be the start of the interface objects. michael@0: static_assert((size_t)constructors::id::_ID_Start == michael@0: (size_t)prototypes::id::_ID_Count, michael@0: "Overlapping or discontiguous indexes."); michael@0: const size_t kProtoAndIfaceCacheCount = constructors::id::_ID_Count; michael@0: michael@0: class ProtoAndIfaceCache michael@0: { michael@0: // The caching strategy we use depends on what sort of global we're dealing michael@0: // with. For a window-like global, we want everything to be as fast as michael@0: // possible, so we use a flat array, indexed by prototype/constructor ID. michael@0: // For everything else (e.g. globals for JSMs), space is more important than michael@0: // speed, so we use a two-level lookup table. michael@0: michael@0: class ArrayCache : public Array, kProtoAndIfaceCacheCount> michael@0: { michael@0: public: michael@0: JSObject* EntrySlotIfExists(size_t i) { michael@0: return (*this)[i]; michael@0: } michael@0: michael@0: JS::Heap& EntrySlotOrCreate(size_t i) { michael@0: return (*this)[i]; michael@0: } michael@0: michael@0: JS::Heap& EntrySlotMustExist(size_t i) { michael@0: MOZ_ASSERT((*this)[i]); michael@0: return (*this)[i]; michael@0: } michael@0: michael@0: void Trace(JSTracer* aTracer) { michael@0: for (size_t i = 0; i < ArrayLength(*this); ++i) { michael@0: if ((*this)[i]) { michael@0: JS_CallHeapObjectTracer(aTracer, &(*this)[i], "protoAndIfaceCache[i]"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { michael@0: return aMallocSizeOf(this); michael@0: } michael@0: }; michael@0: michael@0: class PageTableCache michael@0: { michael@0: public: michael@0: PageTableCache() { michael@0: memset(&mPages, 0, sizeof(mPages)); michael@0: } michael@0: michael@0: ~PageTableCache() { michael@0: for (size_t i = 0; i < ArrayLength(mPages); ++i) { michael@0: delete mPages[i]; michael@0: } michael@0: } michael@0: michael@0: JSObject* EntrySlotIfExists(size_t i) { michael@0: MOZ_ASSERT(i < kProtoAndIfaceCacheCount); michael@0: size_t pageIndex = i / kPageSize; michael@0: size_t leafIndex = i % kPageSize; michael@0: Page* p = mPages[pageIndex]; michael@0: if (!p) { michael@0: return nullptr; michael@0: } michael@0: return (*p)[leafIndex]; michael@0: } michael@0: michael@0: JS::Heap& EntrySlotOrCreate(size_t i) { michael@0: MOZ_ASSERT(i < kProtoAndIfaceCacheCount); michael@0: size_t pageIndex = i / kPageSize; michael@0: size_t leafIndex = i % kPageSize; michael@0: Page* p = mPages[pageIndex]; michael@0: if (!p) { michael@0: p = new Page; michael@0: mPages[pageIndex] = p; michael@0: } michael@0: return (*p)[leafIndex]; michael@0: } michael@0: michael@0: JS::Heap& EntrySlotMustExist(size_t i) { michael@0: MOZ_ASSERT(i < kProtoAndIfaceCacheCount); michael@0: size_t pageIndex = i / kPageSize; michael@0: size_t leafIndex = i % kPageSize; michael@0: Page* p = mPages[pageIndex]; michael@0: MOZ_ASSERT(p); michael@0: return (*p)[leafIndex]; michael@0: } michael@0: michael@0: void Trace(JSTracer* trc) { michael@0: for (size_t i = 0; i < ArrayLength(mPages); ++i) { michael@0: Page* p = mPages[i]; michael@0: if (p) { michael@0: for (size_t j = 0; j < ArrayLength(*p); ++j) { michael@0: if ((*p)[j]) { michael@0: JS_CallHeapObjectTracer(trc, &(*p)[j], "protoAndIfaceCache[i]"); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { michael@0: size_t n = aMallocSizeOf(this); michael@0: for (size_t i = 0; i < ArrayLength(mPages); ++i) { michael@0: n += aMallocSizeOf(mPages[i]); michael@0: } michael@0: return n; michael@0: } michael@0: michael@0: private: michael@0: static const size_t kPageSize = 16; michael@0: typedef Array, kPageSize> Page; michael@0: static const size_t kNPages = kProtoAndIfaceCacheCount / kPageSize + michael@0: size_t(bool(kProtoAndIfaceCacheCount % kPageSize)); michael@0: Array mPages; michael@0: }; michael@0: michael@0: public: michael@0: enum Kind { michael@0: WindowLike, michael@0: NonWindowLike michael@0: }; michael@0: michael@0: ProtoAndIfaceCache(Kind aKind) : mKind(aKind) { michael@0: MOZ_COUNT_CTOR(ProtoAndIfaceCache); michael@0: if (aKind == WindowLike) { michael@0: mArrayCache = new ArrayCache(); michael@0: } else { michael@0: mPageTableCache = new PageTableCache(); michael@0: } michael@0: } michael@0: michael@0: ~ProtoAndIfaceCache() { michael@0: if (mKind == WindowLike) { michael@0: delete mArrayCache; michael@0: } else { michael@0: delete mPageTableCache; michael@0: } michael@0: MOZ_COUNT_DTOR(ProtoAndIfaceCache); michael@0: } michael@0: michael@0: #define FORWARD_OPERATION(opName, args) \ michael@0: do { \ michael@0: if (mKind == WindowLike) { \ michael@0: return mArrayCache->opName args; \ michael@0: } else { \ michael@0: return mPageTableCache->opName args; \ michael@0: } \ michael@0: } while(0) michael@0: michael@0: JSObject* EntrySlotIfExists(size_t i) { michael@0: FORWARD_OPERATION(EntrySlotIfExists, (i)); michael@0: } michael@0: michael@0: JS::Heap& EntrySlotOrCreate(size_t i) { michael@0: FORWARD_OPERATION(EntrySlotOrCreate, (i)); michael@0: } michael@0: michael@0: JS::Heap& EntrySlotMustExist(size_t i) { michael@0: FORWARD_OPERATION(EntrySlotMustExist, (i)); michael@0: } michael@0: michael@0: void Trace(JSTracer *aTracer) { michael@0: FORWARD_OPERATION(Trace, (aTracer)); michael@0: } michael@0: michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { michael@0: size_t n = aMallocSizeOf(this); michael@0: n += (mKind == WindowLike michael@0: ? mArrayCache->SizeOfIncludingThis(aMallocSizeOf) michael@0: : mPageTableCache->SizeOfIncludingThis(aMallocSizeOf)); michael@0: return n; michael@0: } michael@0: #undef FORWARD_OPERATION michael@0: michael@0: private: michael@0: union { michael@0: ArrayCache *mArrayCache; michael@0: PageTableCache *mPageTableCache; michael@0: }; michael@0: Kind mKind; michael@0: }; michael@0: michael@0: inline void michael@0: AllocateProtoAndIfaceCache(JSObject* obj, ProtoAndIfaceCache::Kind aKind) michael@0: { michael@0: MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); michael@0: MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined()); michael@0: michael@0: ProtoAndIfaceCache* protoAndIfaceCache = new ProtoAndIfaceCache(aKind); michael@0: michael@0: js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, michael@0: JS::PrivateValue(protoAndIfaceCache)); michael@0: } michael@0: michael@0: inline void michael@0: TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) michael@0: { michael@0: MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); michael@0: michael@0: if (!HasProtoAndIfaceCache(obj)) michael@0: return; michael@0: ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj); michael@0: protoAndIfaceCache->Trace(trc); michael@0: } michael@0: michael@0: inline void michael@0: DestroyProtoAndIfaceCache(JSObject* obj) michael@0: { michael@0: MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); michael@0: michael@0: ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj); michael@0: michael@0: delete protoAndIfaceCache; michael@0: } michael@0: michael@0: /** michael@0: * Add constants to an object. michael@0: */ michael@0: bool michael@0: DefineConstants(JSContext* cx, JS::Handle obj, michael@0: const ConstantSpec* cs); michael@0: michael@0: struct JSNativeHolder michael@0: { michael@0: JSNative mNative; michael@0: const NativePropertyHooks* mPropertyHooks; michael@0: }; michael@0: michael@0: struct NamedConstructor michael@0: { michael@0: const char* mName; michael@0: const JSNativeHolder mHolder; michael@0: unsigned mNargs; michael@0: }; michael@0: michael@0: /* michael@0: * Create a DOM interface object (if constructorClass is non-null) and/or a michael@0: * DOM interface prototype object (if protoClass is non-null). michael@0: * michael@0: * global is used as the parent of the interface object and the interface michael@0: * prototype object michael@0: * protoProto is the prototype to use for the interface prototype object. michael@0: * interfaceProto is the prototype to use for the interface object. michael@0: * protoClass is the JSClass to use for the interface prototype object. michael@0: * This is null if we should not create an interface prototype michael@0: * object. michael@0: * protoCache a pointer to a JSObject pointer where we should cache the michael@0: * interface prototype object. This must be null if protoClass is and michael@0: * vice versa. michael@0: * constructorClass is the JSClass to use for the interface object. michael@0: * This is null if we should not create an interface object or michael@0: * if it should be a function object. michael@0: * constructor holds the JSNative to back the interface object which should be a michael@0: * Function, unless constructorClass is non-null in which case it is michael@0: * ignored. If this is null and constructorClass is also null then michael@0: * we should not create an interface object at all. michael@0: * ctorNargs is the length of the constructor function; 0 if no constructor michael@0: * constructorCache a pointer to a JSObject pointer where we should cache the michael@0: * interface object. This must be null if both constructorClass michael@0: * and constructor are null, and non-null otherwise. michael@0: * domClass is the DOMClass of instance objects for this class. This can be michael@0: * null if this is not a concrete proto. michael@0: * properties contains the methods, attributes and constants to be defined on michael@0: * objects in any compartment. michael@0: * chromeProperties contains the methods, attributes and constants to be defined michael@0: * on objects in chrome compartments. This must be null if the michael@0: * interface doesn't have any ChromeOnly properties or if the michael@0: * object is being created in non-chrome compartment. michael@0: * defineOnGlobal controls whether properties should be defined on the given michael@0: * global for the interface object (if any) and named michael@0: * constructors (if any) for this interface. This can be michael@0: * false in situations where we want the properties to only michael@0: * appear on privileged Xrays but not on the unprivileged michael@0: * underlying global. michael@0: * michael@0: * At least one of protoClass, constructorClass or constructor should be michael@0: * non-null. If constructorClass or constructor are non-null, the resulting michael@0: * interface object will be defined on the given global with property name michael@0: * |name|, which must also be non-null. michael@0: */ michael@0: void michael@0: CreateInterfaceObjects(JSContext* cx, JS::Handle global, michael@0: JS::Handle protoProto, michael@0: const JSClass* protoClass, JS::Heap* protoCache, michael@0: JS::Handle interfaceProto, michael@0: const JSClass* constructorClass, const JSNativeHolder* constructor, michael@0: unsigned ctorNargs, const NamedConstructor* namedConstructors, michael@0: JS::Heap* constructorCache, const DOMClass* domClass, michael@0: const NativeProperties* regularProperties, michael@0: const NativeProperties* chromeOnlyProperties, michael@0: const char* name, bool defineOnGlobal); michael@0: michael@0: /* michael@0: * Define the unforgeable attributes on an object. michael@0: */ michael@0: bool michael@0: DefineUnforgeableAttributes(JSContext* cx, JS::Handle obj, michael@0: const Prefable* props); michael@0: michael@0: bool michael@0: DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx, michael@0: JS::Handle obj, michael@0: const NativeProperties* properties, michael@0: bool defineUnforgeableAttributes); michael@0: michael@0: #ifdef _MSC_VER michael@0: #define HAS_MEMBER_CHECK(_name) \ michael@0: template static yes& Check(char (*)[(&V::_name == 0) + 1]) michael@0: #else michael@0: #define HAS_MEMBER_CHECK(_name) \ michael@0: template static yes& Check(char (*)[sizeof(&V::_name) + 1]) michael@0: #endif michael@0: michael@0: #define HAS_MEMBER(_name) \ michael@0: template \ michael@0: class Has##_name##Member { \ michael@0: typedef char yes[1]; \ michael@0: typedef char no[2]; \ michael@0: HAS_MEMBER_CHECK(_name); \ michael@0: template static no& Check(...); \ michael@0: \ michael@0: public: \ michael@0: static bool const Value = sizeof(Check(nullptr)) == sizeof(yes); \ michael@0: }; michael@0: michael@0: HAS_MEMBER(WrapObject) michael@0: michael@0: // HasWrapObject::Value will be true if T has a WrapObject member but it's michael@0: // not nsWrapperCache::WrapObject. michael@0: template michael@0: struct HasWrapObject michael@0: { michael@0: private: michael@0: typedef char yes[1]; michael@0: typedef char no[2]; michael@0: typedef JSObject* (nsWrapperCache::*WrapObject)(JSContext*, michael@0: JS::Handle); michael@0: template struct SFINAE; michael@0: template static no& Check(SFINAE*); michael@0: template static yes& Check(...); michael@0: michael@0: public: michael@0: static bool const Value = HasWrapObjectMember::Value && michael@0: sizeof(Check(nullptr)) == sizeof(yes); michael@0: }; michael@0: michael@0: #ifdef DEBUG michael@0: template ::value> michael@0: struct michael@0: CheckWrapperCacheCast michael@0: { michael@0: static bool Check() michael@0: { michael@0: return reinterpret_cast( michael@0: static_cast( michael@0: reinterpret_cast(1))) == 1; michael@0: } michael@0: }; michael@0: template michael@0: struct michael@0: CheckWrapperCacheCast michael@0: { michael@0: static bool Check() michael@0: { michael@0: return true; michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: CouldBeDOMBinding(void*) michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: MOZ_ALWAYS_INLINE bool michael@0: CouldBeDOMBinding(nsWrapperCache* aCache) michael@0: { michael@0: return aCache->IsDOMBinding(); michael@0: } michael@0: michael@0: inline bool michael@0: TryToOuterize(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: if (js::IsInnerObject(&rval.toObject())) { michael@0: JS::Rooted obj(cx, &rval.toObject()); michael@0: obj = JS_ObjectToOuterObject(cx, obj); michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: rval.set(JS::ObjectValue(*obj)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Make sure to wrap the given string value into the right compartment, as michael@0: // needed. michael@0: MOZ_ALWAYS_INLINE michael@0: bool michael@0: MaybeWrapStringValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(rval.isString()); michael@0: JSString* str = rval.toString(); michael@0: if (JS::GetGCThingZone(str) != js::GetContextZone(cx)) { michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: // Make sure to wrap the given object value into the right compartment as michael@0: // needed. This will work correctly, but possibly slowly, on all objects. michael@0: MOZ_ALWAYS_INLINE michael@0: bool michael@0: MaybeWrapObjectValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(rval.isObject()); michael@0: michael@0: // Cross-compartment always requires wrapping. michael@0: JSObject* obj = &rval.toObject(); michael@0: if (js::GetObjectCompartment(obj) != js::GetContextCompartment(cx)) { michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // We're same-compartment, but even then we might need to wrap michael@0: // objects specially. Check for that. michael@0: if (IsDOMObject(obj)) { michael@0: return TryToOuterize(cx, rval); michael@0: } michael@0: michael@0: // It's not a WebIDL object. But it might be an XPConnect one, in which case michael@0: // we may need to outerize here, so make sure to call JS_WrapValue. michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // Like MaybeWrapObjectValue, but also allows null michael@0: MOZ_ALWAYS_INLINE michael@0: bool michael@0: MaybeWrapObjectOrNullValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(rval.isObjectOrNull()); michael@0: if (rval.isNull()) { michael@0: return true; michael@0: } michael@0: return MaybeWrapObjectValue(cx, rval); michael@0: } michael@0: michael@0: // Wrapping for objects that are known to not be DOM or XPConnect objects michael@0: MOZ_ALWAYS_INLINE michael@0: bool michael@0: MaybeWrapNonDOMObjectValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(rval.isObject()); michael@0: MOZ_ASSERT(!GetDOMClass(&rval.toObject())); michael@0: MOZ_ASSERT(!(js::GetObjectClass(&rval.toObject())->flags & michael@0: JSCLASS_PRIVATE_IS_NSISUPPORTS)); michael@0: michael@0: JSObject* obj = &rval.toObject(); michael@0: if (js::GetObjectCompartment(obj) == js::GetContextCompartment(cx)) { michael@0: return true; michael@0: } michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // Like MaybeWrapNonDOMObjectValue but allows null michael@0: MOZ_ALWAYS_INLINE michael@0: bool michael@0: MaybeWrapNonDOMObjectOrNullValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(rval.isObjectOrNull()); michael@0: if (rval.isNull()) { michael@0: return true; michael@0: } michael@0: return MaybeWrapNonDOMObjectValue(cx, rval); michael@0: } michael@0: michael@0: // If rval is a gcthing and is not in the compartment of cx, wrap rval michael@0: // into the compartment of cx (typically by replacing it with an Xray or michael@0: // cross-compartment wrapper around the original object). michael@0: MOZ_ALWAYS_INLINE bool michael@0: MaybeWrapValue(JSContext* cx, JS::MutableHandle rval) michael@0: { michael@0: if (rval.isString()) { michael@0: return MaybeWrapStringValue(cx, rval); michael@0: } michael@0: michael@0: if (!rval.isObject()) { michael@0: return true; michael@0: } michael@0: michael@0: return MaybeWrapObjectValue(cx, rval); michael@0: } michael@0: michael@0: // Create a JSObject wrapping "value", if there isn't one already, and store it michael@0: // in rval. "value" must be a concrete class that implements a michael@0: // GetWrapperPreserveColor() which can return its existing wrapper, if any, and michael@0: // a WrapObject() which will try to create a wrapper. Typically, this is done by michael@0: // having "value" inherit from nsWrapperCache. michael@0: template michael@0: MOZ_ALWAYS_INLINE bool michael@0: WrapNewBindingObject(JSContext* cx, T* value, JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(value); michael@0: JSObject* obj = value->GetWrapperPreserveColor(); michael@0: // We can get rid of this when we remove support for hasXPConnectImpls. michael@0: bool couldBeDOMBinding = CouldBeDOMBinding(value); michael@0: if (obj) { michael@0: JS::ExposeObjectToActiveJS(obj); michael@0: } else { michael@0: // Inline this here while we have non-dom objects in wrapper caches. michael@0: if (!couldBeDOMBinding) { michael@0: return false; michael@0: } michael@0: michael@0: obj = value->WrapObject(cx); michael@0: if (!obj) { michael@0: // At this point, obj is null, so just return false. michael@0: // Callers seem to be testing JS_IsExceptionPending(cx) to michael@0: // figure out whether WrapObject() threw. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: const DOMClass* clasp = GetDOMClass(obj); michael@0: // clasp can be null if the cache contained a non-DOM object. michael@0: if (clasp) { michael@0: // Some sanity asserts about our object. Specifically: michael@0: // 1) If our class claims we're nsISupports, we better be nsISupports michael@0: // XXXbz ideally, we could assert that reinterpret_cast to nsISupports michael@0: // does the right thing, but I don't see a way to do it. :( michael@0: // 2) If our class doesn't claim we're nsISupports we better be michael@0: // reinterpret_castable to nsWrapperCache. michael@0: MOZ_ASSERT(clasp, "What happened here?"); michael@0: MOZ_ASSERT_IF(clasp->mDOMObjectIsISupports, (IsBaseOf::value)); michael@0: MOZ_ASSERT(CheckWrapperCacheCast::Check()); michael@0: } michael@0: #endif michael@0: michael@0: rval.set(JS::ObjectValue(*obj)); michael@0: michael@0: bool sameCompartment = michael@0: js::GetObjectCompartment(obj) == js::GetContextCompartment(cx); michael@0: if (sameCompartment && couldBeDOMBinding) { michael@0: // We only need to outerize Window objects, so anything inheriting from michael@0: // nsGlobalWindow (which inherits from EventTarget itself). michael@0: return IsBaseOf::value || IsSame::value ? michael@0: TryToOuterize(cx, rval) : true; michael@0: } michael@0: michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // Create a JSObject wrapping "value", for cases when "value" is a michael@0: // non-wrapper-cached object using WebIDL bindings. "value" must implement a michael@0: // WrapObject() method taking a JSContext and a scope. michael@0: template michael@0: inline bool michael@0: WrapNewBindingNonWrapperCachedObject(JSContext* cx, michael@0: JS::Handle scopeArg, michael@0: T* value, michael@0: JS::MutableHandle rval) michael@0: { michael@0: MOZ_ASSERT(value); michael@0: // We try to wrap in the compartment of the underlying object of "scope" michael@0: JS::Rooted obj(cx); michael@0: { michael@0: // scope for the JSAutoCompartment so that we restore the compartment michael@0: // before we call JS_WrapValue. michael@0: Maybe ac; michael@0: // Maybe doesn't so much work, and in any case, adding michael@0: // more Maybe (one for a Rooted and one for a Handle) adds more michael@0: // code (and branches!) than just adding a single rooted. michael@0: JS::Rooted scope(cx, scopeArg); michael@0: if (js::IsWrapper(scope)) { michael@0: scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false); michael@0: if (!scope) michael@0: return false; michael@0: ac.construct(cx, scope); michael@0: } michael@0: michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); michael@0: obj = value->WrapObject(cx); michael@0: } michael@0: michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: // We can end up here in all sorts of compartments, per above. Make michael@0: // sure to JS_WrapValue! michael@0: rval.set(JS::ObjectValue(*obj)); michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // Create a JSObject wrapping "value", for cases when "value" is a michael@0: // non-wrapper-cached owned object using WebIDL bindings. "value" must implement a michael@0: // WrapObject() method taking a JSContext, a scope, and a boolean outparam that michael@0: // is true if the JSObject took ownership michael@0: template michael@0: inline bool michael@0: WrapNewBindingNonWrapperCachedOwnedObject(JSContext* cx, michael@0: JS::Handle scopeArg, michael@0: nsAutoPtr& value, michael@0: JS::MutableHandle rval) michael@0: { michael@0: // We do a runtime check on value, because otherwise we might in michael@0: // fact end up wrapping a null and invoking methods on it later. michael@0: if (!value) { michael@0: NS_RUNTIMEABORT("Don't try to wrap null objects"); michael@0: } michael@0: // We try to wrap in the compartment of the underlying object of "scope" michael@0: JS::Rooted obj(cx); michael@0: { michael@0: // scope for the JSAutoCompartment so that we restore the compartment michael@0: // before we call JS_WrapValue. michael@0: Maybe ac; michael@0: // Maybe doesn't so much work, and in any case, adding michael@0: // more Maybe (one for a Rooted and one for a Handle) adds more michael@0: // code (and branches!) than just adding a single rooted. michael@0: JS::Rooted scope(cx, scopeArg); michael@0: if (js::IsWrapper(scope)) { michael@0: scope = js::CheckedUnwrap(scope, /* stopAtOuter = */ false); michael@0: if (!scope) michael@0: return false; michael@0: ac.construct(cx, scope); michael@0: } michael@0: michael@0: bool tookOwnership = false; michael@0: MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); michael@0: obj = value->WrapObject(cx, &tookOwnership); michael@0: MOZ_ASSERT_IF(obj, tookOwnership); michael@0: if (tookOwnership) { michael@0: value.forget(); michael@0: } michael@0: } michael@0: michael@0: if (!obj) { michael@0: return false; michael@0: } michael@0: michael@0: // We can end up here in all sorts of compartments, per above. Make michael@0: // sure to JS_WrapValue! michael@0: rval.set(JS::ObjectValue(*obj)); michael@0: return JS_WrapValue(cx, rval); michael@0: } michael@0: michael@0: // Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). michael@0: template