/* -*- 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 <cstdint>
#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 "nsGlobalWindowInner.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/ObservableArrayProxyHandler.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/XULResizerElementBinding.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 "ipc/IPCMessageUtilsSpecializations.h"
#include "mozilla/dom/DocGroup.h"
#include "nsXULElement.h"
namespace mozilla {
namespace dom {
// Forward declare GetConstructorObjectHandle methods.
#define HTML_TAG(_tag, _classname, _interfacename) \
namespace HTML
##_interfacename
##Element_Binding { \
JS::Handle<JSObject*> GetConstructorObjectHandle(JSContext*); \
}
#define HTML_OTHER(_tag)
#include "nsHTMLTagList.h"
#undef HTML_TAG
#undef HTML_OTHER
using constructorGetterCallback = JS::Handle<JSObject*> (*)(JSContext*);
// Mapping of html tag and GetConstructorObjectHandle methods.
#define HTML_TAG(_tag, _classname, _interfacename) \
HTML
##_interfacename
##Element_Binding::GetConstructorObjectHandle,
#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::GetConstructorObjectHandle,
#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 < std::size(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);
if (!JS_GetFunctionDisplayId(aCx, func, &funcName)) {
return false;
}
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),
static_cast<
const char16_t*>(funcNameStr.get()),
static_cast<
const char16_t*>(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::MessageWriter* aWriter)
const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasMessage);
MOZ_ASSERT(mExtra.mMessage);
WriteParam(aWriter, mExtra.mMessage->mArgs);
WriteParam(aWriter, mExtra.mMessage->mErrorNumber);
}
template <
typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeMessage(
IPC::MessageReader* aReader) {
using namespace IPC;
AssertInOwningThread();
auto readMessage = MakeUnique<Message>();
if (!ReadParam(aReader, &readMessage->mArgs) ||
!ReadParam(aReader, &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::MessageWriter* aWriter)
const {
using namespace IPC;
AssertInOwningThread();
MOZ_ASSERT(mUnionState == HasDOMExceptionInfo);
MOZ_ASSERT(mExtra.mDOMExceptionInfo);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mMessage);
WriteParam(aWriter, mExtra.mDOMExceptionInfo->mRv);
}
template <
typename CleanupPolicy>
bool TErrorResult<CleanupPolicy>::DeserializeDOMExceptionInfo(
IPC::MessageReader* aReader) {
using namespace IPC;
AssertInOwningThread();
nsCString message;
nsresult rv;
if (!ReadParam(aReader, &message) || !ReadParam(aReader, &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()) {
// Note: ReportUncatchableException will clear any existing exception on cx.
JS::ReportUncatchableException(cx);
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);
}
bool InterfaceObjectJSNative(JSContext* cx,
unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
return NativeHolderFromInterfaceObject(&args.callee())->mNative(cx, argc, vp);
}
bool LegacyFactoryFunctionJSNative(JSContext* cx,
unsigned argc,
JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
return NativeHolderFromLegacyFactoryFunction(&args.callee())
->mNative(cx, argc, vp);
}
// This creates a JSFunction and sets its length and name properties in the
// order that ECMAScript's CreateBuiltinFunction does.
static JSObject* CreateBuiltinFunctionForConstructor(
JSContext* aCx, JSNative aNative, size_t aNativeReservedSlot,
void* aNativeReserved,
unsigned int aNargs, jsid aName,
JS::Handle<JSObject*> aProto) {
JSFunction* fun = js::NewFunctionByIdWithReservedAndProto(
aCx, aNative, aProto, aNargs, JSFUN_CONSTRUCTOR, aName);
if (!fun) {
return nullptr;
}
JS::Rooted<JSObject*> constructor(aCx, JS_GetFunctionObject(fun));
js::SetFunctionNativeReserved(constructor, aNativeReservedSlot,
JS::PrivateValue(aNativeReserved));
// Eagerly force creation of the .length and .name properties, because
// SpiderMonkey creates them lazily (see
// https://bugzilla.mozilla.org/show_bug.cgi?id=1629803).
bool unused;
if (!JS_HasProperty(aCx, constructor,
"length", &unused) ||
!JS_HasProperty(aCx, constructor,
"name", &unused)) {
return nullptr;
}
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) {
JSString* nameStr = JS_AtomizeString(cx, name);
if (!nameStr) {
return false;
}
JS::Rooted<JS::PropertyKey> nameKey(cx, JS::PropertyKey::NonIntAtom(nameStr));
return DefineConstructor(cx, global, nameKey, constructor);
}
static bool DefineToStringTag(JSContext* cx, JS::Handle<JSObject*> obj,
JS::Handle<JSString*> class_name) {
JS::Rooted<jsid> toStringTagId(
cx, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::toStringTag));
return JS_DefinePropertyById(cx, obj, toStringTagId, class_name,
JSPROP_READONLY);
}
static bool InterfaceIsInstance(JSContext* cx,
unsigned argc, JS::Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
// If the thing we were passed is not an object, return false like
// OrdinaryHasInstance does.
if (!args.get(0).isObject()) {
args.rval().setBoolean(
false);
return true;
}
// If "this" is not an object, likewise return false (again, like
// OrdinaryHasInstance).
if (!args.thisv().isObject()) {
args.rval().setBoolean(
false);
return true;
}
// If "this" is not an interface object, likewise return false (again, like
// OrdinaryHasInstance). But note that we should CheckedUnwrapStatic here,
// because otherwise we won't get the right answers.
// The static version is OK, because we're looking for interface objects,
// which are not cross-origin objects.
JS::Rooted<JSObject*> thisObj(
cx, js::CheckedUnwrapStatic(&args.thisv().toObject()));
if (!thisObj) {
// Just fall back on the normal thing, in case it still happens to work.
args.rval().setBoolean(
false);
return true;
}
if (!IsInterfaceObject(thisObj)) {
args.rval().setBoolean(
false);
return true;
}
const DOMInterfaceInfo* interfaceInfo = InterfaceInfoFromObject(thisObj);
// If "this" is a constructor for an interface without a prototype, just fall
// back.
if (interfaceInfo->mPrototypeID == prototypes::id::_ID_Count) {
args.rval().setBoolean(
false);
return true;
}
JS::Rooted<JSObject*> instance(cx, &args[0].toObject());
const DOMJSClass* domClass = GetDOMClass(
js::UncheckedUnwrap(instance,
/* stopAtWindowProxy = */ false));
if (domClass && domClass->mInterfaceChain[interfaceInfo->mDepth] ==
interfaceInfo->mPrototypeID) {
args.rval().setBoolean(
true);
return true;
}
if (IsRemoteObjectProxy(instance, interfaceInfo->mPrototypeID)) {
args.rval().setBoolean(
true);
return true;
}
args.rval().setBoolean(
false);
return true;
}
bool InitInterfaceOrNamespaceObject(
JSContext* cx, JS::Handle<JSObject*> obj,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties,
bool isChrome) {
if (properties) {
if (properties->HasStaticMethods() &&
!DefinePrefable(cx, obj, properties->StaticMethods())) {
return false;
}
if (properties->HasStaticAttributes() &&
!DefinePrefable(cx, obj, properties->StaticAttributes())) {
return false;
}
if (properties->HasConstants() &&
!DefinePrefable(cx, obj, properties->Constants())) {
return false;
}
}
if (chromeOnlyProperties && isChrome) {
if (chromeOnlyProperties->HasStaticMethods() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->StaticMethods())) {
return false;
}
if (chromeOnlyProperties->HasStaticAttributes() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->StaticAttributes())) {
return false;
}
if (chromeOnlyProperties->HasConstants() &&
!DefinePrefable(cx, obj, chromeOnlyProperties->Constants())) {
return false;
}
}
return true;
}
// name must be an atom (or JS::PropertyKey::NonIntAtom will assert).
static JSObject* CreateInterfaceObject(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> interfaceProto,
const DOMInterfaceInfo* interfaceInfo,
unsigned ctorNargs,
const Span<
const LegacyFactoryFunction>& legacyFactoryFunctions,
JS::Handle<JSObject*> proto,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties, JS::Handle<JSString*> name,
bool isChrome,
bool defineOnGlobal,
const char*
const* legacyWindowAliases) {
MOZ_ASSERT(interfaceProto);
MOZ_ASSERT(interfaceInfo);
JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(name));
JS::Rooted<JSObject*> constructor(
cx, CreateBuiltinFunctionForConstructor(
cx, InterfaceObjectJSNative, INTERFACE_OBJECT_INFO_RESERVED_SLOT,
const_cast<DOMInterfaceInfo*>(interfaceInfo), ctorNargs, nameId,
interfaceProto));
if (!constructor) {
return nullptr;
}
if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) {
return nullptr;
}
if (!InitInterfaceOrNamespaceObject(cx, constructor, properties,
chromeOnlyProperties, isChrome)) {
return nullptr;
}
if (defineOnGlobal && !DefineConstructor(cx, global, nameId, constructor)) {
return nullptr;
}
if (interfaceInfo->wantsInterfaceIsInstance && isChrome &&
!JS_DefineFunction(cx, constructor,
"isInstance", InterfaceIsInstance, 1,
// Don't bother making it enumerable
0)) {
return nullptr;
}
if (legacyWindowAliases && NS_IsMainThread()) {
for (; *legacyWindowAliases; ++legacyWindowAliases) {
if (!DefineConstructor(cx, global, *legacyWindowAliases, constructor)) {
return nullptr;
}
}
}
int legacyFactoryFunctionSlot =
INTERFACE_OBJECT_FIRST_LEGACY_FACTORY_FUNCTION;
for (
const LegacyFactoryFunction& lff : legacyFactoryFunctions) {
JSString* fname = JS_AtomizeString(cx, lff.mName);
if (!fname) {
return nullptr;
}
nameId = JS::PropertyKey::NonIntAtom(fname);
JS::Rooted<JSObject*> legacyFactoryFunction(
cx, CreateBuiltinFunctionForConstructor(
cx, LegacyFactoryFunctionJSNative,
LEGACY_FACTORY_FUNCTION_RESERVED_SLOT,
const_cast<LegacyFactoryFunction*>(&lff), lff.mNargs, nameId,
nullptr));
if (!legacyFactoryFunction ||
!JS_DefineProperty(cx, legacyFactoryFunction,
"prototype", proto,
JSPROP_PERMANENT | JSPROP_READONLY) ||
(defineOnGlobal &&
!DefineConstructor(cx, global, nameId, legacyFactoryFunction))) {
return nullptr;
}
js::SetFunctionNativeReserved(constructor, legacyFactoryFunctionSlot,
JS::ObjectValue(*legacyFactoryFunction));
++legacyFactoryFunctionSlot;
}
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, JS::GetWellKnownSymbolKey(cx, JS::SymbolCode::unscopables));
// Readonly and non-enumerable to match Array.prototype.
if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
JSPROP_READONLY)) {
return nullptr;
}
}
if (!DefineToStringTag(cx, ourProto, name)) {
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;
}
namespace binding_detail {
void CreateInterfaceObjects(
JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> protoProto,
const DOMIfaceAndProtoJSClass* protoClass,
JS::Heap<JSObject*>* protoCache, JS::Handle<JSObject*> interfaceProto,
const DOMInterfaceInfo* interfaceInfo,
unsigned ctorNargs,
bool isConstructorChromeOnly,
const Span<
const LegacyFactoryFunction>& legacyFactoryFunctions,
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) {
MOZ_ASSERT(protoClass || interfaceInfo,
"Need at least a class or info!");
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()))) ||
interfaceInfo,
"Static methods but no info!");
MOZ_ASSERT(!protoClass == !protoCache,
"If, and only if, there is an interface prototype object we need "
"to cache it");
MOZ_ASSERT(
bool(interfaceInfo) ==
bool(constructorCache),
"If, and only if, there is an interface object we need to cache "
"it");
MOZ_ASSERT(interfaceProto || !interfaceInfo,
"Must have a interface proto if we plan to create an interface "
"object");
bool isChrome = nsContentUtils::ThreadsafeIsSystemCaller(cx);
JS::Rooted<JSString*> nameStr(cx, JS_AtomizeString(cx, name));
if (!nameStr) {
return;
}
JS::Rooted<JSObject*> proto(cx);
if (protoClass) {
proto = CreateInterfacePrototypeObject(
cx, global, protoProto, protoClass->ToJSClass(), properties,
isChrome ? chromeOnlyProperties : nullptr, unscopableNames, nameStr,
isGlobal);
if (!proto) {
return;
}
*protoCache = proto;
}
else {
MOZ_ASSERT(!proto);
}
JSObject* interface;
if (interfaceInfo) {
interface = CreateInterfaceObject(
cx, global, interfaceProto, interfaceInfo,
(isChrome || !isConstructorChromeOnly) ? ctorNargs : 0,
legacyFactoryFunctions, proto, properties, chromeOnlyProperties,
nameStr, isChrome, defineOnGlobal, legacyWindowAliases);
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;
}
}
}
// namespace binding_detail
void CreateNamespaceObject(JSContext* cx, JS::Handle<JSObject*> global,
JS::Handle<JSObject*> namespaceProto,
const DOMIfaceAndProtoJSClass& namespaceClass,
JS::Heap<JSObject*>* namespaceCache,
const NativeProperties* properties,
const NativeProperties* chromeOnlyProperties,
const char* name,
bool defineOnGlobal) {
JS::Rooted<JSString*> nameStr(cx, JS_AtomizeString(cx, name));
if (!nameStr) {
return;
}
JS::Rooted<jsid> nameId(cx, JS::PropertyKey::NonIntAtom(nameStr));
JS::Rooted<JSObject*> namespaceObj(
cx, JS_NewObjectWithGivenProto(cx, namespaceClass.ToJSClass(),
namespaceProto));
if (!namespaceObj) {
return;
}
if (!InitInterfaceOrNamespaceObject(
cx, namespaceObj, properties, chromeOnlyProperties,
nsContentUtils::ThreadsafeIsSystemCaller(cx))) {
return;
}
if (defineOnGlobal && !DefineConstructor(cx, global, nameId, namespaceObj)) {
return;
}
if (!DefineToStringTag(cx, namespaceObj, nameStr)) {
return;
}
*namespaceCache = namespaceObj;
}
// 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);
}
// {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;
auto count = nativeProperties->propertyInfoCount;
for (
auto i = 0; i < count; ++i) {
indices[i] = i;
}
std::sort(indices, indices + count,
[infos = nativeProperties->PropertyInfos()](
const uint16_t left,
const uint16_t right) {
// std::sort may call us with the same element by design but
// PropertyInfo::Compare does not like that.
if (left == right) {
return false;
}
return PropertyInfo::Compare(infos[left], infos[right]) < 0;
});
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
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100666
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* GetNativePropertyHooksFromJSNative(
JS::Handle<JSObject*> obj) {
return NativeHolderFromObject(obj)->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 GetNativePropertyHooksFromJSNative(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 (id.isString()) {
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;
// Whether we're searching for static operations.
const bool mStatic;
// The list of ids we're searching in.
const PropertyInfo* mInfos;
IdToIndexComparator(
const jsid& aId, DOMObjectType aType,
const PropertyInfo* aInfos)
: mId(aId),
mStatic(aType == eInterface || aType == eNamespace),
mInfos(aInfos) {}
int operator()(
const uint16_t aIndex)
const {
const PropertyInfo& info = mInfos[aIndex];
if (mId.asRawBits() == info.Id().asRawBits()) {
if (info.type != eMethod && info.type != eStaticMethod) {
return 0;
}
if (mStatic == info.IsStaticMethod()) {
// We're looking for static properties and we've found a static one for
// the right name.
return 0;
}
// Static operations are sorted before others by PropertyInfo::Compare.
return mStatic ? -1 : 1;
}
return mId.asRawBits() < info.Id().asRawBits() ? -1 : 1;
}
};
static const PropertyInfo* XrayFindOwnPropertyInfo(
JSContext* cx, DOMObjectType type, JS::Handle<jsid> id,
const NativeProperties* nativeProperties) {
if ((type == eInterfacePrototype || type == eGlobalInstance) &&
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, type, 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;
JS::Rooted<jsid> getterId(cx);
if (!JS::ToGetterId(cx, id, &getterId)) {
return false;
}
// 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,
getterId));
if (!getter) {
return false;
}
JS::Rooted<JSObject*> setter(cx);
if (attrSpec.u.accessors.setter.native.op) {
JS::Rooted<jsid> setterId(cx);
if (!JS::ToSetterId(cx, id, &setterId)) {
return false;
}
// We have a setter! Make it.
setter = XrayCreateFunction(cx, wrapper, attrSpec.u.accessors.setter.native,
1, setterId);
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 && type != eNamespace) {
return true;
}
break;
case eMethod:
case eAttribute:
if (type != eGlobalInstance && type != eInterfacePrototype) {
return true;
}
break;
case eUnforgeableMethod:
case eUnforgeableAttribute:
if (!IsInstance(type)) {
return true;
}
break;
case eConstant:
if (IsInstance(type)) {
return true;
}
break;
}
switch (propertyInfo.type) {
RESOLVE_CASE(StaticMethod, JSFunctionSpec, XrayResolveMethod)
RESOLVE_CASE(StaticAttribute, JSPropertySpec, XrayResolveAttribute)
RESOLVE_CASE(Method, JSFunctionSpec, XrayResolveMethod)
RESOLVE_CASE(Attribute, JSPropertySpec, XrayResolveAttribute)
RESOLVE_CASE(UnforgeableMethod, JSFunctionSpec, XrayResolveMethod)
RESOLVE_CASE(UnforgeableAttribute, JSPropertySpec, XrayResolveAttribute)
RESOLVE_CASE(Constant, ConstantSpec, XrayResolveConstant)
}
return true;
}
#undef RESOLVE_CASE
static bool ResolvePrototypeOrConstructor(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
size_t protoAndIfaceCacheIndex,
unsigned attrs,
JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder) {
JS::Rooted<JSObject*> global(cx, JS::GetNonCCWObjectGlobal(obj));
{
JSAutoRealm ar(cx, global);
ProtoAndIfaceCache& protoAndIfaceCache = *GetProtoAndIfaceCache(global);
// This function is called when resolving the "constructor" and "prototype"
// properties of Xrays for DOM prototypes and constructors respectively.
// This means the relevant Xray exists, which means its _target_ exists.
// And that means we managed to successfullly create the prototype or
// constructor, respectively, and hence must have managed to create the
// thing it's pointing to as well. So our entry slot must exist.
JSObject* protoOrIface =
protoAndIfaceCache.EntrySlotMustExist(protoAndIfaceCacheIndex);
MOZ_RELEASE_ASSERT(protoOrIface,
"How can this object not exist?");
cacheOnHolder =
true;
desc.set(Some(
JS::PropertyDescriptor::Data(JS::ObjectValue(*protoOrIface), attrs)));
}
return JS_WrapPropertyDescriptor(cx, desc);
}
/* static */ bool XrayResolveOwnProperty(
JSContext* cx, JS::Handle<JSObject*> wrapper, JS::Handle<JSObject*> obj,
JS::Handle<jsid> id, JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc,
bool& cacheOnHolder) {
MOZ_ASSERT(desc.isNothing());
cacheOnHolder =
false;
DOMObjectType type;
const NativePropertyHooks* nativePropertyHooks =
GetNativePropertyHooks(cx, obj, type);
ResolveOwnProperty resolveOwnProperty =
nativePropertyHooks->mIndexedOrNamedNativeProperties
? nativePropertyHooks->mIndexedOrNamedNativeProperties
->mResolveOwnProperty
: nullptr;
if (type == eNamedPropertiesObject) {
MOZ_ASSERT(!resolveOwnProperty,
"Shouldn't have any Xray-visible properties");
return true;
}
const NativePropertiesHolder& nativePropertiesHolder =
nativePropertyHooks->mNativeProperties;
if (!InitPropertyInfos(cx, nativePropertiesHolder)) {
return false;
}
const NativeProperties* nativeProperties = nullptr;
const PropertyInfo* found = nullptr;
if ((nativeProperties = nativePropertiesHolder.regular)) {
found = XrayFindOwnPropertyInfo(cx, type, id, nativeProperties);
}
if (!found && (nativeProperties = nativePropertiesHolder.chromeOnly) &&
xpc::AccessCheck::isChrome(JS::GetCompartment(wrapper))) {
found = XrayFindOwnPropertyInfo(cx, type, id, nativeProperties);
}
if (IsInstance(type)) {
// Check for unforgeable properties first to prevent names provided by
// resolveOwnProperty callback from shadowing them.
if (found && (found->type == eUnforgeableMethod ||
found->type == eUnforgeableAttribute)) {
if (!XrayResolveProperty(cx, wrapper, obj, id, desc, cacheOnHolder, type,
nativeProperties, *found)) {
return false;
}
if (desc.isSome()) {
return true;
}
}
if (resolveOwnProperty) {
if (!resolveOwnProperty(cx, wrapper, obj, id, desc)) {
return false;
}
if (desc.isSome()) {
// None of these should be cached on the holder, since they're dynamic.
return true;
}
}
// For non-global instance Xrays there are no other properties, so return
// here for them.
if (type != eGlobalInstance) {
return true;
}
}
else if (type == eInterface) {
if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE)) {
return nativePropertyHooks->mPrototypeID == prototypes::id::_ID_Count ||
ResolvePrototypeOrConstructor(
cx, wrapper, obj, nativePropertyHooks->mPrototypeID,
JSPROP_PERMANENT | JSPROP_READONLY, desc, cacheOnHolder);
}
if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_ISINSTANCE)) {
if (IsInterfaceObject(obj) &&
InterfaceInfoFromObject(obj)->wantsInterfaceIsInstance) {
cacheOnHolder =
true;
JSObject* funObj = XrayCreateFunction(
cx, wrapper, {InterfaceIsInstance, nullptr}, 1, id);
if (!funObj) {
return false;
}
desc.set(Some(JS::PropertyDescriptor::Data(
JS::ObjectValue(*funObj), {JS::PropertyAttribute::Configurable,
JS::PropertyAttribute::Writable})));
return true;
}
}
if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_NAME)) {
const char* name = IsInterfaceObject(obj)
? InterfaceInfoFromObject(obj)->mConstructorName
: LegacyFactoryFunctionFromObject(obj)->mName;
JSString* nameStr = JS_NewStringCopyZ(cx, name);
if (!nameStr) {
return false;
}
desc.set(Some(JS::PropertyDescriptor::Data(
JS::StringValue(nameStr), {JS::PropertyAttribute::Configurable,
JS::PropertyAttribute::Enumerable})));
return true;
}
if (id.get() == GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH)) {
uint8_t length = IsInterfaceObject(obj)
? InterfaceInfoFromObject(obj)->mConstructorArgs
: LegacyFactoryFunctionFromObject(obj)->mNargs;
desc.set(Some(JS::PropertyDescriptor::Data(
--> --------------------
--> maximum size reached
--> --------------------