Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 <type_traits>
#include "jsfriendapi.h"
#include "js/CharacterEncoding.h"
#include "js/Conversions.h"
#include "js/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
#include "js/MemoryFunctions.h"
#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
#include "js/RealmOptions.h"
#include "js/String.h" // JS::GetLatin1LinearStringChars, JS::GetTwoByteLinearStringChars, JS::GetLinearStringLength, JS::LinearStringHasLatin1Chars, JS::StringHasLatin1Chars
#include "js/Wrapper.h"
#include "js/Zone.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Array.h"
#include "mozilla/Assertions.h"
#include "mozilla/DeferredFinalize.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/BindingCallContext.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/DOMJSClass.h"
#include "mozilla/dom/DOMJSProxyHandler.h"
#include "mozilla/dom/JSSlots.h"
#include "mozilla/dom/NonRefcountedDOMObject.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PrototypeList.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "mozilla/SegmentedVector.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "nsIGlobalObject.h"
#include "nsJSUtils.h"
#include "nsISupportsImpl.h"
#include "xpcObjectHelper.h"
#include "xpcpublic.h"
#include "nsIVariant.h"
#include "mozilla/dom/FakeString.h"
#include "nsWrapperCacheInlines.h"
class nsGlobalWindowInner;
class nsGlobalWindowOuter;
class nsIInterfaceRequestor;
namespace mozilla {
enum UseCounter : int16_t;
enum class UseCounterWorker : int16_t;
namespace dom {
class CustomElementReactionsStack;
class Document;
class EventTarget;
class MessageManagerGlobal;
class DedicatedWorkerGlobalScope;
template <typename KeyType, typename ValueType>
class Record;
class WindowProxyHolder;
enum class DeprecatedOperations : uint16_t;
nsresult UnwrapArgImpl(JSContext* cx, JS::Handle<JSObject*> src,
const nsIID& iid, void** ppArg);
/** Convert a jsval to an XPCOM pointer. Caller must not assume that src will
keep the XPCOM pointer rooted. */
template <class Interface>
inline nsresult UnwrapArg(JSContext* cx, JS::Handle<JSObject*> src,
Interface** ppArg) {
return UnwrapArgImpl(cx, src, NS_GET_TEMPLATE_IID(Interface),
reinterpret_cast<void**>(ppArg));
}
nsresult UnwrapWindowProxyArg(JSContext* cx, JS::Handle<JSObject*> src,
WindowProxyHolder& ppArg);
// Returns true if the JSClass is used for DOM objects.
inline bool IsDOMClass(const JSClass* clasp) {
return clasp->flags & JSCLASS_IS_DOMJSCLASS;
}
// Return true if the JSClass is used for non-proxy DOM objects.
inline bool IsNonProxyDOMClass(const JSClass* clasp) {
return IsDOMClass(clasp) && clasp->isNativeObject();
}
// 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;
}
static_assert(DOM_OBJECT_SLOT == 0,
"DOM_OBJECT_SLOT doesn't match the proxy private slot. "
"Expect bad things");
template <class T>
inline T* UnwrapDOMObject(JSObject* obj) {
MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
"Don't pass non-DOM objects to this function");
JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
return static_cast<T*>(val.toPrivate());
}
template <class T>
inline T* UnwrapPossiblyNotInitializedDOMObject(JSObject* obj) {
// This is used by the OjectMoved JSClass hook which can be called before
// JS_NewObject has returned and so before we have a chance to set
// DOM_OBJECT_SLOT to anything useful.
MOZ_ASSERT(IsDOMClass(JS::GetClass(obj)),
"Don't pass non-DOM objects to this function");
JS::Value val = JS::GetReservedSlot(obj, DOM_OBJECT_SLOT);
if (val.isUndefined()) {
return nullptr;
}
return static_cast<T*>(val.toPrivate());
}
inline const DOMJSClass* GetDOMClass(const JSClass* clasp) {
return IsDOMClass(clasp) ? DOMJSClass::FromJSClass(clasp) : nullptr;
}
inline const DOMJSClass* GetDOMClass(JSObject* obj) {
return GetDOMClass(JS::GetClass(obj));
}
inline nsISupports* UnwrapDOMObjectToISupports(JSObject* aObject) {
const DOMJSClass* clasp = GetDOMClass(aObject);
if (!clasp || !clasp->mDOMObjectIsISupports) {
return nullptr;
}
return UnwrapPossiblyNotInitializedDOMObject<nsISupports>(aObject);
}
inline bool IsDOMObject(JSObject* obj) { return IsDOMClass(JS::GetClass(obj)); }
// There are two valid ways to use UNWRAP_OBJECT: Either obj needs to
// be a MutableHandle<JSObject*>, or value needs to be a strong-reference
// smart pointer type (OwningNonNull or RefPtr or nsCOMPtr), in which case obj
// can be anything that converts to JSObject*.
//
// This can't be used with Window, EventTarget, or Location as the "Interface"
// argument (and will fail a static_assert if you try to do that). Use
// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT to unwrap to those interfaces.
#define UNWRAP_OBJECT(Interface, obj, value) \
mozilla::dom::binding_detail::UnwrapObjectWithCrossOriginAsserts< \
mozilla::dom::prototypes::id::Interface, \
mozilla::dom::Interface##_Binding::NativeType>(obj, value)
// UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT is just like UNWRAP_OBJECT but requires a
// JSContext in a Realm that represents "who is doing the unwrapping?" to
// properly unwrap the object.
#define UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Interface, obj, value, cx) \
mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface, \
mozilla::dom::Interface##_Binding::NativeType>( \
obj, value, cx)
// Test whether the given object is an instance of the given interface.
#define IS_INSTANCE_OF(Interface, obj) \
mozilla::dom::IsInstanceOf<mozilla::dom::prototypes::id::Interface, \
mozilla::dom::Interface##_Binding::NativeType>( \
obj)
// Unwrap the given non-wrapper object. This can be used with any obj that
// converts to JSObject*; as long as that JSObject* is live the return value
// will be valid.
#define UNWRAP_NON_WRAPPER_OBJECT(Interface, obj, value) \
mozilla::dom::UnwrapNonWrapperObject< \
mozilla::dom::prototypes::id::Interface, \
mozilla::dom::Interface##_Binding::NativeType>(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 RefPtr<T>).
//
// The obj argument will be mutated to point to CheckedUnwrap of itself if the
// passed-in value is not a DOM object and CheckedUnwrap succeeds.
//
// If mayBeWrapper is true, there are three valid ways to invoke
// UnwrapObjectInternal: Either obj needs to be a class wrapping a
// MutableHandle<JSObject*>, with an assignment operator that sets the handle to
// the given object, or U needs to be a strong-reference smart pointer type
// (OwningNonNull or RefPtr or nsCOMPtr), or the value being stored in "value"
// must not escape past being tested for falsiness immediately after the
// UnwrapObjectInternal call.
//
// If mayBeWrapper is false, obj can just be a JSObject*, and U anything that a
// T* can be assigned to.
//
// The cx arg is in practice allowed to be either nullptr or JSContext* or a
// BindingCallContext reference. If it's nullptr we will do a
// CheckedUnwrapStatic and it's the caller's responsibility to make sure they're
// not trying to work with Window or Location objects. Otherwise we'll do a
// CheckedUnwrapDynamic. This all only matters if mayBeWrapper is true; if it's
// false just pass nullptr for the cx arg.
namespace binding_detail {
template <class T, bool mayBeWrapper, typename U, typename V, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObjectInternal(V& obj, U& value,
prototypes::ID protoID,
uint32_t protoDepth,
const CxType& cx) {
static_assert(std::is_same_v<CxType, JSContext*> ||
std::is_same_v<CxType, BindingCallContext> ||
std::is_same_v<CxType, decltype(nullptr)>,
"Unexpected CxType");
/* First check to see whether we have a DOM object */
const DOMJSClass* domClass = GetDOMClass(obj);
if (domClass) {
/* 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<T>(obj);
return NS_OK;
}
}
/* Maybe we have a security wrapper or outer window? */
if (!mayBeWrapper || !js::IsWrapper(obj)) {
// For non-cross-origin-accessible methods and properties, remote object
// proxies should behave the same as opaque wrappers.
if (IsRemoteObjectProxy(obj)) {
return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
}
/* Not a DOM object, not a wrapper, just bail */
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
JSObject* unwrappedObj;
if (std::is_same_v<CxType, decltype(nullptr)>) {
unwrappedObj = js::CheckedUnwrapStatic(obj);
} else {
unwrappedObj =
js::CheckedUnwrapDynamic(obj, cx, /* stopAtWindowProxy = */ false);
}
if (!unwrappedObj) {
return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
}
if (std::is_same_v<CxType, decltype(nullptr)>) {
// We might still have a windowproxy here. But it shouldn't matter, because
// that's not what the caller is looking for, so we're going to fail out
// anyway below once we do the recursive call to ourselves with wrapper
// unwrapping disabled.
MOZ_ASSERT(!js::IsWrapper(unwrappedObj) || js::IsWindowProxy(unwrappedObj));
} else {
// We shouldn't have a wrapper by now.
MOZ_ASSERT(!js::IsWrapper(unwrappedObj));
}
// Recursive call is OK, because now we're using false for mayBeWrapper and
// we never reach this code if that boolean is false, so can't keep calling
// ourselves.
//
// Unwrap into a temporary pointer, because in general unwrapping into
// something of type U might trigger GC (e.g. release the value currently
// stored in there, with arbitrary consequences) and invalidate the
// "unwrappedObj" pointer.
T* tempValue = nullptr;
nsresult rv = UnwrapObjectInternal<T, false>(unwrappedObj, tempValue, protoID,
protoDepth, nullptr);
if (NS_SUCCEEDED(rv)) {
// Suppress a hazard related to keeping tempValue alive across
// UnwrapObjectInternal, because the analysis can't tell that this function
// will not GC if maybeWrapped=False and we've already gone through a level
// of unwrapping so unwrappedObj will be !IsWrapper.
JS::AutoSuppressGCAnalysis suppress;
// It's very important to not update "obj" with the "unwrappedObj" value
// until we know the unwrap has succeeded. Otherwise, in a situation in
// which we have an overload of object and primitive we could end up
// converting to the primitive from the unwrappedObj, whereas we want to do
// it from the original object.
obj = unwrappedObj;
// And now assign to "value"; at this point we don't care if a GC happens
// and invalidates unwrappedObj.
value = tempValue;
return NS_OK;
}
/* It's the wrong sort of DOM object */
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
struct MutableObjectHandleWrapper {
explicit MutableObjectHandleWrapper(JS::MutableHandle<JSObject*> aHandle)
: mHandle(aHandle) {}
void operator=(JSObject* aObject) {
MOZ_ASSERT(aObject);
mHandle.set(aObject);
}
operator JSObject*() const { return mHandle; }
private:
JS::MutableHandle<JSObject*> mHandle;
};
struct MutableValueHandleWrapper {
explicit MutableValueHandleWrapper(JS::MutableHandle<JS::Value> aHandle)
: mHandle(aHandle) {}
void operator=(JSObject* aObject) {
MOZ_ASSERT(aObject);
mHandle.setObject(*aObject);
}
operator JSObject*() const { return &mHandle.toObject(); }
private:
JS::MutableHandle<JS::Value> mHandle;
};
} // namespace binding_detail
// UnwrapObject overloads that ensure we have a MutableHandle to keep it alive.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JSObject*> obj,
U& value, const CxType& cx) {
binding_detail::MutableObjectHandleWrapper wrapper(obj);
return binding_detail::UnwrapObjectInternal<T, true>(
wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::MutableHandle<JS::Value> obj,
U& value, const CxType& cx) {
MOZ_ASSERT(obj.isObject());
binding_detail::MutableValueHandleWrapper wrapper(obj);
return binding_detail::UnwrapObjectInternal<T, true>(
wrapper, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}
// UnwrapObject overloads that ensure we have a strong ref to keep it alive.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, RefPtr<U>& value,
const CxType& cx) {
return binding_detail::UnwrapObjectInternal<T, true>(
obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, nsCOMPtr<U>& value,
const CxType& cx) {
return binding_detail::UnwrapObjectInternal<T, true>(
obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JSObject* obj, OwningNonNull<U>& value,
const CxType& cx) {
return binding_detail::UnwrapObjectInternal<T, true>(
obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, cx);
}
// An UnwrapObject overload that just calls one of the JSObject* ones.
template <prototypes::ID PrototypeID, class T, typename U, typename CxType>
MOZ_ALWAYS_INLINE nsresult UnwrapObject(JS::Handle<JS::Value> obj, U& value,
const CxType& cx) {
MOZ_ASSERT(obj.isObject());
return UnwrapObject<PrototypeID, T>(&obj.toObject(), value, cx);
}
template <prototypes::ID PrototypeID>
MOZ_ALWAYS_INLINE void AssertStaticUnwrapOK() {
static_assert(PrototypeID != prototypes::id::Window,
"Can't do static unwrap of WindowProxy; use "
"UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
"aware version of IS_INSTANCE_OF");
static_assert(PrototypeID != prototypes::id::EventTarget,
"Can't do static unwrap of WindowProxy (which an EventTarget "
"might be); use UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a "
"cross-origin-object aware version of IS_INSTANCE_OF");
static_assert(PrototypeID != prototypes::id::Location,
"Can't do static unwrap of Location; use "
"UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT or a cross-origin-object "
"aware version of IS_INSTANCE_OF");
}
namespace binding_detail {
// This function is just here so we can do some static asserts in a centralized
// place instead of putting them in every single UnwrapObject overload.
template <prototypes::ID PrototypeID, class T, typename U, typename V>
MOZ_ALWAYS_INLINE nsresult UnwrapObjectWithCrossOriginAsserts(V&& obj,
U& value) {
AssertStaticUnwrapOK<PrototypeID>();
return UnwrapObject<PrototypeID, T>(obj, value, nullptr);
}
} // namespace binding_detail
template <prototypes::ID PrototypeID, class T>
MOZ_ALWAYS_INLINE bool IsInstanceOf(JSObject* obj) {
AssertStaticUnwrapOK<PrototypeID>();
void* ignored;
nsresult unwrapped = binding_detail::UnwrapObjectInternal<T, true>(
obj, ignored, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
return NS_SUCCEEDED(unwrapped);
}
template <prototypes::ID PrototypeID, class T, typename U>
MOZ_ALWAYS_INLINE nsresult UnwrapNonWrapperObject(JSObject* obj, U& value) {
MOZ_ASSERT(!js::IsWrapper(obj));
return binding_detail::UnwrapObjectInternal<T, false>(
obj, value, PrototypeID, PrototypeTraits<PrototypeID>::Depth, nullptr);
}
MOZ_ALWAYS_INLINE bool IsConvertibleToDictionary(JS::Handle<JS::Value> val) {
return val.isNullOrUndefined() || val.isObject();
}
// The items in the protoAndIfaceCache are indexed by the prototypes::id::ID,
// constructors::id::ID and namedpropertiesobjects::id::ID enums, in that order.
// The end of the prototype objects should be the start of the interface
// objects, and the end of the interface objects should be the start of the
// named properties objects.
static_assert((size_t)constructors::id::_ID_Start ==
(size_t)prototypes::id::_ID_Count &&
(size_t)namedpropertiesobjects::id::_ID_Start ==
(size_t)constructors::id::_ID_Count,
"Overlapping or discontiguous indexes.");
const size_t kProtoAndIfaceCacheCount = namedpropertiesobjects::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<JS::Heap<JSObject*>, kProtoAndIfaceCacheCount> {
public:
bool HasEntryInSlot(size_t i) { return (*this)[i]; }
JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) { return (*this)[i]; }
JS::Heap<JSObject*>& EntrySlotMustExist(size_t i) { return (*this)[i]; }
void Trace(JSTracer* aTracer) {
for (size_t i = 0; i < ArrayLength(*this); ++i) {
JS::TraceEdge(aTracer, &(*this)[i], "protoAndIfaceCache[i]");
}
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
return aMallocSizeOf(this);
}
};
class PageTableCache {
public:
PageTableCache() { memset(mPages.begin(), 0, sizeof(mPages)); }
~PageTableCache() {
for (size_t i = 0; i < ArrayLength(mPages); ++i) {
delete mPages[i];
}
}
bool HasEntryInSlot(size_t i) {
MOZ_ASSERT(i < kProtoAndIfaceCacheCount);
size_t pageIndex = i / kPageSize;
size_t leafIndex = i % kPageSize;
Page* p = mPages[pageIndex];
if (!p) {
return false;
}
return (*p)[leafIndex];
}
JS::Heap<JSObject*>& 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<JSObject*>& 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) {
JS::TraceEdge(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<JS::Heap<JSObject*>, kPageSize> Page;
static const size_t kNPages =
kProtoAndIfaceCacheCount / kPageSize +
size_t(bool(kProtoAndIfaceCacheCount % kPageSize));
Array<Page*, kNPages> 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)
// Return whether slot i contains an object. This doesn't return the object
// itself because in practice consumers just want to know whether it's there
// or not, and that doesn't require barriering, which returning the object
// pointer does.
bool HasEntryInSlot(size_t i) { FORWARD_OPERATION(HasEntryInSlot, (i)); }
// Return a reference to slot i, creating it if necessary. There
// may not be an object in the returned slot.
JS::Heap<JSObject*>& EntrySlotOrCreate(size_t i) {
FORWARD_OPERATION(EntrySlotOrCreate, (i));
}
// Return a reference to slot i, which is guaranteed to already
// exist. There may not be an object in the slot, if prototype and
// constructor initialization for one of our bindings failed.
JS::Heap<JSObject*>& 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::GetClass(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
struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer {
bool ok;
explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSContext* cx)
: JS::CallbackTracer(cx, JS::TracerKind::VerifyTraceProtoAndIface),
ok(false) {}
void onChild(const JS::GCCellPtr&) override {
// We don't do anything here, we only want to verify that
// TraceProtoAndIfaceCache was called.
}
};
#endif
inline void TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) {
MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
#ifdef DEBUG
if (trc->kind() == JS::TracerKind::VerifyTraceProtoAndIface) {
// We don't do anything here, we only want to verify that
// TraceProtoAndIfaceCache was called.
static_cast<VerifyTraceProtoAndIfaceCacheCalledTracer*>(trc)->ok = true;
return;
}
#endif
if (!DOMGlobalHasProtoAndIFaceCache(obj)) return;
ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
protoAndIfaceCache->Trace(trc);
}
inline void DestroyProtoAndIfaceCache(JSObject* obj) {
MOZ_ASSERT(JS::GetClass(obj)->flags & JSCLASS_DOM_GLOBAL);
if (!DOMGlobalHasProtoAndIFaceCache(obj)) {
return;
}
ProtoAndIfaceCache* protoAndIfaceCache = GetProtoAndIfaceCache(obj);
delete protoAndIfaceCache;
}
/**
* Add constants to an object.
*/
bool DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
const ConstantSpec* cs);
struct JSNativeHolder {
JSNative mNative;
const NativePropertyHooks* mPropertyHooks;
};
struct LegacyFactoryFunction {
const char* mName;
const JSNativeHolder mHolder;
unsigned mNargs;
};
// clang-format off
/*
* 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. This can be
* null if both constructorClass and constructor are null (as in,
* if we're not creating an interface object at all).
* 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.
* name the name to use for 1) the WebIDL class string, which is the value
* that's used for @@toStringTag, 2) the name property for interface
* objects and 3) the property on the global object that would be set to
* the interface object. In general this is the interface identifier.
* LegacyNamespace would expect something different for 1), but we don't
* support that. The class string for default iterator objects is not
* usable as 2) or 3), but default iterator objects don't have an interface
* object.
* 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.
* unscopableNames if not null it points to a null-terminated list of const
* char* names of the unscopable properties for this interface.
* isGlobal if true, we're creating interface objects for a [Global] interface,
* and hence shouldn't define properties on the prototype object.
* legacyWindowAliases if not null it points to a null-terminated list of const
* char* names of the legacy window aliases for this
* interface.
*
* 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.
*/
// clang-format on
void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const JSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
const JSClass* constructorClass, unsigned ctorNargs,
const LegacyFactoryFunction* namedConstructors,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, const char* name,
bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases, bool isNamespace);
/**
* Define the properties (regular and chrome-only) on obj.
*
* obj the object to install 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<JSObject*> obj,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties);
/*
* Define the legacy unforgeable methods on an object.
*/
bool DefineLegacyUnforgeableMethods(
JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSFunctionSpec>* props);
/*
* Define the legacy unforgeable attributes on an object.
*/
bool DefineLegacyUnforgeableAttributes(
JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSPropertySpec>* props);
#define HAS_MEMBER_TYPEDEFS \
private: \
typedef char yes[1]; \
typedef char no[2]
#ifdef _MSC_VER
# define HAS_MEMBER_CHECK(_name) \
template <typename V> \
static yes& Check##_name(char(*)[(&V::_name == 0) + 1])
#else
# define HAS_MEMBER_CHECK(_name) \
template <typename V> \
static yes& Check##_name(char(*)[sizeof(&V::_name) + 1])
#endif
#define HAS_MEMBER(_memberName, _valueName) \
private: \
HAS_MEMBER_CHECK(_memberName); \
template <typename V> \
static no& Check##_memberName(...); \
\
public: \
static bool const _valueName = \
sizeof(Check##_memberName<T>(nullptr)) == sizeof(yes)
template <class T>
struct NativeHasMember {
HAS_MEMBER_TYPEDEFS;
HAS_MEMBER(GetParentObject, GetParentObject);
HAS_MEMBER(WrapObject, WrapObject);
};
template <class T>
struct IsSmartPtr {
HAS_MEMBER_TYPEDEFS;
HAS_MEMBER(get, value);
};
template <class T>
struct IsRefcounted {
HAS_MEMBER_TYPEDEFS;
HAS_MEMBER(AddRef, HasAddref);
HAS_MEMBER(Release, HasRelease);
public:
static bool const value = HasAddref && HasRelease;
private:
// This struct only works if T is fully declared (not just forward declared).
// The std::is_base_of check will ensure that, we don't really need it for any
// other reason (the static assert will of course always be true).
static_assert(!std::is_base_of<nsISupports, T>::value || IsRefcounted::value,
"Classes derived from nsISupports are refcounted!");
};
#undef HAS_MEMBER
#undef HAS_MEMBER_CHECK
#undef HAS_MEMBER_TYPEDEFS
#ifdef DEBUG
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CheckWrapperCacheCast {
static bool Check() {
return reinterpret_cast<uintptr_t>(
static_cast<nsWrapperCache*>(reinterpret_cast<T*>(1))) == 1;
}
};
template <class T>
struct CheckWrapperCacheCast<T, true> {
static bool Check() { return true; }
};
#endif
inline bool TryToOuterize(JS::MutableHandle<JS::Value> rval) {
if (js::IsWindow(&rval.toObject())) {
JSObject* obj = js::ToWindowProxyIfWindow(&rval.toObject());
MOZ_ASSERT(obj);
rval.set(JS::ObjectValue(*obj));
}
return true;
}
inline bool TryToOuterize(JS::MutableHandle<JSObject*> obj) {
if (js::IsWindow(obj)) {
JSObject* proxy = js::ToWindowProxyIfWindow(obj);
MOZ_ASSERT(proxy);
obj.set(proxy);
}
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<JS::Value> rval) {
MOZ_ASSERT(rval.isString());
JSString* str = rval.toString();
if (JS::GetStringZone(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<JS::Value> rval) {
MOZ_ASSERT(rval.isObject());
// Cross-compartment always requires wrapping.
JSObject* obj = &rval.toObject();
if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
return JS_WrapValue(cx, rval);
}
// We're same-compartment, but we might still need to outerize if we
// have a Window.
return TryToOuterize(rval);
}
// Like MaybeWrapObjectValue, but working with a
// JS::MutableHandle<JSObject*> which must be non-null.
MOZ_ALWAYS_INLINE
bool MaybeWrapObject(JSContext* cx, JS::MutableHandle<JSObject*> obj) {
if (JS::GetCompartment(obj) != js::GetContextCompartment(cx)) {
return JS_WrapObject(cx, obj);
}
// We're same-compartment, but we might still need to outerize if we
// have a Window.
return TryToOuterize(obj);
}
// Like MaybeWrapObjectValue, but also allows null
MOZ_ALWAYS_INLINE
bool MaybeWrapObjectOrNullValue(JSContext* cx,
JS::MutableHandle<JS::Value> rval) {
MOZ_ASSERT(rval.isObjectOrNull());
if (rval.isNull()) {
return true;
}
return MaybeWrapObjectValue(cx, rval);
}
// Wrapping for objects that are known to not be DOM objects
MOZ_ALWAYS_INLINE
bool MaybeWrapNonDOMObjectValue(JSContext* cx,
JS::MutableHandle<JS::Value> rval) {
MOZ_ASSERT(rval.isObject());
// Compared to MaybeWrapObjectValue we just skip the TryToOuterize call. The
// only reason it would be needed is if we have a Window object, which would
// have a DOM class. Assert that we don't have any DOM-class objects coming
// through here.
MOZ_ASSERT(!GetDOMClass(&rval.toObject()));
JSObject* obj = &rval.toObject();
if (JS::GetCompartment(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<JS::Value> 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<JS::Value> rval) {
if (rval.isGCThing()) {
if (rval.isString()) {
return MaybeWrapStringValue(cx, rval);
}
if (rval.isObject()) {
return MaybeWrapObjectValue(cx, rval);
}
// This could be optimized by checking the zone first, similar to
// the way strings are handled. At present, this is used primarily
// for structured cloning, so avoiding the overhead of JS_WrapValue
// calls is less important than for other types.
if (rval.isBigInt()) {
return JS_WrapValue(cx, rval);
}
MOZ_ASSERT(rval.isSymbol());
JS_MarkCrossZoneId(cx, SYMBOL_TO_JSID(rval.toSymbol()));
}
return true;
}
namespace binding_detail {
enum GetOrCreateReflectorWrapBehavior {
eWrapIntoContextCompartment,
eDontWrapIntoContextCompartment
};
template <class T>
struct TypeNeedsOuterization {
// We only need to outerize Window objects, so anything inheriting from
// nsGlobalWindow (which inherits from EventTarget itself).
static const bool value = std::is_base_of<nsGlobalWindowInner, T>::value ||
std::is_base_of<nsGlobalWindowOuter, T>::value ||
std::is_same_v<EventTarget, T>;
};
#ifdef DEBUG
template <typename T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CheckWrapperCacheTracing {
static inline void Check(T* aObject) {}
};
template <typename T>
struct CheckWrapperCacheTracing<T, true> {
static void Check(T* aObject) {
// Rooting analysis thinks QueryInterface may GC, but we're dealing with
// a subset of QueryInterface, C++ only types here.
JS::AutoSuppressGCAnalysis nogc;
nsWrapperCache* wrapperCacheFromQI = nullptr;
aObject->QueryInterface(NS_GET_IID(nsWrapperCache),
reinterpret_cast<void**>(&wrapperCacheFromQI));
MOZ_ASSERT(wrapperCacheFromQI,
"Missing nsWrapperCache from QueryInterface implementation?");
if (!wrapperCacheFromQI->GetWrapperPreserveColor()) {
// Can't assert that we trace the wrapper, since we don't have any
// wrapper to trace.
return;
}
nsISupports* ccISupports = nullptr;
aObject->QueryInterface(NS_GET_IID(nsCycleCollectionISupports),
reinterpret_cast<void**>(&ccISupports));
MOZ_ASSERT(ccISupports,
"nsWrapperCache object which isn't cycle collectable?");
nsXPCOMCycleCollectionParticipant* participant = nullptr;
CallQueryInterface(ccISupports, &participant);
MOZ_ASSERT(participant, "Can't QI to CycleCollectionParticipant?");
wrapperCacheFromQI->CheckCCWrapperTraversal(ccISupports, participant);
}
};
void AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector,
JS::Handle<JSObject*> aGivenProto);
#endif // DEBUG
template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
MOZ_ALWAYS_INLINE bool DoGetOrCreateDOMReflector(
JSContext* cx, T* value, JS::Handle<JSObject*> givenProto,
JS::MutableHandle<JS::Value> rval) {
MOZ_ASSERT(value);
MOZ_ASSERT_IF(givenProto, js::IsObjectInContextCompartment(givenProto, cx));
JSObject* obj = value->GetWrapper();
if (obj) {
#ifdef DEBUG
AssertReflectorHasGivenProto(cx, obj, givenProto);
// Have to reget obj because AssertReflectorHasGivenProto can
// trigger gc so the pointer may now be invalid.
obj = value->GetWrapper();
#endif
} else {
obj = value->WrapObject(cx, givenProto);
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
if (std::is_base_of<nsWrapperCache, T>::value) {
CheckWrapperCacheTracing<T>::Check(value);
}
#endif
}
#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,
(std::is_base_of<nsISupports, T>::value));
MOZ_ASSERT(CheckWrapperCacheCast<T>::Check());
}
#endif
rval.set(JS::ObjectValue(*obj));
if (JS::GetCompartment(obj) == js::GetContextCompartment(cx)) {
return TypeNeedsOuterization<T>::value ? TryToOuterize(rval) : true;
}
if (wrapBehavior == eDontWrapIntoContextCompartment) {
if (TypeNeedsOuterization<T>::value) {
JSAutoRealm ar(cx, obj);
return TryToOuterize(rval);
}
return true;
}
return JS_WrapValue(cx, rval);
}
} // namespace binding_detail
// 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.
//
// The value stored in rval will be ready to be exposed to whatever JS
// is running on cx right now. In particular, it will be in the
// compartment of cx, and outerized as needed.
template <class T>
MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflector(
JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
using namespace binding_detail;
return DoGetOrCreateDOMReflector<T, eWrapIntoContextCompartment>(
cx, value, givenProto, rval);
}
// Like GetOrCreateDOMReflector but doesn't wrap into the context compartment,
// and hence does not actually require cx to be in a compartment.
template <class T>
MOZ_ALWAYS_INLINE bool GetOrCreateDOMReflectorNoWrap(
JSContext* cx, T* value, JS::MutableHandle<JS::Value> rval) {
using namespace binding_detail;
return DoGetOrCreateDOMReflector<T, eDontWrapIntoContextCompartment>(
cx, value, nullptr, 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 prototype (possibly null) and
// returning the resulting object via a MutableHandle<JSObject*> outparam.
template <class T>
inline bool WrapNewBindingNonWrapperCachedObject(
JSContext* cx, JS::Handle<JSObject*> scopeArg, T* value,
JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
MOZ_ASSERT(value);
// We try to wrap in the realm of the underlying object of "scope"
JS::Rooted<JSObject*> obj(cx);
{
// scope for the JSAutoRealm so that we restore the realm
// before we call JS_WrapValue.
Maybe<JSAutoRealm> ar;
// Maybe<Handle> 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<JSObject*> scope(cx, scopeArg);
JS::Rooted<JSObject*> proto(cx, givenProto);
if (js::IsWrapper(scope)) {
// We are working in the Realm of cx and will be producing our reflector
// there, so we need to succeed if that realm has access to the scope.
scope =
js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
if (!scope) return false;
ar.emplace(cx, scope);
if (!JS_WrapObject(cx, &proto)) {
return false;
}
} else {
// cx and scope are same-compartment, but they might still be
// different-Realm. Enter the Realm of scope, since that's
// where we want to create our object.
ar.emplace(cx, scope);
}
MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
if (!value->WrapObject(cx, proto, &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 MaybeWrapObjectValue(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 taking a JSContext and a prototype
// (possibly null) and returning two pieces of information: the resulting object
// via a MutableHandle<JSObject*> outparam and a boolean return value that is
// true if the JSObject took ownership
template <class T>
inline bool WrapNewBindingNonWrapperCachedObject(
JSContext* cx, JS::Handle<JSObject*> scopeArg, UniquePtr<T>& value,
JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
static_assert(!IsRefcounted<T>::value, "Only pass owned classes in here.");
// 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) {
MOZ_CRASH("Don't try to wrap null objects");
}
// We try to wrap in the realm of the underlying object of "scope"
JS::Rooted<JSObject*> obj(cx);
{
// scope for the JSAutoRealm so that we restore the realm
// before we call JS_WrapValue.
Maybe<JSAutoRealm> ar;
// Maybe<Handle> 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<JSObject*> scope(cx, scopeArg);
JS::Rooted<JSObject*> proto(cx, givenProto);
if (js::IsWrapper(scope)) {
// We are working in the Realm of cx and will be producing our reflector
// there, so we need to succeed if that realm has access to the scope.
scope =
js::CheckedUnwrapDynamic(scope, cx, /* stopAtWindowProxy = */ false);
if (!scope) return false;
ar.emplace(cx, scope);
if (!JS_WrapObject(cx, &proto)) {
return false;
}
} else {
// cx and scope are same-compartment, but they might still be
// different-Realm. Enter the Realm of scope, since that's
// where we want to create our object.
ar.emplace(cx, scope);
}
MOZ_ASSERT_IF(proto, js::IsObjectInContextCompartment(proto, cx));
MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx));
if (!value->WrapObject(cx, proto, &obj)) {
return false;
}
// JS object took ownership
Unused << value.release();
}
// We can end up here in all sorts of compartments, per above. Make
// sure to JS_WrapValue!
rval.set(JS::ObjectValue(*obj));
return MaybeWrapObjectValue(cx, rval);
}
// Helper for smart pointers (nsRefPtr/nsCOMPtr).
template <template <typename> class SmartPtr, typename T,
typename U = std::enable_if_t<IsRefcounted<T>::value, T>,
typename V = std::enable_if_t<IsSmartPtr<SmartPtr<T>>::value, T>>
inline bool WrapNewBindingNonWrapperCachedObject(
JSContext* cx, JS::Handle<JSObject*> scope, const SmartPtr<T>& value,
JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
return WrapNewBindingNonWrapperCachedObject(cx, scope, value.get(), rval,
givenProto);
}
// Helper for object references (as opposed to pointers).
template <typename T, typename U = std::enable_if_t<!IsSmartPtr<T>::value, T>>
inline bool WrapNewBindingNonWrapperCachedObject(
JSContext* cx, JS::Handle<JSObject*> scope, T& value,
JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
return WrapNewBindingNonWrapperCachedObject(cx, scope, &value, rval,
givenProto);
}
template <bool Fatal>
inline bool EnumValueNotFound(BindingCallContext& cx, JS::HandleString str,
const char* type, const char* sourceDescription);
template <>
inline bool EnumValueNotFound<false>(BindingCallContext& cx,
JS::HandleString str, const char* type,
const char* sourceDescription) {
// TODO: Log a warning to the console.
return true;
}
template <>
inline bool EnumValueNotFound<true>(BindingCallContext& cx,
JS::HandleString str, const char* type,
const char* sourceDescription) {
JS::UniqueChars deflated = JS_EncodeStringToUTF8(cx, str);
if (!deflated) {
return false;
}
return cx.ThrowErrorMessage<MSG_INVALID_ENUM_VALUE>(sourceDescription,
deflated.get(), type);
}
template <typename CharT>
inline int FindEnumStringIndexImpl(const CharT* chars, size_t length,
const EnumEntry* values) {
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) {
return i;
}
}
return -1;
}
template <bool InvalidValueFatal>
inline bool FindEnumStringIndex(BindingCallContext& cx, JS::Handle<JS::Value> v,
const EnumEntry* values, const char* type,
const char* sourceDescription, int* index) {
// JS_StringEqualsAscii is slow as molasses, so don't use it here.
JS::RootedString str(cx, JS::ToString(cx, v));
if (!str) {
return false;
}
{
size_t length;
JS::AutoCheckCannotGC nogc;
if (JS::StringHasLatin1Chars(str)) {
const JS::Latin1Char* chars =
JS_GetLatin1StringCharsAndLength(cx, nogc, str, &length);
if (!chars) {
return false;
}
*index = FindEnumStringIndexImpl(chars, length, values);
} else {
const char16_t* chars =
JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &length);
if (!chars) {
return false;
}
*index = FindEnumStringIndexImpl(chars, length, values);
}
if (*index >= 0) {
return true;
}
}
return EnumValueNotFound<InvalidValueFatal>(cx, str, type, sourceDescription);
}
inline nsWrapperCache* GetWrapperCache(const ParentObject& aParentObject) {
return aParentObject.mWrapperCache;
}
template <class T>
inline T* GetParentPointer(T* aObject) {
return aObject;
}
inline nsISupports* GetParentPointer(const ParentObject& aObject) {
return aObject.mObject;
}
template <typename T>
inline mozilla::dom::ReflectionScope GetReflectionScope(T* aParentObject) {
return mozilla::dom::ReflectionScope::Content;
}
inline mozilla::dom::ReflectionScope GetReflectionScope(
const ParentObject& aParentObject) {
return aParentObject.mReflectionScope;
}
template <class T>
inline void ClearWrapper(T* p, nsWrapperCache* cache, JSObject* obj) {
MOZ_ASSERT(cache->GetWrapperMaybeDead() == obj ||
(js::RuntimeIsBeingDestroyed() && !cache->GetWrapperMaybeDead()));
cache->ClearWrapper(obj);
}
template <class T>
inline void ClearWrapper(T* p, void*, JSObject* obj) {
// QueryInterface to nsWrapperCache can't GC, we hope.
JS::AutoSuppressGCAnalysis nogc;
nsWrapperCache* cache;
CallQueryInterface(p, &cache);
ClearWrapper(p, cache, obj);
}
template <class T>
inline void UpdateWrapper(T* p, nsWrapperCache* cache, JSObject* obj,
const JSObject* old) {
JS::AutoAssertGCCallback inCallback;
cache->UpdateWrapper(obj, old);
}
template <class T>
inline void UpdateWrapper(T* p, void*, JSObject* obj, const JSObject* old) {
JS::AutoAssertGCCallback inCallback;
nsWrapperCache* cache;
CallQueryInterface(p, &cache);
UpdateWrapper(p, cache, obj, old);
}
// Attempt to preserve the wrapper, if any, for a Paris DOM bindings object.
// Return true if we successfully preserved the wrapper, or there is no wrapper
// to preserve. In the latter case we don't need to preserve the wrapper,
// because the object can only be obtained by JS once, or they cannot be
// meaningfully owned from the native side.
//
// This operation will return false only for non-nsISupports cycle-collected
// objects, because we cannot determine if they are wrappercached or not.
bool TryPreserveWrapper(JS::Handle<JSObject*> obj);
bool HasReleasedWrapper(JS::Handle<JSObject*> obj);
// Can only be called with a DOM JSClass.
bool InstanceClassHasProtoAtDepth(const JSClass* clasp, 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, JS::Handle<JSObject*> scope,
xpcObjectHelper& helper, const nsIID* iid,
bool allowNativeWrapper,
JS::MutableHandle<JS::Value> rval);
// Special-cased wrapping for variants
bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
JS::MutableHandle<JS::Value> aRetval);
// Wrap an object "p" which is not using WebIDL bindings yet. This _will_
// actually work on WebIDL binding objects that are wrappercached, but will be
// much slower than GetOrCreateDOMReflector. "cache" must either be null or be
// the nsWrapperCache for "p".
template <class T>
inline bool WrapObject(JSContext* cx, T* p, nsWrapperCache* cache,
const nsIID* iid, JS::MutableHandle<JS::Value> rval) {
if (xpc_FastGetCachedWrapper(cx, cache, rval)) return true;
xpcObjectHelper helper(ToSupports(p), cache);
JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
return XPCOMObjectToJsval(cx, scope, helper, iid, true, rval);
}
// A specialization of the above for nsIVariant, because that needs to
// do something different.
template <>
inline bool WrapObject<nsIVariant>(JSContext* cx, nsIVariant* p,
nsWrapperCache* cache, const nsIID* iid,
JS::MutableHandle<JS::Value> rval) {
MOZ_ASSERT(iid);
MOZ_ASSERT(iid->Equals(NS_GET_IID(nsIVariant)));
return VariantToJsval(cx, p, rval);
}
// Wrap an object "p" which is not using WebIDL bindings yet. Just like the
// variant that takes an nsWrapperCache above, but will try to auto-derive the
// nsWrapperCache* from "p".
template <class T>
inline bool WrapObject(JSContext* cx, T* p, const nsIID* iid,
JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p, GetWrapperCache(p), iid, rval);
}
// Just like the WrapObject above, but without requiring you to pick which
// interface you're wrapping as. This should only be used for objects that have
// classinfo, for which it doesn't matter what IID is used to wrap.
template <class T>
inline bool WrapObject(JSContext* cx, T* p, JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p, nullptr, rval);