Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BindingUtils.h"
#include <algorithm>
#include <stdarg.h>
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Encoding.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/UseCounter.h"
#include "AccessCheck.h"
#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable
#include "js/experimental/JitInfo.h" // JSJit{Getter,Setter,Method}CallArgs, JSJit{Getter,Setter}Op, JSJitInfo
#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
#include "js/Id.h"
#include "js/JSON.h"
#include "js/MapAndSet.h"
#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetReservedSlot, JS::SetReservedSlot
#include "js/PropertyAndElement.h" // JS_AlreadyHasOwnPropertyById, JS_DefineFunction, JS_DefineFunctionById, JS_DefineFunctions, JS_DefineProperties, JS_DefineProperty, JS_DefinePropertyById, JS_ForwardGetPropertyTo, JS_GetProperty, JS_HasProperty, JS_HasPropertyById
#include "js/StableStringChars.h"
#include "js/String.h" // JS::GetStringLength, JS::MaxStringLength, JS::StringHasLatin1Chars
#include "js/Symbol.h"
#include "jsfriendapi.h"
#include "nsContentCreatorFunctions.h"
#include "nsContentUtils.h"
#include "nsGlobalWindow.h"
#include "nsHTMLTags.h"
#include "nsIDOMGlobalPropertyInitializer.h"
#include "nsINode.h"
#include "nsIOService.h"
#include "nsIPrincipal.h"
#include "nsIXPConnect.h"
#include "nsUTF8Utils.h"
#include "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WrapperFactory.h"
#include "xpcprivate.h"
#include "XrayWrapper.h"
#include "nsPrintfCString.h"
#include "mozilla/Sprintf.h"
#include "nsReadableUtils.h"
#include "nsWrapperCacheInlines.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DeprecationReportBody.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/ElementBinding.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/HTMLObjectElement.h"
#include "mozilla/dom/HTMLObjectElementBinding.h"
#include "mozilla/dom/HTMLEmbedElement.h"
#include "mozilla/dom/HTMLElementBinding.h"
#include "mozilla/dom/HTMLEmbedElementBinding.h"
#include "mozilla/dom/MaybeCrossOriginObject.h"
#include "mozilla/dom/ReportingUtils.h"
#include "mozilla/dom/XULElementBinding.h"
#include "mozilla/dom/XULFrameElementBinding.h"
#include "mozilla/dom/XULMenuElementBinding.h"
#include "mozilla/dom/XULPopupElementBinding.h"
#include "mozilla/dom/XULTextElementBinding.h"
#include "mozilla/dom/XULTreeElementBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/XrayExpandoClass.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "ipc/ErrorIPCUtils.h"
#include "mozilla/UseCounter.h"
#include "mozilla/dom/DocGroup.h"
#include "nsXULElement.h"
namespace mozilla {
namespace dom {
// Forward declare GetConstructorObject methods.
#define HTML_TAG(_tag, _classname, _interfacename) \
namespace HTML##_interfacename##Element_Binding { \
JSObject* GetConstructorObject(JSContext*); \
}
#define HTML_OTHER(_tag)
#include "nsHTMLTagList.h"
#undef HTML_TAG
#undef HTML_OTHER
using constructorGetterCallback = JSObject* (*)(JSContext*);
// Mapping of html tag and GetConstructorObject methods.
#define HTML_TAG(_tag, _classname, _interfacename) \
HTML##_interfacename##Element_Binding::GetConstructorObject,
#define HTML_OTHER(_tag) nullptr,
// We use eHTMLTag_foo (where foo is the tag) which is defined in nsHTMLTags.h
// to index into this array.
static const constructorGetterCallback sConstructorGetterCallback[] = {
HTMLUnknownElement_Binding::GetConstructorObject,
#include "nsHTMLTagList.h"
#undef HTML_TAG
#undef HTML_OTHER
};
static const JSErrorFormatString ErrorFormatString[] = {
#define MSG_DEF(_name, _argc, _has_context, _exn, _str) \
{#_name, _str, _argc, _exn},
#include "mozilla/dom/Errors.msg"
#undef MSG_DEF
};
#define MSG_DEF(_name, _argc, _has_context, _exn, _str) \
static_assert( \
(_argc) < JS::MaxNumErrorArguments, #_name \
" must only have as many error arguments as the JS engine can support");
#include "mozilla/dom/Errors.msg"
#undef MSG_DEF
static const JSErrorFormatString* GetErrorMessage(void* aUserRef,
const unsigned aErrorNumber) {
MOZ_ASSERT(aErrorNumber < ArrayLength(ErrorFormatString));
return &ErrorFormatString[aErrorNumber];
}
uint16_t GetErrorArgCount(const ErrNum aErrorNumber) {
return GetErrorMessage(nullptr, aErrorNumber)->argCount;
}
// aErrorNumber needs to be unsigned, not an ErrNum, because the latter makes
// va_start have undefined behavior, and we do not want undefined behavior.
void binding_detail::ThrowErrorMessage(JSContext* aCx,
const unsigned aErrorNumber, ...) {
va_list ap;
va_start(ap, aErrorNumber);
if (!ErrorFormatHasContext[aErrorNumber]) {
JS_ReportErrorNumberUTF8VA(aCx, GetErrorMessage, nullptr, aErrorNumber, ap);
va_end(ap);
return;
}
// Our first arg is the context arg. We want to replace nullptr with empty
// string, leave empty string alone, and for anything else append ": " to the
// end. See also the behavior of
// TErrorResult::SetPendingExceptionWithMessage, which this is mirroring for
// exceptions that are thrown directly, not via an ErrorResult.
const char* args[JS::MaxNumErrorArguments + 1];
size_t argCount = GetErrorArgCount(static_cast<ErrNum>(aErrorNumber));
MOZ_ASSERT(argCount > 0, "We have a context arg!");
nsAutoCString firstArg;
for (size_t i = 0; i < argCount; ++i) {
args[i] = va_arg(ap, const char*);
if (i == 0) {
if (args[0] && *args[0]) {
firstArg.Append(args[0]);
firstArg.AppendLiteral(": ");
}
args[0] = firstArg.get();
}
}
JS_ReportErrorNumberUTF8Array(aCx, GetErrorMessage, nullptr, aErrorNumber,
args);
va_end(ap);
}
static bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
bool aSecurityError, const char* aInterfaceName) {
NS_ConvertASCIItoUTF16 ifaceName(aInterfaceName);
// This should only be called for DOM methods/getters/setters, which
// are JSNative-backed functions, so we can assume that
// JS_ValueToFunction and JS_GetFunctionDisplayId will both return
// non-null and that JS_GetStringCharsZ returns non-null.
JS::Rooted<JSFunction*> func(aCx, JS_ValueToFunction(aCx, aArgs.calleev()));
MOZ_ASSERT(func);
JS::Rooted<JSString*> funcName(aCx, JS_GetFunctionDisplayId(func));
MOZ_ASSERT(funcName);
nsAutoJSString funcNameStr;
if (!funcNameStr.init(aCx, funcName)) {
return false;
}
if (aSecurityError) {
return Throw(aCx, NS_ERROR_DOM_SECURITY_ERR,
nsPrintfCString("Permission to call '%s' denied.",
NS_ConvertUTF16toUTF8(funcNameStr).get()));
}
const ErrNum errorNumber = MSG_METHOD_THIS_DOES_NOT_IMPLEMENT_INTERFACE;
MOZ_RELEASE_ASSERT(GetErrorArgCount(errorNumber) == 2);
JS_ReportErrorNumberUC(aCx, GetErrorMessage, nullptr,
static_cast<unsigned>(errorNumber), funcNameStr.get(),
ifaceName.get());
return false;
}
bool ThrowInvalidThis(JSContext* aCx, const JS::CallArgs& aArgs,
bool aSecurityError, prototypes::ID aProtoId) {
return ThrowInvalidThis(aCx, aArgs, aSecurityError,
NamesOfInterfacesWithProtos(aProtoId));
}
bool ThrowNoSetterArg(JSContext* aCx, const JS::CallArgs& aArgs,
prototypes::ID aProtoId) {
nsPrintfCString errorMessage("%s attribute setter",
NamesOfInterfacesWithProtos(aProtoId));
return aArgs.requireAtLeast(aCx, errorMessage.get(), 1);
}
} // namespace dom
namespace binding_danger {
template <typename CleanupPolicy>
struct TErrorResult<CleanupPolicy>::Message {
Message() : mErrorNumber(dom::Err_Limit) {
MOZ_COUNT_CTOR(TErrorResult::Message);
}
~Message() { MOZ_COUNT_DTOR(TErrorResult::Message); }
// UTF-8 strings (probably ASCII in most cases) in mArgs.
nsTArray<nsCString> mArgs;
dom::ErrNum mErrorNumber;
bool HasCorrectNumberOfArguments() {
return GetErrorArgCount(mErrorNumber) == mArgs.Length();
}
bool operator==(const TErrorResult<CleanupPolicy>::Message& aRight) const {
return mErrorNumber == aRight.mErrorNumber && mArgs == aRight.mArgs;
}
};
template <typename CleanupPolicy>
nsTArray<nsCString>& TErrorResult<CleanupPolicy>::CreateErrorMessageHelper(
const dom::ErrNum errorNumber, nsresult errorType) {
AssertInOwningThread();
mResult = errorType;
Message* message = InitMessage(new Message());
message->mErrorNumber = errorNumber;
return message->mArgs;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SerializeMessage(IPC::Message* aMsg) const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mExtra.mMessage);
WriteParam(aMsg, mExtra.mMessage->mArgs);
WriteParam(aMsg, mExtra.mMessage->mErrorNumber);
}
template <typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeMessage(const IPC::Message* aMsg,
PickleIterator* aIter) {
using namespace IPC;
AssertInOwningThread();
auto readMessage = MakeUnique<Message>();
if (!ReadParam(aMsg, aIter, &readMessage->mArgs) ||
!ReadParam(aMsg, aIter, &readMessage->mErrorNumber)) {
return false;
}
if (!readMessage->HasCorrectNumberOfArguments()) {
return false;
}
MOZ_ASSERT(mUnionState == HasNothing);
InitMessage(readMessage.release());
#ifdef DEBUG
mUnionState = HasMessage;
#endif // DEBUG
return true;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingExceptionWithMessage(
JSContext* aCx, const char* context) {
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mExtra.mMessage,
"SetPendingExceptionWithMessage() can be called only once");
Message* message = mExtra.mMessage;
MOZ_RELEASE_ASSERT(message->HasCorrectNumberOfArguments());
if (dom::ErrorFormatHasContext[message->mErrorNumber]) {
MOZ_ASSERT(!message->mArgs.IsEmpty(), "How could we have no args here?");
MOZ_ASSERT(message->mArgs[0].IsEmpty(), "Context should not be set yet!");
if (context) {
// Prepend our context and ": "; see API documentation.
message->mArgs[0].AssignASCII(context);
message->mArgs[0].AppendLiteral(": ");
}
}
const uint32_t argCount = message->mArgs.Length();
const char* args[JS::MaxNumErrorArguments + 1];
for (uint32_t i = 0; i < argCount; ++i) {
args[i] = message->mArgs.ElementAt(i).get();
}
args[argCount] = nullptr;
JS_ReportErrorNumberUTF8Array(aCx, dom::GetErrorMessage, nullptr,
static_cast<unsigned>(message->mErrorNumber),
argCount > 0 ? args : nullptr);
ClearMessage();
mResult = NS_OK;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::ClearMessage() {
AssertInOwningThread();
MOZ_ASSERT(IsErrorWithMessage());
MOZ_ASSERT(mUnionState == HasMessage);
delete mExtra.mMessage;
mExtra.mMessage = nullptr;
#ifdef DEBUG
mUnionState = HasNothing;
#endif // DEBUG
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::ThrowJSException(JSContext* cx,
JS::Handle<JS::Value> exn) {
AssertInOwningThread();
MOZ_ASSERT(mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to throw a JS exception?");
ClearUnionData();
// Make sure mExtra.mJSException is initialized _before_ we try to root it.
// But don't set it to exn yet, because we don't want to do that until after
// we root.
JS::Value& exc = InitJSException();
if (!js::AddRawValueRoot(cx, &exc, "TErrorResult::mExtra::mJSException")) {
// Don't use NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION, because that
// indicates we have in fact rooted mExtra.mJSException.
mResult = NS_ERROR_OUT_OF_MEMORY;
} else {
exc = exn;
mResult = NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION;
#ifdef DEBUG
mUnionState = HasJSException;
#endif // DEBUG
}
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingJSException(JSContext* cx) {
AssertInOwningThread();
MOZ_ASSERT(!mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to handle JS exceptions?");
MOZ_ASSERT(mUnionState == HasJSException);
JS::Rooted<JS::Value> exception(cx, mExtra.mJSException);
if (JS_WrapValue(cx, &exception)) {
JS_SetPendingException(cx, exception);
}
mExtra.mJSException = exception;
// If JS_WrapValue failed, not much we can do about it... No matter
// what, go ahead and unroot mExtra.mJSException.
js::RemoveRawValueRoot(cx, &mExtra.mJSException);
mResult = NS_OK;
#ifdef DEBUG
mUnionState = HasNothing;
#endif // DEBUG
}
template <typename CleanupPolicy>
struct TErrorResult<CleanupPolicy>::DOMExceptionInfo {
DOMExceptionInfo(nsresult rv, const nsACString& message)
: mMessage(message), mRv(rv) {}
nsCString mMessage;
nsresult mRv;
bool operator==(
const TErrorResult<CleanupPolicy>::DOMExceptionInfo& aRight) const {
return mRv == aRight.mRv && mMessage == aRight.mMessage;
}
};
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SerializeDOMExceptionInfo(
IPC::Message* aMsg) const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
MOZ_ASSERT(mExtra.mDOMExceptionInfo);
WriteParam(aMsg, mExtra.mDOMExceptionInfo->mMessage);
WriteParam(aMsg, mExtra.mDOMExceptionInfo->mRv);
}
template <typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(
const IPC::Message* aMsg, PickleIterator* aIter) {
using namespace IPC;
AssertInOwningThread();
nsCString message;
nsresult rv;
if (!ReadParam(aMsg, aIter, &message) || !ReadParam(aMsg, aIter, &rv)) {
return false;
}
MOZ_ASSERT(mUnionState == HasNothing);
MOZ_ASSERT(IsDOMException());
InitDOMExceptionInfo(new DOMExceptionInfo(rv, message));
#ifdef DEBUG
mUnionState = HasDOMExceptionInfo;
#endif // DEBUG
return true;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::ThrowDOMException(nsresult rv,
const nsACString& message) {
AssertInOwningThread();
ClearUnionData();
mResult = NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION;
InitDOMExceptionInfo(new DOMExceptionInfo(rv, message));
#ifdef DEBUG
mUnionState = HasDOMExceptionInfo;
#endif
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingDOMException(JSContext* cx,
const char* context) {
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
MOZ_ASSERT(mExtra.mDOMExceptionInfo,
"SetPendingDOMException() can be called only once");
if (context && !mExtra.mDOMExceptionInfo->mMessage.IsEmpty()) {
// Prepend our context and ": "; see API documentation.
nsAutoCString prefix(context);
prefix.AppendLiteral(": ");
mExtra.mDOMExceptionInfo->mMessage.Insert(prefix, 0);
}
dom::Throw(cx, mExtra.mDOMExceptionInfo->mRv,
mExtra.mDOMExceptionInfo->mMessage);
ClearDOMExceptionInfo();
mResult = NS_OK;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::ClearDOMExceptionInfo() {
AssertInOwningThread();
MOZ_ASSERT(IsDOMException());
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
delete mExtra.mDOMExceptionInfo;
mExtra.mDOMExceptionInfo = nullptr;
#ifdef DEBUG
mUnionState = HasNothing;
#endif // DEBUG
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::ClearUnionData() {
AssertInOwningThread();
if (IsJSException()) {
JSContext* cx = dom::danger::GetJSContext();
MOZ_ASSERT(cx);
mExtra.mJSException.setUndefined();
js::RemoveRawValueRoot(cx, &mExtra.mJSException);
#ifdef DEBUG
mUnionState = HasNothing;
#endif // DEBUG
} else if (IsErrorWithMessage()) {
ClearMessage();
} else if (IsDOMException()) {
ClearDOMExceptionInfo();
}
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingGenericErrorException(
JSContext* cx) {
AssertInOwningThread();
MOZ_ASSERT(!IsErrorWithMessage());
MOZ_ASSERT(!IsJSException());
MOZ_ASSERT(!IsDOMException());
dom::Throw(cx, ErrorCode());
mResult = NS_OK;
}
template <typename CleanupPolicy>
TErrorResult<CleanupPolicy>& TErrorResult<CleanupPolicy>::operator=(
TErrorResult<CleanupPolicy>&& aRHS) {
AssertInOwningThread();
aRHS.AssertInOwningThread();
// Clear out any union members we may have right now, before we
// start writing to it.
ClearUnionData();
#ifdef DEBUG
mMightHaveUnreportedJSException = aRHS.mMightHaveUnreportedJSException;
aRHS.mMightHaveUnreportedJSException = false;
#endif
if (aRHS.IsErrorWithMessage()) {
InitMessage(aRHS.mExtra.mMessage);
aRHS.mExtra.mMessage = nullptr;
} else if (aRHS.IsJSException()) {
JSContext* cx = dom::danger::GetJSContext();
MOZ_ASSERT(cx);
JS::Value& exn = InitJSException();
if (!js::AddRawValueRoot(cx, &exn, "TErrorResult::mExtra::mJSException")) {
MOZ_CRASH("Could not root mExtra.mJSException, we're about to OOM");
}
mExtra.mJSException = aRHS.mExtra.mJSException;
aRHS.mExtra.mJSException.setUndefined();
js::RemoveRawValueRoot(cx, &aRHS.mExtra.mJSException);
} else if (aRHS.IsDOMException()) {
InitDOMExceptionInfo(aRHS.mExtra.mDOMExceptionInfo);
aRHS.mExtra.mDOMExceptionInfo = nullptr;
} else {
// Null out the union on both sides for hygiene purposes. This is purely
// precautionary, so InitMessage/placement-new is unnecessary.
mExtra.mMessage = aRHS.mExtra.mMessage = nullptr;
}
#ifdef DEBUG
mUnionState = aRHS.mUnionState;
aRHS.mUnionState = HasNothing;
#endif // DEBUG
// Note: It's important to do this last, since this affects the condition
// checks above!
mResult = aRHS.mResult;
aRHS.mResult = NS_OK;
return *this;
}
template <typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::operator==(const ErrorResult& aRight) const {
auto right = reinterpret_cast<const TErrorResult<CleanupPolicy>*>(&aRight);
if (mResult != right->mResult) {
return false;
}
if (IsJSException()) {
// js exceptions are always non-equal
return false;
}
if (IsErrorWithMessage()) {
return *mExtra.mMessage == *right->mExtra.mMessage;
}
if (IsDOMException()) {
return *mExtra.mDOMExceptionInfo == *right->mExtra.mDOMExceptionInfo;
}
return true;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::CloneTo(TErrorResult& aRv) const {
AssertInOwningThread();
aRv.AssertInOwningThread();
aRv.ClearUnionData();
aRv.mResult = mResult;
#ifdef DEBUG
aRv.mMightHaveUnreportedJSException = mMightHaveUnreportedJSException;
#endif
if (IsErrorWithMessage()) {
#ifdef DEBUG
aRv.mUnionState = HasMessage;
#endif
Message* message = aRv.InitMessage(new Message());
message->mArgs = mExtra.mMessage->mArgs.Clone();
message->mErrorNumber = mExtra.mMessage->mErrorNumber;
} else if (IsDOMException()) {
#ifdef DEBUG
aRv.mUnionState = HasDOMExceptionInfo;
#endif
auto* exnInfo = new DOMExceptionInfo(mExtra.mDOMExceptionInfo->mRv,
mExtra.mDOMExceptionInfo->mMessage);
aRv.InitDOMExceptionInfo(exnInfo);
} else if (IsJSException()) {
#ifdef DEBUG
aRv.mUnionState = HasJSException;
#endif
JSContext* cx = dom::danger::GetJSContext();
JS::Rooted<JS::Value> exception(cx, mExtra.mJSException);
aRv.ThrowJSException(cx, exception);
}
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SuppressException() {
AssertInOwningThread();
WouldReportJSException();
ClearUnionData();
// We don't use AssignErrorCode, because we want to override existing error
// states, which AssignErrorCode is not allowed to do.
mResult = NS_OK;
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::SetPendingException(JSContext* cx,
const char* context) {
AssertInOwningThread();
if (IsUncatchableException()) {
// Nuke any existing exception on cx, to make sure we're uncatchable.
JS_ClearPendingException(cx);
// Don't do any reporting. Just return, to create an
// uncatchable exception.
mResult = NS_OK;
return;
}
if (IsJSContextException()) {
// Whatever we need to throw is on the JSContext already.
MOZ_ASSERT(JS_IsExceptionPending(cx));
mResult = NS_OK;
return;
}
if (IsErrorWithMessage()) {
SetPendingExceptionWithMessage(cx, context);
return;
}
if (IsJSException()) {
SetPendingJSException(cx);
return;
}
if (IsDOMException()) {
SetPendingDOMException(cx, context);
return;
}
SetPendingGenericErrorException(cx);
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::StealExceptionFromJSContext(JSContext* cx) {
AssertInOwningThread();
MOZ_ASSERT(mMightHaveUnreportedJSException,
"Why didn't you tell us you planned to throw a JS exception?");
JS::Rooted<JS::Value> exn(cx);
if (!JS_GetPendingException(cx, &exn)) {
ThrowUncatchableException();
return;
}
ThrowJSException(cx, exn);
JS_ClearPendingException(cx);
}
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::NoteJSContextException(JSContext* aCx) {
AssertInOwningThread();
if (JS_IsExceptionPending(aCx)) {
mResult = NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT;
} else {
mResult = NS_ERROR_UNCATCHABLE_EXCEPTION;
}
}
/* static */
template <typename CleanupPolicy>
void TErrorResult<CleanupPolicy>::EnsureUTF8Validity(nsCString& aValue,
size_t aValidUpTo) {
nsCString valid;
if (NS_SUCCEEDED(UTF_8_ENCODING->DecodeWithoutBOMHandling(aValue, valid,
aValidUpTo))) {
aValue = valid;
} else {
aValue.SetLength(aValidUpTo);
}
}
template class TErrorResult<JustAssertCleanupPolicy>;
template class TErrorResult<AssertAndSuppressCleanupPolicy>;
template class TErrorResult<JustSuppressCleanupPolicy>;
template class TErrorResult<ThreadSafeJustSuppressCleanupPolicy>;
} // namespace binding_danger
namespace dom {
bool DefineConstants(JSContext* cx, JS::Handle<JSObject*> obj,
const ConstantSpec* cs) {
JS::Rooted<JS::Value> value(cx);
for (; cs->name; ++cs) {
value = cs->value;
bool ok = JS_DefineProperty(
cx, obj, cs->name, value,
JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
if (!ok) {
return false;
}
}
return true;
}
static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
const JSFunctionSpec* spec) {
return JS_DefineFunctions(cx, obj, spec);
}
static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
const JSPropertySpec* spec) {
return JS_DefineProperties(cx, obj, spec);
}
static inline bool Define(JSContext* cx, JS::Handle<JSObject*> obj,
const ConstantSpec* spec) {
return DefineConstants(cx, obj, spec);
}
template <typename T>
bool DefinePrefable(JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<T>* props) {
MOZ_ASSERT(props);
MOZ_ASSERT(props->specs);
do {
// Define if enabled
if (props->isEnabled(cx, obj)) {
if (!Define(cx, obj, props->specs)) {
return false;
}
}
} while ((++props)->specs);
return true;
}
bool DefineLegacyUnforgeableMethods(
JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSFunctionSpec>* props) {
return DefinePrefable(cx, obj, props);
}
bool DefineLegacyUnforgeableAttributes(
JSContext* cx, JS::Handle<JSObject*> obj,
const Prefable<const JSPropertySpec>* props) {
return DefinePrefable(cx, obj, props);
}
// We should use JSFunction objects for interface objects, but we need a custom
// hasInstance hook because we have new interface objects on prototype chains of
// old (XPConnect-based) bindings. We also need Xrays and arbitrary numbers of
// reserved slots (e.g. for named constructors). So we define a custom
// funToString ObjectOps member for interface objects.
JSString* InterfaceObjectToString(JSContext* aCx, JS::Handle<JSObject*> aObject,
bool /* isToSource */) {
const JSClass* clasp = JS::GetClass(aObject);
MOZ_ASSERT(IsDOMIfaceAndProtoClass(clasp));
const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
DOMIfaceAndProtoJSClass::FromJSClass(clasp);
return JS_NewStringCopyZ(aCx, ifaceAndProtoJSClass->mFunToString);
}
bool Constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
const JS::Value& v = js::GetFunctionNativeReserved(
&args.callee(), CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
const JSNativeHolder* nativeHolder =
static_cast<const JSNativeHolder*>(v.toPrivate());
return (nativeHolder->mNative)(cx, argc, vp);
}
static JSObject* CreateConstructor(JSContext* cx, JS::Handle<JSObject*> global,
const char* name,
const JSNativeHolder* nativeHolder,
unsigned ctorNargs) {
JSFunction* fun = js::NewFunctionWithReserved(cx, Constructor, ctorNargs,
JSFUN_CONSTRUCTOR, name);
if (!fun) {
return nullptr;
}
JSObject* constructor = JS_GetFunctionObject(fun);
js::SetFunctionNativeReserved(
constructor, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT,
JS::PrivateValue(const_cast<JSNativeHolder*>(nativeHolder)));
return constructor;
}
static bool DefineConstructor(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<jsid> name,
JS::Handle<JSObject*> constructor) {
bool alreadyDefined;
if (!JS_AlreadyHasOwnPropertyById(cx, global, name, &alreadyDefined)) {
return false;
}
// This is Enumerable: False per spec.
return alreadyDefined ||
JS_DefinePropertyById(cx, global, name, constructor, JSPROP_RESOLVING);
}
static bool DefineConstructor(JSContext* cx, JS::Handle<JSObject*> global,
const char* name,
JS::Handle<JSObject*> constructor) {
PinnedStringId nameStr;
return nameStr.init(cx, name) &&
DefineConstructor(cx, global, nameStr, constructor);
}
// name must be a pinned string (or JS::PropertyKey::fromPinnedString will
// assert).
static JSObject* CreateInterfaceObject(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> constructorProto, const JSClass* constructorClass,
unsigned ctorNargs, const LegacyFactoryFunction* namedConstructors,
JS::Handle<JSObject*> proto, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name,
bool isChrome, bool defineOnGlobal, const char* const* legacyWindowAliases,
bool isNamespace) {
JS::Rooted<JSObject*> constructor(cx);
MOZ_ASSERT(constructorProto);
MOZ_ASSERT(constructorClass);
constructor =
JS_NewObjectWithGivenProto(cx, constructorClass, constructorProto);
if (!constructor) {
return nullptr;
}
if (!isNamespace) {
if (!JS_DefineProperty(cx, constructor, "length", ctorNargs,
JSPROP_READONLY)) {
return nullptr;
}
if (!JS_DefineProperty(cx, constructor, "name", name, JSPROP_READONLY)) {
return nullptr;
}
}
if (DOMIfaceAndProtoJSClass::FromJSClass(constructorClass)
->wantsInterfaceHasInstance) {
if (isChrome ||
StaticPrefs::dom_webidl_crosscontext_hasinstance_enabled()) {
JS::Rooted<jsid> hasInstanceId(cx, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(
cx, JS::SymbolCode::hasInstance)));
if (!JS_DefineFunctionById(
cx, constructor, hasInstanceId, InterfaceHasInstance, 1,
// Flags match those of Function[Symbol.hasInstance]
JSPROP_READONLY | JSPROP_PERMANENT)) {
return nullptr;
}
}
if (isChrome && !JS_DefineFunction(cx, constructor, "isInstance",
InterfaceIsInstance, 1,
// Don't bother making it enumerable
0)) {
return nullptr;
}
}
if (properties) {
if (properties->HasStaticMethods() &&
!DefinePrefable(cx, constructor, properties->StaticMethods())) {
return nullptr;
}
if (properties->HasStaticAttributes() &&
!DefinePrefable(cx, constructor, properties->StaticAttributes())) {
return nullptr;
}
if (properties->HasConstants() &&
!DefinePrefable(cx, constructor, properties->Constants())) {
return nullptr;
}
}
if (chromeOnlyProperties && isChrome) {
if (chromeOnlyProperties->HasStaticMethods() &&
!DefinePrefable(cx, constructor,
chromeOnlyProperties->StaticMethods())) {
return nullptr;
}
if (chromeOnlyProperties->HasStaticAttributes() &&
!DefinePrefable(cx, constructor,
chromeOnlyProperties->StaticAttributes())) {
return nullptr;
}
if (chromeOnlyProperties->HasConstants() &&
!DefinePrefable(cx, constructor, chromeOnlyProperties->Constants())) {
return nullptr;
}
}
if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) {
return nullptr;
}
JS::Rooted<jsid> nameStr(cx, JS::PropertyKey::fromPinnedString(name));
if (defineOnGlobal && !DefineConstructor(cx, global, nameStr, constructor)) {
return nullptr;
}
if (legacyWindowAliases && NS_IsMainThread()) {
for (; *legacyWindowAliases; ++legacyWindowAliases) {
if (!DefineConstructor(cx, global, *legacyWindowAliases, constructor)) {
return nullptr;
}
}
}
if (namedConstructors) {
int namedConstructorSlot = DOM_INTERFACE_SLOTS_BASE;
while (namedConstructors->mName) {
JS::Rooted<JSObject*> namedConstructor(
cx, CreateConstructor(cx, global, namedConstructors->mName,
&namedConstructors->mHolder,
namedConstructors->mNargs));
if (!namedConstructor ||
!JS_DefineProperty(cx, namedConstructor, "prototype", proto,
JSPROP_PERMANENT | JSPROP_READONLY) ||
(defineOnGlobal &&
!DefineConstructor(cx, global, namedConstructors->mName,
namedConstructor))) {
return nullptr;
}
JS::SetReservedSlot(constructor, namedConstructorSlot++,
JS::ObjectValue(*namedConstructor));
++namedConstructors;
}
}
return constructor;
}
static JSObject* CreateInterfacePrototypeObject(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> parentProto, const JSClass* protoClass,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties,
const char* const* unscopableNames, JS::Handle<JSString*> name,
bool isGlobal) {
JS::Rooted<JSObject*> ourProto(
cx, JS_NewObjectWithGivenProto(cx, protoClass, parentProto));
if (!ourProto ||
// We don't try to define properties on the global's prototype; those
// properties go on the global itself.
(!isGlobal &&
!DefineProperties(cx, ourProto, properties, chromeOnlyProperties))) {
return nullptr;
}
if (unscopableNames) {
JS::Rooted<JSObject*> unscopableObj(
cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr));
if (!unscopableObj) {
return nullptr;
}
for (; *unscopableNames; ++unscopableNames) {
if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
JS::TrueHandleValue, JSPROP_ENUMERATE)) {
return nullptr;
}
}
JS::Rooted<jsid> unscopableId(cx, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(
cx, JS::SymbolCode::unscopables)));
// Readonly and non-enumerable to match Array.prototype.
if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
JSPROP_READONLY)) {
return nullptr;
}
}
JS::Rooted<jsid> toStringTagId(cx, SYMBOL_TO_JSID(JS::GetWellKnownSymbol(
cx, JS::SymbolCode::toStringTag)));
if (!JS_DefinePropertyById(cx, ourProto, toStringTagId, name,
JSPROP_READONLY)) {
return nullptr;
}
return ourProto;
}
bool DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties) {
if (properties) {
if (properties->HasMethods() &&
!DefinePrefable(cx, obj, properties->Methods())) {
return false;
}
if (properties->HasAttributes() &&
!DefinePrefable(cx, obj, properties->Attributes())) {
return false;
}
if (properties->HasConstants() &&
!DefinePrefable(cx, obj, properties->Constants())) {
return false;
}
}
if (chromeOnlyProperties) {
if (chromeOnlyProperties->HasMethods() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->Methods())) {
return false;
}
if (chromeOnlyProperties->HasAttributes() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->Attributes())) {
return false;
}
if (chromeOnlyProperties->HasConstants() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->Constants())) {
return false;
}
}
return true;
}
void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto, const JSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> constructorProto,
const JSClass* constructorClass, unsigned ctorNargs,
const LegacyFactoryFunction* namedConstructors,
JS::Heap<JSObject*>* constructorCache, const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, const char* name,
bool defineOnGlobal, const char* const* unscopableNames, bool isGlobal,
const char* const* legacyWindowAliases, bool isNamespace) {
MOZ_ASSERT(protoClass || constructorClass, "Need at least one class!");
MOZ_ASSERT(
!((properties &&
(properties->HasMethods() || properties->HasAttributes())) ||
(chromeOnlyProperties && (chromeOnlyProperties->HasMethods() ||
chromeOnlyProperties->HasAttributes()))) ||
protoClass,
"Methods or properties but no protoClass!");
MOZ_ASSERT(!((properties && (properties->HasStaticMethods() ||
properties->HasStaticAttributes())) ||
(chromeOnlyProperties &&
(chromeOnlyProperties->HasStaticMethods() ||
chromeOnlyProperties->HasStaticAttributes()))) ||
constructorClass,
"Static methods but no constructorClass!");
MOZ_ASSERT(!protoClass == !protoCache,
"If, and only if, there is an interface prototype object we need "
"to cache it");
MOZ_ASSERT(bool(constructorClass) == bool(constructorCache),
"If, and only if, there is an interface object we need to cache "
"it");
MOZ_ASSERT(constructorProto || !constructorClass,
"Must have a constructor proto if we plan to create a constructor "
"object");
bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx);
// Might as well intern, since we're going to need an atomized
// version of name anyway when we stick our constructor on the
// global.
JS::Rooted<JSString*> nameStr(cx, JS_AtomizeAndPinString(cx, name));
if (!nameStr) {
return;
}
JS::Rooted<JSObject*> proto(cx);
if (protoClass) {
proto = CreateInterfacePrototypeObject(
cx, global, protoProto, protoClass, properties,
isChrome ? chromeOnlyProperties : nullptr, unscopableNames, nameStr,
isGlobal);
if (!proto) {
return;
}
*protoCache = proto;
} else {
MOZ_ASSERT(!proto);
}
JSObject* interface;
if (constructorClass) {
interface = CreateInterfaceObject(
cx, global, constructorProto, constructorClass, ctorNargs,
namedConstructors, proto, properties, chromeOnlyProperties, nameStr,
isChrome, defineOnGlobal, legacyWindowAliases, isNamespace);
if (!interface) {
if (protoCache) {
// If we fail we need to make sure to clear the value of protoCache we
// set above.
*protoCache = nullptr;
}
return;
}
*constructorCache = interface;
}
}
// Only set aAllowNativeWrapper to false if you really know you need it; if in
// doubt use true. Setting it to false disables security wrappers.
static bool NativeInterface2JSObjectAndThrowIfFailed(
JSContext* aCx, JS::Handle<JSObject*> aScope,
JS::MutableHandle<JS::Value> aRetval, xpcObjectHelper& aHelper,
const nsIID* aIID, bool aAllowNativeWrapper) {
js::AssertSameCompartment(aCx, aScope);
nsresult rv;
// Inline some logic from XPCConvert::NativeInterfaceToJSObject that we need
// on all threads.
nsWrapperCache* cache = aHelper.GetWrapperCache();
if (cache) {
JS::Rooted<JSObject*> obj(aCx, cache->GetWrapper());
if (!obj) {
obj = cache->WrapObject(aCx, nullptr);
if (!obj) {
return Throw(aCx, NS_ERROR_UNEXPECTED);
}
}
if (aAllowNativeWrapper && !JS_WrapObject(aCx, &obj)) {
return false;
}
aRetval.setObject(*obj);
return true;
}
MOZ_ASSERT(NS_IsMainThread());
if (!XPCConvert::NativeInterface2JSObject(aCx, aRetval, aHelper, aIID,
aAllowNativeWrapper, &rv)) {
// I can't tell if NativeInterface2JSObject throws JS exceptions
// or not. This is a sloppy stab at the right semantics; the
// method really ought to be fixed to behave consistently.
if (!JS_IsExceptionPending(aCx)) {
Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
}
return false;
}
return true;
}
bool TryPreserveWrapper(JS::Handle<JSObject*> obj) {
MOZ_ASSERT(IsDOMObject(obj));
// nsISupports objects are special cased because DOM proxies are nsISupports
// and have addProperty hooks that do more than wrapper preservation (so we
// don't want to call them).
if (nsISupports* native = UnwrapDOMObjectToISupports(obj)) {
nsWrapperCache* cache = nullptr;
CallQueryInterface(native, &cache);
if (cache) {
cache->PreserveWrapper(native);
}
return true;
}
// The addProperty hook for WebIDL classes does wrapper preservation, and
// nothing else, so call it, if present.
const JSClass* clasp = JS::GetClass(obj);
const DOMJSClass* domClass = GetDOMClass(clasp);
// We expect all proxies to be nsISupports.
MOZ_RELEASE_ASSERT(clasp->isNativeObject(),
"Should not call addProperty for proxies.");
JSAddPropertyOp addProperty = clasp->getAddProperty();
if (!addProperty) {
return true;
}
// The class should have an addProperty hook iff it is a CC participant.
MOZ_RELEASE_ASSERT(domClass->mParticipant);
JS::Rooted<jsid> dummyId(RootingCx());
JS::Rooted<JS::Value> dummyValue(RootingCx());
return addProperty(nullptr, obj, dummyId, dummyValue);
}
bool HasReleasedWrapper(JS::Handle<JSObject*> obj) {
MOZ_ASSERT(obj);
MOZ_ASSERT(IsDOMObject(obj));
nsWrapperCache* cache = nullptr;
if (nsISupports* native = UnwrapDOMObjectToISupports(obj)) {
CallQueryInterface(native, &cache);
} else {
const JSClass* clasp = JS::GetClass(obj);
const DOMJSClass* domClass = GetDOMClass(clasp);
// We expect all proxies to be nsISupports.
MOZ_RELEASE_ASSERT(clasp->isNativeObject(),
"Should not call getWrapperCache for proxies.");
WrapperCacheGetter getter = domClass->mWrapperCacheGetter;
if (getter) {
// If the class has a wrapper cache getter it must be a CC participant.
MOZ_RELEASE_ASSERT(domClass->mParticipant);
cache = getter(obj);
}
}
return cache && !cache->PreservingWrapper();
}
// Can only be called with a DOM JSClass.
bool InstanceClassHasProtoAtDepth(const JSClass* clasp, uint32_t protoID,
uint32_t depth) {
const DOMJSClass* domClass = DOMJSClass::FromJSClass(clasp);
return static_cast<uint32_t>(domClass->mInterfaceChain[depth]) == protoID;
}
// 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) {
return NativeInterface2JSObjectAndThrowIfFailed(cx, scope, rval, helper, iid,
allowNativeWrapper);
}
bool VariantToJsval(JSContext* aCx, nsIVariant* aVariant,
JS::MutableHandle<JS::Value> aRetval) {
nsresult rv;
if (!XPCVariant::VariantDataToJS(aCx, aVariant, &rv, aRetval)) {
// Does it throw? Who knows
if (!JS_IsExceptionPending(aCx)) {
Throw(aCx, NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED);
}
return false;
}
return true;
}
bool WrapObject(JSContext* cx, const WindowProxyHolder& p,
JS::MutableHandle<JS::Value> rval) {
return ToJSValue(cx, p, rval);
}
static int CompareIdsAtIndices(const void* aElement1, const void* aElement2,
void* aClosure) {
const uint16_t index1 = *static_cast<const uint16_t*>(aElement1);
const uint16_t index2 = *static_cast<const uint16_t*>(aElement2);
const PropertyInfo* infos = static_cast<PropertyInfo*>(aClosure);
MOZ_ASSERT(JSID_BITS(infos[index1].Id()) != JSID_BITS(infos[index2].Id()));
return JSID_BITS(infos[index1].Id()) < JSID_BITS(infos[index2].Id()) ? -1 : 1;
}
// {JSPropertySpec,JSFunctionSpec} use {JSPropertySpec,JSFunctionSpec}::Name
// and ConstantSpec uses `const char*` for name field.
static inline JSPropertySpec::Name ToPropertySpecName(
JSPropertySpec::Name name) {
return name;
}
static inline JSPropertySpec::Name ToPropertySpecName(const char* name) {
return JSPropertySpec::Name(name);
}
template <typename SpecT>
static bool InitPropertyInfos(JSContext* cx, const Prefable<SpecT>* pref,
PropertyInfo* infos, PropertyType type) {
MOZ_ASSERT(pref);
MOZ_ASSERT(pref->specs);
// Index of the Prefable that contains the id for the current PropertyInfo.
uint32_t prefIndex = 0;
do {
// We ignore whether the set of ids is enabled and just intern all the IDs,
// because this is only done once per application runtime.
const SpecT* spec = pref->specs;
// Index of the property/function/constant spec for our current PropertyInfo
// in the "specs" array of the relevant Prefable.
uint32_t specIndex = 0;
do {
jsid id;
if (!JS::PropertySpecNameToPermanentId(cx, ToPropertySpecName(spec->name),
&id)) {
return false;
}
infos->SetId(id);
infos->type = type;
infos->prefIndex = prefIndex;
infos->specIndex = specIndex++;
++infos;
} while ((++spec)->name);
++prefIndex;
} while ((++pref)->specs);
return true;
}
#define INIT_PROPERTY_INFOS_IF_DEFINED(TypeName) \
{ \
if (nativeProperties->Has##TypeName##s() && \
!InitPropertyInfos(cx, nativeProperties->TypeName##s(), \
nativeProperties->TypeName##PropertyInfos(), \
e##TypeName)) { \
return false; \
} \
}
static bool InitPropertyInfos(JSContext* cx,
const NativeProperties* nativeProperties) {
INIT_PROPERTY_INFOS_IF_DEFINED(StaticMethod);
INIT_PROPERTY_INFOS_IF_DEFINED(StaticAttribute);
INIT_PROPERTY_INFOS_IF_DEFINED(Method);
INIT_PROPERTY_INFOS_IF_DEFINED(Attribute);
INIT_PROPERTY_INFOS_IF_DEFINED(UnforgeableMethod);
INIT_PROPERTY_INFOS_IF_DEFINED(UnforgeableAttribute);
INIT_PROPERTY_INFOS_IF_DEFINED(Constant);
// Initialize and sort the index array.
uint16_t* indices = nativeProperties->sortedPropertyIndices;
for (unsigned int i = 0; i < nativeProperties->propertyInfoCount; ++i) {
indices[i] = i;
}
// CompareIdsAtIndices() doesn't actually modify the PropertyInfo array, so
// the const_cast here is OK in spite of the signature of NS_QuickSort().
NS_QuickSort(indices, nativeProperties->propertyInfoCount, sizeof(uint16_t),
CompareIdsAtIndices,
const_cast<PropertyInfo*>(nativeProperties->PropertyInfos()));
return true;
}
#undef INIT_PROPERTY_INFOS_IF_DEFINED
static inline bool InitPropertyInfos(
JSContext* aCx, const NativePropertiesHolder& nativeProperties) {
MOZ_ASSERT(NS_IsMainThread());
if (!*nativeProperties.inited) {
if (nativeProperties.regular &&
!InitPropertyInfos(aCx, nativeProperties.regular)) {
return false;
}
if (nativeProperties.chromeOnly &&
!InitPropertyInfos(aCx, nativeProperties.chromeOnly)) {
return false;
}
*nativeProperties.inited = true;
}
return true;
}
void GetInterfaceImpl(JSContext* aCx, nsIInterfaceRequestor* aRequestor,
nsWrapperCache* aCache, JS::Handle<JS::Value> aIID,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aError) {
Maybe<nsIID> iid = xpc::JSValue2ID(aCx, aIID);
if (!iid) {
aError.Throw(NS_ERROR_XPC_BAD_CONVERT_JS);
return;
}
RefPtr<nsISupports> result;
aError = aRequestor->GetInterface(*iid, getter_AddRefs(result));
if (aError.Failed()) {
return;
}
if (!WrapObject(aCx, result, iid.ptr(), aRetval)) {
aError.Throw(NS_ERROR_FAILURE);
}
}
bool ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp) {
// Cast nullptr to void* to work around
return ThrowErrorMessage<MSG_ILLEGAL_CONSTRUCTOR>(cx, (void*)nullptr);
}
bool ThrowConstructorWithoutNew(JSContext* cx, const char* name) {
return ThrowErrorMessage<MSG_CONSTRUCTOR_WITHOUT_NEW>(cx, name);
}
inline const NativePropertyHooks* GetNativePropertyHooksFromConstructorFunction(
JS::Handle<JSObject*> obj) {
MOZ_ASSERT(JS_IsNativeFunction(obj, Constructor));
const JS::Value& v = js::GetFunctionNativeReserved(
obj, CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT);
const JSNativeHolder* nativeHolder =
static_cast<const JSNativeHolder*>(v.toPrivate());
return nativeHolder->mPropertyHooks;
}
inline const NativePropertyHooks* GetNativePropertyHooks(
JSContext* cx, JS::Handle<JSObject*> obj, DOMObjectType& type) {
const JSClass* clasp = JS::GetClass(obj);
const DOMJSClass* domClass = GetDOMClass(clasp);
if (domClass) {
bool isGlobal = (clasp->flags & JSCLASS_DOM_GLOBAL) != 0;
type = isGlobal ? eGlobalInstance : eInstance;
return domClass->mNativeHooks;
}
if (JS_ObjectIsFunction(obj)) {
type = eInterface;
return GetNativePropertyHooksFromConstructorFunction(obj);
}
MOZ_ASSERT(IsDOMIfaceAndProtoClass(JS::GetClass(obj)));
const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
DOMIfaceAndProtoJSClass::FromJSClass(JS::GetClass(obj));
type = ifaceAndProtoJSClass->mType;
return ifaceAndProtoJSClass->mNativeHooks;
}
static JSObject* XrayCreateFunction(JSContext* cx,
JS::Handle<JSObject*> wrapper,
JSNativeWrapper native, unsigned nargs,
JS::Handle<jsid> id) {
JSFunction* fun;
if (JSID_IS_STRING(id)) {
fun = js::NewFunctionByIdWithReserved(cx, native.op, nargs, 0, id);
} else {
// Can't pass this id (probably a symbol) to NewFunctionByIdWithReserved;
// just use an empty name for lack of anything better.
fun = js::NewFunctionWithReserved(cx, native.op, nargs, 0, nullptr);
}
if (!fun) {
return nullptr;
}
SET_JITINFO(fun, native.info);
JSObject* obj = JS_GetFunctionObject(fun);
js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT,
JS::ObjectValue(*wrapper));
#ifdef DEBUG
js::SetFunctionNativeReserved(obj, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF,
JS::ObjectValue(*obj));
#endif
return obj;
}
struct IdToIndexComparator {
// The id we're searching for.
const jsid& mId;
// The list of ids we're searching in.
const PropertyInfo* mInfos;
explicit IdToIndexComparator(const jsid& aId, const PropertyInfo* aInfos)
: mId(aId), mInfos(aInfos) {}
int operator()(const uint16_t aIndex) const {
if (JSID_BITS(mId) == JSID_BITS(mInfos[aIndex].Id())) {
return 0;
}
return JSID_BITS(mId) < JSID_BITS(mInfos[aIndex].Id()) ? -1 : 1;
}
};
static const PropertyInfo* XrayFindOwnPropertyInfo(
JSContext* cx, JS::Handle<jsid> id,
const NativeProperties* nativeProperties) {
if (MOZ_UNLIKELY(nativeProperties->iteratorAliasMethodIndex >= 0) &&
id.isWellKnownSymbol(JS::SymbolCode::iterator)) {
return nativeProperties->MethodPropertyInfos() +
nativeProperties->iteratorAliasMethodIndex;
}
size_t idx;
const uint16_t* sortedPropertyIndices =
nativeProperties->sortedPropertyIndices;
const PropertyInfo* propertyInfos = nativeProperties->PropertyInfos();
if (BinarySearchIf(sortedPropertyIndices, 0,
nativeProperties->propertyInfoCount,
IdToIndexComparator(id, propertyInfos), &idx)) {
return propertyInfos + sortedPropertyIndices[idx];
}
return nullptr;
}
static bool XrayResolveAttribute(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, const Prefable<const JSPropertySpec>& pref,
const JSPropertySpec& attrSpec,
JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder) {
if (!pref.isEnabled(cx, obj)) {
return true;
}
MOZ_ASSERT(attrSpec.isAccessor());
MOZ_ASSERT(
!attrSpec.isSelfHosted(),
"Bad JSPropertySpec declaration: unsupported self-hosted accessor");
cacheOnHolder = true;
// Because of centralization, we need to make sure we fault in the JitInfos as
// well. At present, until the JSAPI changes, the easiest way to do this is
// wrap them up as functions ourselves.
// They all have getters, so we can just make it.
JS::Rooted<JSObject*> getter(
cx, XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.getter.native, 0,
id));
if (!getter) {
return false;
}
JS::Rooted<JSObject*> setter(cx);
if (attrSpec.u.accessors.setter.native.op) {
// We have a setter! Make it.
setter = XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.setter.native,
1, id);
if (!setter) {
return false;
}
}
desc.set(Some(
JS::PropertyDescriptor::Accessor(getter, setter, attrSpec.attributes())));
return true;
}
static bool XrayResolveMethod(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, const Prefable<const JSFunctionSpec>& pref,
const JSFunctionSpec& methodSpec,
JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder) {
if (!pref.isEnabled(cx, obj)) {
return true;
}
cacheOnHolder = true;
JSObject* funobj;
if (methodSpec.selfHostedName) {
JSFunction* fun = JS::GetSelfHostedFunction(cx, methodSpec.selfHostedName,
id, methodSpec.nargs);
if (!fun) {
return false;
}
MOZ_ASSERT(!methodSpec.call.op,
"Bad FunctionSpec declaration: non-null native");
MOZ_ASSERT(!methodSpec.call.info,
"Bad FunctionSpec declaration: non-null jitinfo");
funobj = JS_GetFunctionObject(fun);
} else {
funobj =
XrayCreateFunction(cx, wrapper, methodSpec.call, methodSpec.nargs, id);
if (!funobj) {
return false;
}
}
desc.set(Some(JS::PropertyDescriptor::Data(JS::ObjectValue(*funobj),
methodSpec.flags)));
return true;
}
static bool XrayResolveConstant(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
JS::Handle<jsid>, const Prefable<const ConstantSpec>& pref,
const ConstantSpec& constantSpec,
JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder) {
if (!pref.isEnabled(cx, obj)) {
return true;
}
cacheOnHolder = true;
desc.set(Some(JS::PropertyDescriptor::Data(
constantSpec.value, {JS::PropertyAttribute::Enumerable})));
return true;
}
#define RESOLVE_CASE(PropType, SpecType, Resolver) \
case e##PropType: { \
MOZ_ASSERT(nativeProperties->Has##PropType##s()); \
const Prefable<const SpecType>& pref = \
nativeProperties->PropType##s()[propertyInfo.prefIndex]; \
return Resolver(cx, wrapper, obj, id, pref, \
pref.specs[propertyInfo.specIndex], desc, cacheOnHolder); \
}
static bool XrayResolveProperty(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder, DOMObjectType type,
const NativeProperties* nativeProperties,
const PropertyInfo& propertyInfo) {
MOZ_ASSERT(type != eGlobalInterfacePrototype);
// Make sure we resolve for matched object type.
switch (propertyInfo.type) {
case eStaticMethod:
case eStaticAttribute:
if (type != eInterface) {
return true;
}
break;