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