mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
49d77af36e
Differential Revision: https://phabricator.services.mozilla.com/D89332
3184 lines
114 KiB
C++
3184 lines
114 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/experimental/JitInfo.h" // JSJitGetterOp, JSJitInfo
|
|
#include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowProxyIfWindow
|
|
#include "js/MemoryFunctions.h"
|
|
#include "js/String.h" // JS::GetLatin1LinearStringChars, JS::GetTwoByteLinearStringChars, JS::GetLinearStringLength, JS::LinearStringHasLatin1Chars, JS::StringHasLatin1Chars
|
|
#include "js/Wrapper.h"
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Array.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/DeferredFinalize.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/dom/BindingCallContext.h"
|
|
#include "mozilla/dom/BindingDeclarations.h"
|
|
#include "mozilla/dom/DOMJSClass.h"
|
|
#include "mozilla/dom/DOMJSProxyHandler.h"
|
|
#include "mozilla/dom/NonRefcountedDOMObject.h"
|
|
#include "mozilla/dom/Nullable.h"
|
|
#include "mozilla/dom/PrototypeList.h"
|
|
#include "mozilla/dom/RemoteObjectProxy.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 "nsJSUtils.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "xpcObjectHelper.h"
|
|
#include "xpcpublic.h"
|
|
#include "nsIVariant.h"
|
|
#include "mozilla/dom/FakeString.h"
|
|
|
|
#include "nsWrapperCacheInlines.h"
|
|
|
|
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);
|
|
|
|
bool HasReleasedWrapper(JS::Handle<JSObject*> obj);
|
|
|
|
// Can only be called with a DOM JSClass.
|
|
bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
|
|
uint32_t depth);
|
|
|
|
// Only set allowNativeWrapper to false if you really know you need it; if in
|
|
// doubt use true. Setting it to false disables security wrappers.
|
|
bool XPCOMObjectToJsval(JSContext* cx, JS::Handle<JSObject*> scope,
|
|
xpcObjectHelper& helper, const nsIID* iid,
|
|
bool allowNativeWrapper,
|
|
JS::MutableHandle<JS::Value> rval);
|
|
|
|
// Special-cased wrapping for variants
|
|
bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
|
|
JS::MutableHandle<JS::Value> aRetval);
|
|
|
|
// Wrap an object "p" which is not using WebIDL bindings yet. This _will_
|
|
// actually work on WebIDL binding objects that are wrappercached, but will be
|
|
// much slower than GetOrCreateDOMReflector. "cache" must either be null or be
|
|
// the nsWrapperCache for "p".
|
|
template <class T>
|
|
inline bool WrapObject(JSContext* cx, T* p, nsWrapperCache* cache,
|
|
const nsIID* iid, JS::MutableHandle<JS::Value> rval) {
|
|
if (xpc_FastGetCachedWrapper(cx, cache, rval)) return true;
|
|
xpcObjectHelper helper(ToSupports(p), cache);
|
|
JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
|
|
return XPCOMObjectToJsval(cx, scope, helper, iid, true, rval);
|
|
}
|
|
|
|
// A specialization of the above for nsIVariant, because that needs to
|
|
// do something different.
|
|
template <>
|
|
inline bool WrapObject<nsIVariant>(JSContext* cx, nsIVariant* p,
|
|
nsWrapperCache* cache, const nsIID* iid,
|
|
JS::MutableHandle<JS::Value> rval) {
|
|
MOZ_ASSERT(iid);
|
|
MOZ_ASSERT(iid->Equals(NS_GET_IID(nsIVariant)));
|
|
return VariantToJsval(cx, p, rval);
|
|
}
|
|
|
|
// Wrap an object "p" which is not using WebIDL bindings yet. Just like the
|
|
// variant that takes an nsWrapperCache above, but will try to auto-derive the
|
|
// nsWrapperCache* from "p".
|
|
template <class T>
|
|
inline bool WrapObject(JSContext* cx, T* p, const nsIID* iid,
|
|
JS::MutableHandle<JS::Value> rval) {
|
|
return WrapObject(cx, p, GetWrapperCache(p), iid, rval);
|
|
}
|
|
|
|
// Just like the WrapObject above, but without requiring you to pick which
|
|
// interface you're wrapping as. This should only be used for objects that have
|
|
// classinfo, for which it doesn't matter what IID is used to wrap.
|
|
template <class T>
|
|
inline bool WrapObject(JSContext* cx, T* p, JS::MutableHandle<JS::Value> rval) {
|
|
return WrapObject(cx, p, nullptr, rval);
|
|
}
|
|
|
|
// 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)
|
|
: JS::CustomAutoRooter(cx),
|
|
mFallibleArray(aSequence),
|
|
mSequenceType(eFallibleArray) {}
|
|
|
|
template <typename CX>
|
|
SequenceRooter(const CX& cx, nsTArray<T>* aSequence)
|
|
: JS::CustomAutoRooter(cx),
|
|
mInfallibleArray(aSequence),
|
|
mSequenceType(eInfallibleArray) {}
|
|
|
|
template <typename CX>
|
|
SequenceRooter(const CX& cx, Nullable<nsTArray<T>>* aSequence)
|
|
: JS::CustomAutoRooter(cx),
|
|
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)
|
|
: JS::CustomAutoRooter(cx), mRecord(aRecord), mRecordType(eRecord) {}
|
|
|
|
template <typename CX>
|
|
RecordRooter(const CX& cx, Nullable<Record<K, V>>* aRecord)
|
|
: JS::CustomAutoRooter(cx),
|
|
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) : T(), JS::CustomAutoRooter(cx) {}
|
|
|
|
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)
|
|
: Nullable<T>(), JS::CustomAutoRooter(cx) {}
|
|
|
|
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;
|
|
}
|
|
|
|
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();
|
|
JSObject* UnprivilegedJunkScopeOrWorkerGlobal(const fallible_t&);
|
|
|
|
// 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__ */
|