gecko-dev/dom/bindings/BindingUtils.h
Tom Schuster f77a3af47a Bug 1277799 - Define @@toStringTag on all DOM interface prototype objects. r=peterv
Instead of manually defining toStringTag we now add the toStringTag symbol to the list of properties.
This is also how we usually define toStringTag in the JS engine.
Even though this changes more code I like this approach better. Everything is centralized in the generated bindings file.

Differential Revision: https://phabricator.services.mozilla.com/D72179
2020-05-05 17:54:51 +00:00

3222 lines
115 KiB
C++

/* -*- 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/MemoryFunctions.h"
#include "js/Wrapper.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Alignment.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/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/PrototypeList.h"
#include "mozilla/dom/RemoteObjectProxy.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/SegmentedVector.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/Document.h"
#include "nsIGlobalObject.h"
#include "nsIVariant.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 nsGenericHTMLElement;
namespace mozilla {
enum UseCounter : int16_t;
enum class UseCounterWorker : int16_t;
namespace dom {
class CustomElementReactionsStack;
class MessageManagerGlobal;
class DedicatedWorkerGlobalScope;
template <typename KeyType, typename ValueType>
class Record;
class WindowProxyHolder;
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->isProxy();
}
// 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::GetObjectClass(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::GetObjectClass(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::GetObjectClass(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::GetObjectClass(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::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
struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer {
bool ok;
explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSContext* cx)
: JS::CallbackTracer(cx), ok(false) {}
bool onChild(const JS::GCCellPtr&) override {
// We don't do anything here, we only want to verify that
// TraceProtoAndIfaceCache was called.
return true;
}
TracerKind getTracerKind() const override {
return TracerKind::VerifyTraceProtoAndIface;
}
};
#endif
inline void TraceProtoAndIfaceCache(JSTracer* trc, JSObject* obj) {
MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL);
#ifdef DEBUG
if (trc->isCallbackTracer() &&
(trc->asCallbackTracer()->getTracerKind() ==
JS::CallbackTracer::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::GetObjectClass(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 NamedConstructor {
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.
* 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*> interfaceProto,
const JSClass* constructorClass, unsigned ctorNargs,
const NamedConstructor* namedConstructors,
JS::Heap<JSObject*>* constructorCache,
const NativeProperties* regularProperties,
const NativeProperties* chromeOnlyProperties,
const char* name, bool defineOnGlobal,
const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases);
/**
* 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 unforgeable methods on an object.
*/
bool DefineUnforgeableMethods(JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSFunctionSpec>* props);
/*
* Define the unforgeable attributes on an object.
*/
bool DefineUnforgeableAttributes(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::GetObjectCompartment(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::GetObjectCompartment(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::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<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::GetObjectCompartment(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);
// 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);
}
// Helper to make it possible to wrap directly out of an nsCOMPtr
template <class T>
inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p, const nsIID* iid,
JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p.get(), iid, rval);
}
// Helper to make it possible to wrap directly out of an nsCOMPtr
template <class T>
inline bool WrapObject(JSContext* cx, const nsCOMPtr<T>& p,
JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p, nullptr, rval);
}
// Helper to make it possible to wrap directly out of an nsRefPtr
template <class T>
inline bool WrapObject(JSContext* cx, const RefPtr<T>& p, const nsIID* iid,
JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p.get(), iid, rval);
}
// Helper to make it possible to wrap directly out of an nsRefPtr
template <class T>
inline bool WrapObject(JSContext* cx, const RefPtr<T>& p,
JS::MutableHandle<JS::Value> rval) {
return WrapObject(cx, p, nullptr, rval);
}
// Specialization to make it easy to use WrapObject in codegen.
template <>
inline bool WrapObject<JSObject>(JSContext* cx, JSObject* p,
JS::MutableHandle<JS::Value> rval) {
rval.set(JS::ObjectOrNullValue(p));
return true;
}
inline bool WrapObject(JSContext* cx, JSObject& p,
JS::MutableHandle<JS::Value> rval) {
rval.set(JS::ObjectValue(p));
return true;
}
bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
JS::MutableHandle<JS::Value> rval);
// Given an object "p" that inherits from nsISupports, wrap it and return the
// result. Null is returned on wrapping failure. This is somewhat similar to
// WrapObject() above, but does NOT allow Xrays around the result, since we
// don't want those for our parent object.
template <typename T>
static inline JSObject* WrapNativeISupports(JSContext* cx, T* p,
nsWrapperCache* cache) {
JS::Rooted<JSObject*> retval(cx);
{
xpcObjectHelper helper(ToSupports(p), cache);
JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
JS::Rooted<JS::Value> v(cx);
retval = XPCOMObjectToJsval(cx, scope, helper, nullptr, false, &v)
? v.toObjectOrNull()
: nullptr;
}
return retval;
}
// Wrapping of our native parent, for cases when it's a WebIDL object.
template <typename T, bool hasWrapObject = NativeHasMember<T>::WrapObject>
struct WrapNativeHelper {
static inline JSObject* Wrap(JSContext* cx, T* parent,
nsWrapperCache* cache) {
MOZ_ASSERT(cache);
JSObject* obj;
if ((obj = cache->GetWrapper())) {
// GetWrapper always unmarks gray.
JS::AssertObjectIsNotGray(obj);
return obj;
}
// WrapObject never returns a gray thing.
obj = parent->WrapObject(cx, nullptr);
JS::AssertObjectIsNotGray(obj);
return obj;
}
};
// Wrapping of our native parent, for cases when it's not a WebIDL object. In
// this case it must be nsISupports.
template <typename T>
struct WrapNativeHelper<T, false> {
static inline JSObject* Wrap(JSContext* cx, T* parent,
nsWrapperCache* cache) {
JSObject* obj;
if (cache && (obj = cache->GetWrapper())) {
#ifdef DEBUG
JS::Rooted<JSObject*> rootedObj(cx, obj);
NS_ASSERTION(WrapNativeISupports(cx, parent, cache) == rootedObj,
"Unexpected object in nsWrapperCache");
obj = rootedObj;
#endif
JS::AssertObjectIsNotGray(obj);
return obj;
}
obj = WrapNativeISupports(cx, parent, cache);
JS::AssertObjectIsNotGray(obj);
return obj;
}
};
// Finding the associated global for an object.
template <typename T>
static inline JSObject* FindAssociatedGlobal(
JSContext* cx, T* p, nsWrapperCache* cache,
mozilla::dom::ReflectionScope scope =
mozilla::dom::ReflectionScope::Content) {
if (!p) {
return JS::CurrentGlobalOrNull(cx);
}
JSObject* obj = WrapNativeHelper<T>::Wrap(cx, p, cache);
if (!obj) {
return nullptr;
}
JS::AssertObjectIsNotGray(obj);
// The object is never a CCW but it may not be in the current compartment of
// the JSContext.
obj = JS::GetNonCCWObjectGlobal(obj);
switch (scope) {
case mozilla::dom::ReflectionScope::NAC: {
return xpc::NACScope(obj);
}
case mozilla::dom::ReflectionScope::UAWidget: {
// If scope is set to UAWidgetScope, it means that the canonical reflector
// for this native object should live in the UA widget scope.
if (xpc::IsInUAWidgetScope(obj)) {
return obj;
}
JS::Rooted<JSObject*> rootedObj(cx, obj);
JSObject* uaWidgetScope = xpc::GetUAWidgetScope(cx, rootedObj);
MOZ_ASSERT_IF(uaWidgetScope, JS_IsGlobalObject(uaWidgetScope));
JS::AssertObjectIsNotGray(uaWidgetScope);
return uaWidgetScope;
}
case ReflectionScope::Content:
return obj;
}
MOZ_CRASH("Unknown ReflectionScope variant");
return nullptr;
}
// Finding of the associated global for an object, when we don't want to
// explicitly pass in things like the nsWrapperCache for it.
template <typename T>
static inline JSObject* FindAssociatedGlobal(JSContext* cx, const T& p) {
return FindAssociatedGlobal(cx, GetParentPointer(p), GetWrapperCache(p),
GetReflectionScope(p));
}
// Specialization for the case of nsIGlobalObject, since in that case
// we can just get the JSObject* directly.
template <>
inline JSObject* FindAssociatedGlobal(JSContext* cx,
nsIGlobalObject* const& p) {
if (!p) {
return JS::CurrentGlobalOrNull(cx);
}
JSObject* global = p->GetGlobalJSObject();
if (!global) {
// nsIGlobalObject doesn't have a JS object anymore,
// fallback to the current global.
return JS::CurrentGlobalOrNull(cx);
}
MOZ_ASSERT(JS_IsGlobalObject(global));
JS::AssertObjectIsNotGray(global);
return global;
}
template <typename T,
bool hasAssociatedGlobal = NativeHasMember<T>::GetParentObject>
struct FindAssociatedGlobalForNative {
static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
MOZ_ASSERT(js::IsObjectInContextCompartment(obj, cx));
T* native = UnwrapDOMObject<T>(obj);
return FindAssociatedGlobal(cx, native->GetParentObject());
}
};
template <typename T>
struct FindAssociatedGlobalForNative<T, false> {
static JSObject* Get(JSContext* cx, JS::Handle<JSObject*> obj) {
MOZ_CRASH();
return nullptr;
}
};
// Helper for calling GetOrCreateDOMReflector with smart pointers
// (UniquePtr/RefPtr/nsCOMPtr) or references.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetOrCreateDOMReflectorHelper {
static inline bool GetOrCreate(JSContext* cx, const T& value,
JS::Handle<JSObject*> givenProto,
JS::MutableHandle<JS::Value> rval) {
return GetOrCreateDOMReflector(cx, value.get(), rval, givenProto);
}
};
template <class T>
struct GetOrCreateDOMReflectorHelper<T, false> {
static inline bool GetOrCreate(JSContext* cx, T& value,
JS::Handle<JSObject*> givenProto,
JS::MutableHandle<JS::Value> rval) {
static_assert(IsRefcounted<T>::value, "Don't pass owned classes in here.");
return GetOrCreateDOMReflector(cx, &value, rval, givenProto);
}
};
template <class T>
inline bool GetOrCreateDOMReflector(
JSContext* cx, T& value, JS::MutableHandle<JS::Value> rval,
JS::Handle<JSObject*> givenProto = nullptr) {
return GetOrCreateDOMReflectorHelper<T>::GetOrCreate(cx, value, givenProto,
rval);
}
// Helper for calling GetOrCreateDOMReflectorNoWrap with smart pointers
// (UniquePtr/RefPtr/nsCOMPtr) or references.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetOrCreateDOMReflectorNoWrapHelper {
static inline bool GetOrCreate(JSContext* cx, const T& value,
JS::MutableHandle<JS::Value> rval) {
return GetOrCreateDOMReflectorNoWrap(cx, value.get(), rval);
}
};
template <class T>
struct GetOrCreateDOMReflectorNoWrapHelper<T, false> {
static inline bool GetOrCreate(JSContext* cx, T& value,
JS::MutableHandle<JS::Value> rval) {
return GetOrCreateDOMReflectorNoWrap(cx, &value, rval);
}
};
template <class T>
inline bool GetOrCreateDOMReflectorNoWrap(JSContext* cx, T& value,
JS::MutableHandle<JS::Value> rval) {
return GetOrCreateDOMReflectorNoWrapHelper<T>::GetOrCreate(cx, value, rval);
}
template <class T>
inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T* aObj) {
return aObj->Callback(aCx);
}
// Helper for getting the callback JSObject* of a smart ptr around a
// CallbackObject or a reference to a CallbackObject or something like
// that.
template <class T, bool isSmartPtr = IsSmartPtr<T>::value>
struct GetCallbackFromCallbackObjectHelper {
static inline JSObject* Get(JSContext* aCx, const T& aObj) {
return GetCallbackFromCallbackObject(aCx, aObj.get());
}
};
template <class T>
struct GetCallbackFromCallbackObjectHelper<T, false> {
static inline JSObject* Get(JSContext* aCx, T& aObj) {
return GetCallbackFromCallbackObject(aCx, &aObj);
}
};
template <class T>
inline JSObject* GetCallbackFromCallbackObject(JSContext* aCx, T& aObj) {
return GetCallbackFromCallbackObjectHelper<T>::Get(aCx, aObj);
}
static inline bool AtomizeAndPinJSString(JSContext* cx, jsid& id,
const char* chars) {
if (JSString* str = ::JS_AtomizeAndPinString(cx, chars)) {
id = JS::PropertyKey::fromPinnedString(str);
return true;
}
return false;
}
bool InitIds(JSContext* cx, const NativeProperties* properties);
void GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor,
nsWrapperCache* aCache, JS::Handle<JS::Value> aIID,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aError);
template <class T>
void GetInterface(JSContext* aCx, T* aThis, JS::Handle<JS::Value> aIID,
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aError) {
GetInterfaceImpl(aCx, aThis, aThis, aIID, aRetval, aError);
}
bool ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp);
bool ThrowConstructorWithoutNew(JSContext* cx, const char* name);
// Helper for throwing an "invalid this" exception.
bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
bool aSecurityError, prototypes::ID aProtoId);
bool GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
bool* found, JS::MutableHandle<JS::Value> vp);
//
bool HasPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
JS::Handle<jsid> id, bool* has);
// Append the property names in "names" to "props". If
// shadowPrototypeProperties is false then skip properties that are also
// present on the proto chain of proxy. If shadowPrototypeProperties is true,
// then the "proxy" argument is ignored.
bool AppendNamedPropertyIds(JSContext* cx, JS::Handle<JSObject*> proxy,
nsTArray<nsString>& names,
bool shadowPrototypeProperties,
JS::MutableHandleVector<jsid> props);
enum StringificationBehavior { eStringify, eEmpty, eNull };
static inline JSString* ConvertJSValueToJSString(JSContext* cx,
JS::Handle<JS::Value> v) {
if (MOZ_LIKELY(v.isString())) {
return v.toString();
}
return JS::ToString(cx, v);
}
template <typename T>
static inline bool ConvertJSValueToString(
JSContext* cx, JS::Handle<JS::Value> v,
StringificationBehavior nullBehavior,
StringificationBehavior undefinedBehavior, T& result) {
JSString* s;
if (v.isString()) {
s = v.toString();
} else {
StringificationBehavior behavior;
if (v.isNull()) {
behavior = nullBehavior;
} else if (v.isUndefined()) {
behavior = undefinedBehavior;
} else {
behavior = eStringify;
}
if (behavior != eStringify) {
if (behavior == eEmpty) {
result.Truncate();
} else {
result.SetIsVoid(true);
}
return true;
}
s = JS::ToString(cx, v);
if (!s) {
return false;
}
}
return AssignJSString(cx, result, s);
}
template <typename T>
static inline bool ConvertJSValueToString(
JSContext* cx, JS::Handle<JS::Value> v,
const char* /* unused sourceDescription */, T& result) {
return ConvertJSValueToString(cx, v, eStringify, eStringify, result);
}
MOZ_MUST_USE bool NormalizeUSVString(nsAString& aString);
MOZ_MUST_USE bool NormalizeUSVString(
binding_detail::FakeString<char16_t>& aString);
template <typename T>
static inline bool ConvertJSValueToUSVString(
JSContext* cx, JS::Handle<JS::Value> v,
const char* /* unused sourceDescription */, T& result) {
if (!ConvertJSValueToString(cx, v, eStringify, eStringify, result)) {
return false;
}
if (!NormalizeUSVString(result)) {
JS_ReportOutOfMemory(cx);
return false;
}
return true;
}
template <typename T>
inline bool ConvertIdToString(JSContext* cx, JS::HandleId id, T& result,
bool& isSymbol) {
if (MOZ_LIKELY(JSID_IS_STRING(id))) {
if (!AssignJSString(cx, result, JSID_TO_STRING(id))) {
return false;
}
} else if (JSID_IS_SYMBOL(id)) {
isSymbol = true;
return true;
} else {
JS::RootedValue nameVal(cx, js::IdToValue(id));
if (!ConvertJSValueToString(cx, nameVal, eStringify, eStringify, result)) {
return false;
}
}
isSymbol = false;
return true;
}
bool ConvertJSValueToByteString(BindingCallContext& cx, JS::Handle<JS::Value> v,
bool nullable, const char* sourceDescription,
nsACString& result);
inline bool ConvertJSValueToByteString(BindingCallContext& cx,
JS::Handle<JS::Value> v,
const char* sourceDescription,
nsACString& result) {
return ConvertJSValueToByteString(cx, v, false, sourceDescription, result);
}
template <typename T>
void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq);
template <typename T>
void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq);
// Class used to trace sequences, with specializations for various
// sequence types.
template <typename T,
bool isDictionary = std::is_base_of<DictionaryBase, T>::value,
bool isTypedArray = std::is_base_of<AllTypedArraysBase, T>::value,
bool isOwningUnion = std::is_base_of<AllOwningUnionBase, T>::value>
class SequenceTracer {
explicit SequenceTracer() = delete; // Should never be instantiated
};
// sequence<object> or sequence<object?>
template <>
class SequenceTracer<JSObject*, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, JSObject** objp, JSObject** end) {
for (; objp != end; ++objp) {
JS::UnsafeTraceRoot(trc, objp, "sequence<object>");
}
}
};
// sequence<any>
template <>
class SequenceTracer<JS::Value, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, JS::Value* valp, JS::Value* end) {
for (; valp != end; ++valp) {
JS::UnsafeTraceRoot(trc, valp, "sequence<any>");
}
}
};
// sequence<sequence<T>>
template <typename T>
class SequenceTracer<Sequence<T>, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, Sequence<T>* seqp,
Sequence<T>* end) {
for (; seqp != end; ++seqp) {
DoTraceSequence(trc, *seqp);
}
}
};
// sequence<sequence<T>> as return value
template <typename T>
class SequenceTracer<nsTArray<T>, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, nsTArray<T>* seqp,
nsTArray<T>* end) {
for (; seqp != end; ++seqp) {
DoTraceSequence(trc, *seqp);
}
}
};
// sequence<someDictionary>
template <typename T>
class SequenceTracer<T, true, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, T* dictp, T* end) {
for (; dictp != end; ++dictp) {
dictp->TraceDictionary(trc);
}
}
};
// sequence<SomeTypedArray>
template <typename T>
class SequenceTracer<T, false, true, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
for (; arrayp != end; ++arrayp) {
arrayp->TraceSelf(trc);
}
}
};
// sequence<SomeOwningUnion>
template <typename T>
class SequenceTracer<T, false, false, true> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, T* arrayp, T* end) {
for (; arrayp != end; ++arrayp) {
arrayp->TraceUnion(trc);
}
}
};
// sequence<T?> with T? being a Nullable<T>
template <typename T>
class SequenceTracer<Nullable<T>, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, Nullable<T>* seqp,
Nullable<T>* end) {
for (; seqp != end; ++seqp) {
if (!seqp->IsNull()) {
// Pretend like we actually have a length-one sequence here so
// we can do template instantiation correctly for T.
T& val = seqp->Value();
T* ptr = &val;
SequenceTracer<T>::TraceSequence(trc, ptr, ptr + 1);
}
}
}
};
template <typename K, typename V>
void TraceRecord(JSTracer* trc, Record<K, V>& record) {
for (auto& entry : record.Entries()) {
// Act like it's a one-element sequence to leverage all that infrastructure.
SequenceTracer<V>::TraceSequence(trc, &entry.mValue, &entry.mValue + 1);
}
}
// sequence<record>
template <typename K, typename V>
class SequenceTracer<Record<K, V>, false, false, false> {
explicit SequenceTracer() = delete; // Should never be instantiated
public:
static void TraceSequence(JSTracer* trc, Record<K, V>* seqp,
Record<K, V>* end) {
for (; seqp != end; ++seqp) {
TraceRecord(trc, *seqp);
}
}
};
template <typename T>
void DoTraceSequence(JSTracer* trc, FallibleTArray<T>& seq) {
SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
seq.Elements() + seq.Length());
}
template <typename T>
void DoTraceSequence(JSTracer* trc, nsTArray<T>& seq) {
SequenceTracer<T>::TraceSequence(trc, seq.Elements(),
seq.Elements() + seq.Length());
}
// Rooter class for sequences; this is what we mostly use in the codegen
template <typename T>
class MOZ_RAII SequenceRooter final : private JS::CustomAutoRooter {
public:
template <typename CX>
SequenceRooter(const CX& cx,
FallibleTArray<T>* aSequence MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mFallibleArray(aSequence),
mSequenceType(eFallibleArray) {}
template <typename CX>
SequenceRooter(const CX& cx,
nsTArray<T>* aSequence MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mInfallibleArray(aSequence),
mSequenceType(eInfallibleArray) {}
template <typename CX>
SequenceRooter(const CX& cx, Nullable<nsTArray<T>>* aSequence
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mNullableArray(aSequence),
mSequenceType(eNullableArray) {}
private:
enum SequenceType { eInfallibleArray, eFallibleArray, eNullableArray };
virtual void trace(JSTracer* trc) override {
if (mSequenceType == eFallibleArray) {
DoTraceSequence(trc, *mFallibleArray);
} else if (mSequenceType == eInfallibleArray) {
DoTraceSequence(trc, *mInfallibleArray);
} else {
MOZ_ASSERT(mSequenceType == eNullableArray);
if (!mNullableArray->IsNull()) {
DoTraceSequence(trc, mNullableArray->Value());
}
}
}
union {
nsTArray<T>* mInfallibleArray;
FallibleTArray<T>* mFallibleArray;
Nullable<nsTArray<T>>* mNullableArray;
};
SequenceType mSequenceType;
};
// Rooter class for Record; this is what we mostly use in the codegen.
template <typename K, typename V>
class MOZ_RAII RecordRooter final : private JS::CustomAutoRooter {
public:
template <typename CX>
RecordRooter(const CX& cx,
Record<K, V>* aRecord MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mRecord(aRecord),
mRecordType(eRecord) {}
template <typename CX>
RecordRooter(const CX& cx,
Nullable<Record<K, V>>* aRecord MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
mNullableRecord(aRecord),
mRecordType(eNullableRecord) {}
private:
enum RecordType { eRecord, eNullableRecord };
virtual void trace(JSTracer* trc) override {
if (mRecordType == eRecord) {
TraceRecord(trc, *mRecord);
} else {
MOZ_ASSERT(mRecordType == eNullableRecord);
if (!mNullableRecord->IsNull()) {
TraceRecord(trc, mNullableRecord->Value());
}
}
}
union {
Record<K, V>* mRecord;
Nullable<Record<K, V>>* mNullableRecord;
};
RecordType mRecordType;
};
template <typename T>
class MOZ_RAII RootedUnion : public T, private JS::CustomAutoRooter {
public:
template <typename CX>
explicit RootedUnion(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: T(),
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) {}
virtual void trace(JSTracer* trc) override { this->TraceUnion(trc); }
};
template <typename T>
class MOZ_STACK_CLASS NullableRootedUnion : public Nullable<T>,
private JS::CustomAutoRooter {
public:
template <typename CX>
explicit NullableRootedUnion(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: Nullable<T>(),
JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) {}
virtual void trace(JSTracer* trc) override {
if (!this->IsNull()) {
this->Value().TraceUnion(trc);
}
}
};
inline bool AddStringToIDVector(JSContext* cx,
JS::MutableHandleVector<jsid> vector,
const char* name) {
return vector.growBy(1) &&
AtomizeAndPinJSString(cx, *(vector[vector.length() - 1]).address(),
name);
}
// We use one constructor JSNative to represent all DOM interface objects (so
// we can easily detect when we need to wrap them in an Xray wrapper). We store
// the real JSNative in the mNative member of a JSNativeHolder in the
// CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
// specific interface object. We also store the NativeProperties in the
// JSNativeHolder.
// Note that some interface objects are not yet a JSFunction but a normal
// JSObject with a DOMJSClass, those do not use these slots.
enum { CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT = 0 };
bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp);
// Implementation of the bits that XrayWrapper needs
/**
* This resolves operations, attributes and constants of the interfaces for obj.
*
* wrapper is the Xray JS object.
* obj is the target object of the Xray, a binding's instance object or a
* interface or interface prototype object.
*/
bool XrayResolveOwnProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::MutableHandle<JS::PropertyDescriptor> desc,
bool& cacheOnHolder);
/**
* Define a property on obj through an Xray wrapper.
*
* wrapper is the Xray JS object.
* obj is the target object of the Xray, a binding's instance object or a
* interface or interface prototype object.
* id and desc are the parameters for the property to be defined.
* result is the out-parameter indicating success (read it only if
* this returns true and also sets *done to true).
* done will be set to true if a property was set as a result of this call
* or if we want to always avoid setting this property
* (i.e. indexed properties on DOM objects)
*/
bool XrayDefineProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::Handle<JS::PropertyDescriptor> desc,
JS::ObjectOpResult& result, bool* done);
/**
* Add to props the property keys of all indexed or named properties of obj and
* operations, attributes and constants of the interfaces for obj.
*
* wrapper is the Xray JS object.
* obj is the target object of the Xray, a binding's instance object or a
* interface or interface prototype object.
* flags are JSITER_* flags.
*/
bool XrayOwnPropertyKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<JSObject*> obj, unsigned flags,
JS::MutableHandleVector<jsid> props);
/**
* Returns the prototype to use for an Xray for a DOM object, wrapped in cx's
* compartment. This always returns the prototype that would be used for a DOM
* object if we ignore any changes that might have been done to the prototype
* chain by JS, the XBL code or plugins.
*
* cx should be in the Xray's compartment.
* obj is the target object of the Xray, a binding's instance object or an
* interface or interface prototype object.
*/
inline bool XrayGetNativeProto(JSContext* cx, JS::Handle<JSObject*> obj,
JS::MutableHandle<JSObject*> protop) {
JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj));
{
JSAutoRealm ar(cx, global);
const DOMJSClass* domClass = GetDOMClass(obj);
if (domClass) {
ProtoHandleGetter protoGetter = domClass->mGetProto;
if (protoGetter) {
protop.set(protoGetter(cx));
} else {
protop.set(JS::GetRealmObjectPrototype(cx));
}
} else if (JS_ObjectIsFunction(obj)) {
MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
protop.set(JS::GetRealmFunctionPrototype(cx));
} else {
const JSClass* clasp = js::GetObjectClass(obj);
MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
ProtoGetter protoGetter =
DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mGetParentProto;
protop.set(protoGetter(cx));
}
}
return JS_WrapObject(cx, protop);
}
/**
* Get the Xray expando class to use for the given DOM object.
*/
const JSClass* XrayGetExpandoClass(JSContext* cx, JS::Handle<JSObject*> obj);
/**
* Delete a named property, if any. Return value is false if exception thrown,
* true otherwise. The caller should not do any more work after calling this
* function, because it has no way whether a deletion was performed and hence
* opresult already has state set on it. If callers ever need to change that,
* add a "bool* found" argument and change the generated DeleteNamedProperty to
* use it instead of a local variable.
*/
bool XrayDeleteNamedProperty(JSContext* cx, JS::Handle<JSObject*> wrapper,
JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
JS::ObjectOpResult& opresult);
/**
* Get the object which should be used to cache the return value of a property
* getter in the case of a [Cached] or [StoreInSlot] property. `obj` is the
* `this` value for our property getter that we're working with.
*
* This function can return null on failure to allocate the object, throwing on
* the JSContext in the process.
*
* The isXray outparam will be set to true if obj is an Xray and false
* otherwise.
*
* Note that the Slow version should only be called from
* GetCachedSlotStorageObject.
*/
JSObject* GetCachedSlotStorageObjectSlow(JSContext* cx,
JS::Handle<JSObject*> obj,
bool* isXray);
inline JSObject* GetCachedSlotStorageObject(JSContext* cx,
JS::Handle<JSObject*> obj,
bool* isXray) {
if (IsDOMObject(obj)) {
*isXray = false;
return obj;
}
return GetCachedSlotStorageObjectSlow(cx, obj, isXray);
}
extern NativePropertyHooks sEmptyNativePropertyHooks;
extern const JSClassOps sBoringInterfaceObjectClassClassOps;
extern const js::ObjectOps sInterfaceObjectClassObjectOps;
inline bool UseDOMXray(JSObject* obj) {
const JSClass* clasp = js::GetObjectClass(obj);
return IsDOMClass(clasp) || JS_IsNativeFunction(obj, Constructor) ||
IsDOMIfaceAndProtoClass(clasp);
}
inline bool IsDOMConstructor(JSObject* obj) {
if (JS_IsNativeFunction(obj, dom::Constructor)) {
// NamedConstructor, like Image
return true;
}
const JSClass* clasp = js::GetObjectClass(obj);
// Check for a DOM interface object.
return dom::IsDOMIfaceAndProtoClass(clasp) &&
dom::DOMIfaceAndProtoJSClass::FromJSClass(clasp)->mType ==
dom::eInterface;
}
#ifdef DEBUG
inline bool HasConstructor(JSObject* obj) {
return JS_IsNativeFunction(obj, Constructor) ||
js::GetObjectClass(obj)->getConstruct();
}
#endif
// Helpers for creating a const version of a type.
template <typename T>
const T& Constify(T& arg) {
return arg;
}
// Helper for turning (Owning)NonNull<T> into T&
template <typename T>
T& NonNullHelper(T& aArg) {
return aArg;
}
template <typename T>
T& NonNullHelper(NonNull<T>& aArg) {
return aArg;
}
template <typename T>
const T& NonNullHelper(const NonNull<T>& aArg) {
return aArg;
}
template <typename T>
T& NonNullHelper(OwningNonNull<T>& aArg) {
return aArg;
}
template <typename T>
const T& NonNullHelper(const OwningNonNull<T>& aArg) {
return aArg;
}
template <typename CharT>
inline void NonNullHelper(NonNull<binding_detail::FakeString<CharT>>& aArg) {
// This overload is here to make sure that we never end up applying
// NonNullHelper to a NonNull<binding_detail::FakeString>. If we
// try to, it should fail to compile, since presumably the caller will try to
// use our nonexistent return value.
}
template <typename CharT>
inline void NonNullHelper(
const NonNull<binding_detail::FakeString<CharT>>& aArg) {
// This overload is here to make sure that we never end up applying
// NonNullHelper to a NonNull<binding_detail::FakeString>. If we
// try to, it should fail to compile, since presumably the caller will try to
// use our nonexistent return value.
}
template <typename CharT>
inline void NonNullHelper(binding_detail::FakeString<CharT>& aArg) {
// This overload is here to make sure that we never end up applying
// NonNullHelper to a FakeString before we've constified it. If we
// try to, it should fail to compile, since presumably the caller will try to
// use our nonexistent return value.
}
template <typename CharT>
MOZ_ALWAYS_INLINE const nsTSubstring<CharT>& NonNullHelper(
const binding_detail::FakeString<CharT>& aArg) {
return aArg;
}
// Given a DOM reflector aObj, give its underlying DOM object a reflector in
// whatever global that underlying DOM object now thinks it should be in. If
// this is in a different compartment from aObj, aObj will become a
// cross-compatment wrapper for the new object. Otherwise, aObj will become the
// new object (via a brain transplant). If the new global is the same as the
// old global, we just keep using the same object.
//
// On entry to this method, aCx and aObj must be same-compartment.
void UpdateReflectorGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
ErrorResult& aError);
/**
* Used to implement the Symbol.hasInstance property of an interface object.
*/
bool InterfaceHasInstance(JSContext* cx, unsigned argc, JS::Value* vp);
bool InterfaceHasInstance(JSContext* cx, int prototypeID, int depth,
JS::Handle<JSObject*> instance, bool* bp);
// Used to implement the cross-context <Interface>.isInstance static method.
bool InterfaceIsInstance(JSContext* cx, unsigned argc, JS::Value* vp);
// Helper for lenient getters/setters to report to console. If this
// returns false, we couldn't even get a global.
bool ReportLenientThisUnwrappingFailure(JSContext* cx, JSObject* obj);
// Given a JSObject* that represents the chrome side of a JS-implemented WebIDL
// interface, get the nsIGlobalObject corresponding to the content side, if any.
// A false return means an exception was thrown.
bool GetContentGlobalForJSImplementedObject(BindingCallContext& cx,
JS::Handle<JSObject*> obj,
nsIGlobalObject** global);
void ConstructJSImplementation(const char* aContractId,
nsIGlobalObject* aGlobal,
JS::MutableHandle<JSObject*> aObject,
ErrorResult& aRv);
template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
nsIGlobalObject* aGlobal,
ErrorResult& aRv) {
JS::RootingContext* cx = RootingCx();
JS::Rooted<JSObject*> jsImplObj(cx);
ConstructJSImplementation(aContractId, aGlobal, &jsImplObj, aRv);
if (aRv.Failed()) {
return nullptr;
}
MOZ_RELEASE_ASSERT(!js::IsWrapper(jsImplObj));
JS::Rooted<JSObject*> jsImplGlobal(cx, JS::GetNonCCWObjectGlobal(jsImplObj));
RefPtr<T> newObj = new T(jsImplObj, jsImplGlobal, aGlobal);
return newObj.forget();
}
template <typename T>
already_AddRefed<T> ConstructJSImplementation(const char* aContractId,
const GlobalObject& aGlobal,
ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
if (!global) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
return ConstructJSImplementation<T>(aContractId, global, aRv);
}
/**
* Convert an nsCString to jsval, returning true on success.
* These functions are intended for ByteString implementations.
* As such, the string is not UTF-8 encoded. Any UTF8 strings passed to these
* methods will be mangled.
*/
bool NonVoidByteStringToJsval(JSContext* cx, const nsACString& str,
JS::MutableHandle<JS::Value> rval);
inline bool ByteStringToJsval(JSContext* cx, const nsACString& str,
JS::MutableHandle<JS::Value> rval) {
if (str.IsVoid()) {
rval.setNull();
return true;
}
return NonVoidByteStringToJsval(cx, str, rval);
}
// Convert an utf-8 encoded nsCString to jsval, returning true on success.
//
// TODO(bug 1606957): This could probably be better.
inline bool NonVoidUTF8StringToJsval(JSContext* cx, const nsACString& str,
JS::MutableHandle<JS::Value> rval) {
JSString* jsStr =
JS_NewStringCopyUTF8N(cx, {str.BeginReading(), str.Length()});
if (!jsStr) {
return false;
}
rval.setString(jsStr);
return true;
}
inline bool UTF8StringToJsval(JSContext* cx, const nsACString& str,
JS::MutableHandle<JS::Value> rval) {
if (str.IsVoid()) {
rval.setNull();
return true;
}
return NonVoidUTF8StringToJsval(cx, str, rval);
}
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct PreserveWrapperHelper {
static void PreserveWrapper(T* aObject) {
aObject->PreserveWrapper(aObject, NS_CYCLE_COLLECTION_PARTICIPANT(T));
}
};
template <class T>
struct PreserveWrapperHelper<T, true> {
static void PreserveWrapper(T* aObject) {
aObject->PreserveWrapper(reinterpret_cast<nsISupports*>(aObject));
}
};
template <class T>
void PreserveWrapper(T* aObject) {
PreserveWrapperHelper<T>::PreserveWrapper(aObject);
}
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct CastingAssertions {
static bool ToSupportsIsCorrect(T*) { return true; }
static bool ToSupportsIsOnPrimaryInheritanceChain(T*, nsWrapperCache*) {
return true;
}
};
template <class T>
struct CastingAssertions<T, true> {
static bool ToSupportsIsCorrect(T* aObject) {
return ToSupports(aObject) == reinterpret_cast<nsISupports*>(aObject);
}
static bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject,
nsWrapperCache* aCache) {
return reinterpret_cast<void*>(aObject) != aCache;
}
};
template <class T>
bool ToSupportsIsCorrect(T* aObject) {
return CastingAssertions<T>::ToSupportsIsCorrect(aObject);
}
template <class T>
bool ToSupportsIsOnPrimaryInheritanceChain(T* aObject, nsWrapperCache* aCache) {
return CastingAssertions<T>::ToSupportsIsOnPrimaryInheritanceChain(aObject,
aCache);
}
// Get the size of allocated memory to associate with a binding JSObject for a
// native object. This is supplied to the JS engine to allow it to schedule GC
// when necessary.
//
// This function supplies a default value and is overloaded for specific native
// object types.
inline size_t BindingJSObjectMallocBytes(void* aNativePtr) { return 0; }
// The BindingJSObjectCreator class is supposed to be used by a caller that
// wants to create and initialise a binding JSObject. After initialisation has
// been successfully completed it should call ForgetObject().
// The BindingJSObjectCreator object will root the JSObject until ForgetObject()
// is called on it. If the native object for the binding is refcounted it will
// also hold a strong reference to it, that reference is transferred to the
// JSObject (which holds the native in a slot) when ForgetObject() is called. If
// the BindingJSObjectCreator object is destroyed and ForgetObject() was never
// called on it then the JSObject's slot holding the native will be set to
// undefined, and for a refcounted native the strong reference will be released.
template <class T>
class MOZ_STACK_CLASS BindingJSObjectCreator {
public:
explicit BindingJSObjectCreator(JSContext* aCx) : mReflector(aCx) {}
~BindingJSObjectCreator() {
if (mReflector) {
js::SetReservedSlot(mReflector, DOM_OBJECT_SLOT, JS::UndefinedValue());
}
}
void CreateProxyObject(JSContext* aCx, const JSClass* aClass,
const DOMProxyHandler* aHandler,
JS::Handle<JSObject*> aProto, bool aLazyProto,
T* aNative, JS::Handle<JS::Value> aExpandoValue,
JS::MutableHandle<JSObject*> aReflector) {
js::ProxyOptions options;
options.setClass(aClass);
options.setLazyProto(aLazyProto);
aReflector.set(
js::NewProxyObject(aCx, aHandler, aExpandoValue, aProto, options));
if (aReflector) {
js::SetProxyReservedSlot(aReflector, DOM_OBJECT_SLOT,
JS::PrivateValue(aNative));
mNative = aNative;
mReflector = aReflector;
if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
JS::AddAssociatedMemory(aReflector, mallocBytes,
JS::MemoryUse::DOMBinding);
}
}
}
void CreateObject(JSContext* aCx, const JSClass* aClass,
JS::Handle<JSObject*> aProto, T* aNative,
JS::MutableHandle<JSObject*> aReflector) {
aReflector.set(JS_NewObjectWithGivenProto(aCx, aClass, aProto));
if (aReflector) {
js::SetReservedSlot(aReflector, DOM_OBJECT_SLOT,
JS::PrivateValue(aNative));
mNative = aNative;
mReflector = aReflector;
if (size_t mallocBytes = BindingJSObjectMallocBytes(aNative)) {
JS::AddAssociatedMemory(aReflector, mallocBytes,
JS::MemoryUse::DOMBinding);
}
}
}
void InitializationSucceeded() {
T* pointer;
mNative.forget(&pointer);
mReflector = nullptr;
}
private:
struct OwnedNative {
// Make sure the native objects inherit from NonRefcountedDOMObject so
// that we log their ctor and dtor.
static_assert(std::is_base_of<NonRefcountedDOMObject, T>::value,
"Non-refcounted objects with DOM bindings should inherit "
"from NonRefcountedDOMObject.");
OwnedNative& operator=(T* aNative) {
mNative = aNative;
return *this;
}
// This signature sucks, but it's the only one that will make a nsRefPtr
// just forget about its pointer without warning.
void forget(T** aResult) {
*aResult = mNative;
mNative = nullptr;
}
// Keep track of the pointer for use in InitializationSucceeded().
// The caller (or, after initialization succeeds, the JS object) retains
// ownership of the object.
T* mNative;
};
JS::Rooted<JSObject*> mReflector;
std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, OwnedNative> mNative;
};
template <class T>
struct DeferredFinalizerImpl {
using SmartPtr = std::conditional_t<
std::is_same_v<T, nsISupports>, nsCOMPtr<T>,
std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>>;
typedef SegmentedVector<SmartPtr> SmartPtrArray;
static_assert(
std::is_same_v<T, nsISupports> || !std::is_base_of<nsISupports, T>::value,
"nsISupports classes should all use the nsISupports instantiation");
static inline void AppendAndTake(
SegmentedVector<nsCOMPtr<nsISupports>>& smartPtrArray, nsISupports* ptr) {
smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
}
template <class U>
static inline void AppendAndTake(SegmentedVector<RefPtr<U>>& smartPtrArray,
U* ptr) {
smartPtrArray.InfallibleAppend(dont_AddRef(ptr));
}
template <class U>
static inline void AppendAndTake(SegmentedVector<UniquePtr<U>>& smartPtrArray,
U* ptr) {
smartPtrArray.InfallibleAppend(ptr);
}
static void* AppendDeferredFinalizePointer(void* aData, void* aObject) {
SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
if (!pointers) {
pointers = new SmartPtrArray();
}
AppendAndTake(*pointers, static_cast<T*>(aObject));
return pointers;
}
static bool DeferredFinalize(uint32_t aSlice, void* aData) {
MOZ_ASSERT(aSlice > 0, "nonsensical/useless call with aSlice == 0");
SmartPtrArray* pointers = static_cast<SmartPtrArray*>(aData);
uint32_t oldLen = pointers->Length();
if (oldLen < aSlice) {
aSlice = oldLen;
}
uint32_t newLen = oldLen - aSlice;
pointers->PopLastN(aSlice);
if (newLen == 0) {
delete pointers;
return true;
}
return false;
}
};
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
struct DeferredFinalizer {
static void AddForDeferredFinalization(T* aObject) {
typedef DeferredFinalizerImpl<T> Impl;
DeferredFinalize(Impl::AppendDeferredFinalizePointer,
Impl::DeferredFinalize, aObject);
}
};
template <class T>
struct DeferredFinalizer<T, true> {
static void AddForDeferredFinalization(T* aObject) {
DeferredFinalize(reinterpret_cast<nsISupports*>(aObject));
}
};
template <class T>
static void AddForDeferredFinalization(T* aObject) {
DeferredFinalizer<T>::AddForDeferredFinalization(aObject);
}
// This returns T's CC participant if it participates in CC and does not inherit
// from nsISupports. Otherwise, it returns null. QI should be used to get the
// participant if T inherits from nsISupports.
template <class T, bool isISupports = std::is_base_of<nsISupports, T>::value>
class GetCCParticipant {
// Helper for GetCCParticipant for classes that participate in CC.
template <class U>
static constexpr nsCycleCollectionParticipant* GetHelper(
int, typename U::NS_CYCLE_COLLECTION_INNERCLASS* dummy = nullptr) {
return T::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant();
}
// Helper for GetCCParticipant for classes that don't participate in CC.
template <class U>
static constexpr nsCycleCollectionParticipant* GetHelper(double) {
return nullptr;
}
public:
static constexpr nsCycleCollectionParticipant* Get() {
// Passing int() here will try to call the GetHelper that takes an int as
// its first argument. If T doesn't participate in CC then substitution for
// the second argument (with a default value) will fail and because of
// SFINAE the next best match (the variant taking a double) will be called.
return GetHelper<T>(int());
}
};
template <class T>
class GetCCParticipant<T, true> {
public:
static constexpr nsCycleCollectionParticipant* Get() { return nullptr; }
};
void FinalizeGlobal(JSFreeOp* aFop, JSObject* aObj);
bool ResolveGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
JS::Handle<jsid> aId, bool* aResolvedp);
bool MayResolveGlobal(const JSAtomState& aNames, jsid aId, JSObject* aMaybeObj);
bool EnumerateGlobal(JSContext* aCx, JS::HandleObject aObj,
JS::MutableHandleVector<jsid> aProperties,
bool aEnumerableOnly);
struct CreateGlobalOptionsGeneric {
static void TraceGlobal(JSTracer* aTrc, JSObject* aObj) {
mozilla::dom::TraceProtoAndIfaceCache(aTrc, aObj);
}
static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
MOZ_ALWAYS_TRUE(TryPreserveWrapper(aGlobal));
return true;
}
};
struct CreateGlobalOptionsWithXPConnect {
static void TraceGlobal(JSTracer* aTrc, JSObject* aObj);
static bool PostCreateGlobal(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
};
template <class T>
using IsGlobalWithXPConnect =
std::integral_constant<bool,
std::is_base_of<nsGlobalWindowInner, T>::value ||
std::is_base_of<MessageManagerGlobal, T>::value>;
template <class T>
struct CreateGlobalOptions
: std::conditional_t<IsGlobalWithXPConnect<T>::value,
CreateGlobalOptionsWithXPConnect,
CreateGlobalOptionsGeneric> {
static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
ProtoAndIfaceCache::NonWindowLike;
};
template <>
struct CreateGlobalOptions<nsGlobalWindowInner>
: public CreateGlobalOptionsWithXPConnect {
static constexpr ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
ProtoAndIfaceCache::WindowLike;
};
uint64_t GetWindowID(void* aGlobal);
uint64_t GetWindowID(nsGlobalWindowInner* aGlobal);
uint64_t GetWindowID(DedicatedWorkerGlobalScope* aGlobal);
// The return value is true if we created and successfully performed our part of
// the setup for the global, false otherwise.
//
// Typically this method's caller will want to ensure that
// xpc::InitGlobalObjectOptions is called before, and xpc::InitGlobalObject is
// called after, this method, to ensure that this global object and its
// compartment are consistent with other global objects.
template <class T, ProtoHandleGetter GetProto>
bool CreateGlobal(JSContext* aCx, T* aNative, nsWrapperCache* aCache,
const JSClass* aClass, JS::RealmOptions& aOptions,
JSPrincipals* aPrincipal, bool aInitStandardClasses,
JS::MutableHandle<JSObject*> aGlobal) {
aOptions.creationOptions()
.setTrace(CreateGlobalOptions<T>::TraceGlobal)
.setProfilerRealmID(GetWindowID(aNative));
xpc::SetPrefableRealmOptions(aOptions);
aGlobal.set(JS_NewGlobalObject(aCx, aClass, aPrincipal,
JS::DontFireOnNewGlobalHook, aOptions));
if (!aGlobal) {
NS_WARNING("Failed to create global");
return false;
}
JSAutoRealm ar(aCx, aGlobal);
{
js::SetReservedSlot(aGlobal, DOM_OBJECT_SLOT, JS::PrivateValue(aNative));
NS_ADDREF(aNative);
aCache->SetWrapper(aGlobal);
dom::AllocateProtoAndIfaceCache(
aGlobal, CreateGlobalOptions<T>::ProtoAndIfaceCacheKind);
if (!CreateGlobalOptions<T>::PostCreateGlobal(aCx, aGlobal)) {
return false;
}
}
if (aInitStandardClasses && !JS::InitRealmStandardClasses(aCx)) {
NS_WARNING("Failed to init standard classes");
return false;
}
JS::Handle<JSObject*> proto = GetProto(aCx);
if (!proto || !JS_SplicePrototype(aCx, aGlobal, proto)) {
NS_WARNING("Failed to set proto");
return false;
}
bool succeeded;
if (!JS_SetImmutablePrototype(aCx, aGlobal, &succeeded)) {
return false;
}
MOZ_ASSERT(succeeded,
"making a fresh global object's [[Prototype]] immutable can "
"internally fail, but it should never be unsuccessful");
if (!JS_DefineProfilingFunctions(aCx, aGlobal)) {
return false;
}
return true;
}
/*
* Holds a jsid that is initialized to a pinned string, with automatic
* conversion to Handle<jsid>, as it is held live forever by pinning.
*/
class PinnedStringId {
jsid id;
public:
constexpr PinnedStringId() : id(JSID_VOID) {}
bool init(JSContext* cx, const char* string) {
JSString* str = JS_AtomizeAndPinString(cx, string);
if (!str) {
return false;
}
id = JS::PropertyKey::fromPinnedString(str);
return true;
}
operator const jsid&() const { return id; }
operator JS::Handle<jsid>() const {
/* This is safe because we have pinned the string. */
return JS::Handle<jsid>::fromMarkedLocation(&id);
}
} JS_HAZ_ROOTED;
namespace binding_detail {
/**
* WebIDL getters have a "generic" JSNative that is responsible for the
* following things:
*
* 1) Determining the "this" pointer for the C++ call.
* 2) Extracting the "specialized" getter from the jitinfo on the JSFunction.
* 3) Calling the specialized getter.
* 4) Handling exceptions as needed.
*
* There are several variants of (1) depending on the interface involved and
* there are two variants of (4) depending on whether the return type is a
* Promise. We handle this by templating our generic getter on a
* this-determination policy and an exception handling policy, then explicitly
* instantiating the relevant template specializations.
*/
template <typename ThisPolicy, typename ExceptionPolicy>
bool GenericGetter(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* WebIDL setters have a "generic" JSNative that is responsible for the
* following things:
*
* 1) Determining the "this" pointer for the C++ call.
* 2) Extracting the "specialized" setter from the jitinfo on the JSFunction.
* 3) Calling the specialized setter.
*
* There are several variants of (1) depending on the interface
* involved. We handle this by templating our generic setter on a
* this-determination policy, then explicitly instantiating the
* relevant template specializations.
*/
template <typename ThisPolicy>
bool GenericSetter(JSContext* cx, unsigned argc, JS::Value* vp);
/**
* WebIDL methods have a "generic" JSNative that is responsible for the
* following things:
*
* 1) Determining the "this" pointer for the C++ call.
* 2) Extracting the "specialized" method from the jitinfo on the JSFunction.
* 3) Calling the specialized methodx.
* 4) Handling exceptions as needed.
*
* There are several variants of (1) depending on the interface involved and
* there are two variants of (4) depending on whether the return type is a
* Promise. We handle this by templating our generic method on a
* this-determination policy and an exception handling policy, then explicitly
* instantiating the relevant template specializations.
*/
template <typename ThisPolicy, typename ExceptionPolicy>
bool GenericMethod(JSContext* cx, unsigned argc, JS::Value* vp);
// A this-extraction policy for normal getters/setters/methods.
struct NormalThisPolicy;
// A this-extraction policy for getters/setters/methods on interfaces
// that are on some global's proto chain.
struct MaybeGlobalThisPolicy;
// A this-extraction policy for lenient getters/setters.
struct LenientThisPolicy;
// A this-extraction policy for cross-origin getters/setters/methods.
struct CrossOriginThisPolicy;
// A this-extraction policy for getters/setters/methods that should
// not be allowed to be called cross-origin but expect objects that
// _can_ be cross-origin.
struct MaybeCrossOriginObjectThisPolicy;
// A this-extraction policy which is just like
// MaybeCrossOriginObjectThisPolicy but has lenient-this behavior.
struct MaybeCrossOriginObjectLenientThisPolicy;
// An exception-reporting policy for normal getters/setters/methods.
struct ThrowExceptions;
// An exception-handling policy for Promise-returning getters/methods.
struct ConvertExceptionsToPromises;
} // namespace binding_detail
bool StaticMethodPromiseWrapper(JSContext* cx, unsigned argc, JS::Value* vp);
// ConvertExceptionToPromise should only be called when we have an error
// condition (e.g. returned false from a JSAPI method). Note that there may be
// no exception on cx, in which case this is an uncatchable failure that will
// simply be propagated. Otherwise this method will attempt to convert the
// exception to a Promise rejected with the exception that it will store in
// rval.
bool ConvertExceptionToPromise(JSContext* cx,
JS::MutableHandle<JS::Value> rval);
#ifdef DEBUG
void AssertReturnTypeMatchesJitinfo(const JSJitInfo* aJitinfo,
JS::Handle<JS::Value> aValue);
#endif
bool CallerSubsumes(JSObject* aObject);
MOZ_ALWAYS_INLINE bool CallerSubsumes(JS::Handle<JS::Value> aValue) {
if (!aValue.isObject()) {
return true;
}
return CallerSubsumes(&aValue.toObject());
}
template <class T, class S>
inline RefPtr<T> StrongOrRawPtr(already_AddRefed<S>&& aPtr) {
return std::move(aPtr);
}
template <class T, class S>
inline RefPtr<T> StrongOrRawPtr(RefPtr<S>&& aPtr) {
return std::move(aPtr);
}
template <class T, class ReturnType = std::conditional_t<IsRefcounted<T>::value,
T*, UniquePtr<T>>>
inline ReturnType StrongOrRawPtr(T* aPtr) {
return ReturnType(aPtr);
}
template <class T, template <typename> class SmartPtr, class S>
inline void StrongOrRawPtr(SmartPtr<S>&& aPtr) = delete;
template <class T>
using StrongPtrForMember =
std::conditional_t<IsRefcounted<T>::value, RefPtr<T>, UniquePtr<T>>;
namespace binding_detail {
inline JSObject* GetHackedNamespaceProtoObject(JSContext* aCx) {
return JS_NewPlainObject(aCx);
}
} // namespace binding_detail
// Resolve an id on the given global object that wants to be included in
// Exposed=System webidl annotations. False return value means exception
// thrown.
bool SystemGlobalResolve(JSContext* cx, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, bool* resolvedp);
// Enumerate all ids on the given global object that wants to be included in
// Exposed=System webidl annotations. False return value means exception
// thrown.
bool SystemGlobalEnumerate(JSContext* cx, JS::Handle<JSObject*> obj);
// Slot indexes for maplike/setlike forEach functions
#define FOREACH_CALLBACK_SLOT 0
#define FOREACH_MAPLIKEORSETLIKEOBJ_SLOT 1
// Backing function for running .forEach() on maplike/setlike interfaces.
// Unpacks callback and maplike/setlike object from reserved slots, then runs
// callback for each key (and value, for maplikes)
bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp);
// Unpacks backing object (ES6 map/set) from the reserved slot of a reflector
// for a maplike/setlike interface. If backing object does not exist, creates
// backing object in the compartment of the reflector involved, making this safe
// to use across compartments/via xrays. Return values of these methods will
// always be in the context compartment.
bool GetMaplikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated);
bool GetSetlikeBackingObject(JSContext* aCx, JS::Handle<JSObject*> aObj,
size_t aSlotIndex,
JS::MutableHandle<JSObject*> aBackingObj,
bool* aBackingObjCreated);
// Get the desired prototype object for an object construction from the given
// CallArgs. The CallArgs must be for a constructor call. The
// aProtoId/aCreator arguments are used to get a default if we don't find a
// prototype on the newTarget of the callargs.
bool GetDesiredProto(JSContext* aCx, const JS::CallArgs& aCallArgs,
prototypes::id::ID aProtoId,
CreateInterfaceObjectsMethod aCreator,
JS::MutableHandle<JSObject*> aDesiredProto);
// This function is expected to be called from the constructor function for an
// HTML or XUL element interface; the global/callargs need to be whatever was
// passed to that constructor function.
already_AddRefed<Element> CreateXULOrHTMLElement(
const GlobalObject& aGlobal, const JS::CallArgs& aCallArgs,
JS::Handle<JSObject*> aGivenProto, ErrorResult& aRv);
void SetUseCounter(JSObject* aObject, UseCounter aUseCounter);
void SetUseCounter(UseCounterWorker aUseCounter);
// Warnings
void DeprecationWarning(JSContext* aCx, JSObject* aObject,
Document::DeprecatedOperations aOperation);
void DeprecationWarning(const GlobalObject& aGlobal,
Document::DeprecatedOperations aOperation);
// A callback to perform funToString on an interface object
JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
unsigned /* indent */);
namespace binding_detail {
// Get a JS global object that can be used for some temporary allocations. The
// idea is that this should be used for situations when you need to operate in
// _some_ compartment but don't care which one. A typical example is when you
// have non-JS input, non-JS output, but have to go through some sort of JS
// representation in the middle, so need a compartment to allocate things in.
//
// It's VERY important that any consumers of this function only do things that
// are guaranteed to be side-effect-free, even in the face of a script
// environment controlled by a hostile adversary. This is because in the worker
// case the global is in fact the worker global, so it and its standard objects
// are controlled by the worker script. This is why this function is in the
// binding_detail namespace. Any use of this function MUST be very carefully
// reviewed by someone who is sufficiently devious and has a very good
// understanding of all the code that will run while we're using the return
// value, including the SpiderMonkey parts.
JSObject* UnprivilegedJunkScopeOrWorkerGlobal();
// Implementation of the [HTMLConstructor] extended attribute.
bool HTMLConstructor(JSContext* aCx, unsigned aArgc, JS::Value* aVp,
constructors::id::ID aConstructorId,
prototypes::id::ID aProtoId,
CreateInterfaceObjectsMethod aCreator);
// A method to test whether an attribute with the given JSJitGetterOp getter is
// enabled in the given set of prefable proeprty specs. For use for toJSON
// conversions. aObj is the object that would be used as the "this" value.
bool IsGetterEnabled(JSContext* aCx, JS::Handle<JSObject*> aObj,
JSJitGetterOp aGetter,
const Prefable<const JSPropertySpec>* aAttributes);
// A class that can be used to examine the chars of a linear string.
class StringIdChars {
public:
// Require a non-const ref to an AutoRequireNoGC to prevent callers
// from passing temporaries.
StringIdChars(JS::AutoRequireNoGC& nogc, JSLinearString* str) {
mIsLatin1 = js::LinearStringHasLatin1Chars(str);
if (mIsLatin1) {
mLatin1Chars = js::GetLatin1LinearStringChars(nogc, str);
} else {
mTwoByteChars = js::GetTwoByteLinearStringChars(nogc, str);
}
#ifdef DEBUG
mLength = js::GetLinearStringLength(str);
#endif // DEBUG
}
MOZ_ALWAYS_INLINE char16_t operator[](size_t index) {
MOZ_ASSERT(index < mLength);
if (mIsLatin1) {
return mLatin1Chars[index];
}
return mTwoByteChars[index];
}
private:
bool mIsLatin1;
union {
const JS::Latin1Char* mLatin1Chars;
const char16_t* mTwoByteChars;
};
#ifdef DEBUG
size_t mLength;
#endif // DEBUG
};
} // namespace binding_detail
} // namespace dom
} // namespace mozilla
#endif /* mozilla_dom_BindingUtils_h__ */