diff -r 000000000000 -r 6474c204b198 dom/bindings/BindingUtils.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/bindings/BindingUtils.h Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2751 @@ +/* -*- 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/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 "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 DOMClass* +GetDOMClass(JSObject* obj) +{ + const js::Class* clasp = js::GetObjectClass(obj); + if (IsDOMClass(clasp)) { + return &DOMJSClass::FromJSClass(clasp)->mClass; + } + return nullptr; +} + +inline nsISupports* +UnwrapDOMObjectToISupports(JSObject* aObject) +{ + const DOMClass* 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 DOMClass* 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_CallHeapObjectTracer(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_CallHeapObjectTracer(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 + }; + + 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)); +} + +inline void +TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); + + 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. + * domClass is the DOMClass of instance objects for this class. This can be + * null if this is not a concrete proto. + * 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 DOMClass* domClass, + const NativeProperties* regularProperties, + const NativeProperties* chromeOnlyProperties, + const char* name, bool defineOnGlobal); + +/* + * Define the unforgeable attributes on an object. + */ +bool +DefineUnforgeableAttributes(JSContext* cx, JS::Handle obj, + const Prefable* props); + +bool +DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx, + JS::Handle obj, + const NativeProperties* properties, + bool defineUnforgeableAttributes); + +#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::GetGCThingZone(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 DOMClass* 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.construct(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.construct(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