Source code

Revision control

Copy as Markdown

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/. */
/* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */
#include "xpcprivate.h"
#include "xpc_make_class.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "js/CharacterEncoding.h"
#include "js/Class.h"
#include "js/Object.h" // JS::GetClass
#include "js/Printf.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_DefinePropertyById, JS_GetProperty, JS_GetPropertyById
#include "js/Symbol.h"
#include <string_view>
using namespace mozilla;
using namespace JS;
using namespace xpc;
/***************************************************************************/
// All of the exceptions thrown into JS from this file go through here.
// That makes this a nice place to set a breakpoint.
static bool Throw(nsresult errNum, JSContext* cx) {
XPCThrower::Throw(errNum, cx);
return false;
}
// Handy macro used in many callback stub below.
#define THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper) \
PR_BEGIN_MACRO \
if (!wrapper) return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \
if (!wrapper->IsValid()) return Throw(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, cx); \
PR_END_MACRO
/***************************************************************************/
static bool ToStringGuts(XPCCallContext& ccx) {
UniqueChars sz;
XPCWrappedNative* wrapper = ccx.GetWrapper();
if (wrapper) {
sz.reset(wrapper->ToString(ccx.GetTearOff()));
} else {
sz = JS_smprintf("[xpconnect wrapped native prototype]");
}
if (!sz) {
JS_ReportOutOfMemory(ccx);
return false;
}
JSString* str = JS_NewStringCopyZ(ccx, sz.get());
if (!str) {
return false;
}
ccx.SetRetVal(JS::StringValue(str));
return true;
}
/***************************************************************************/
static bool XPC_WN_Shared_ToString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
if (!args.computeThis(cx, &obj)) {
return false;
}
XPCCallContext ccx(cx, obj);
if (!ccx.IsValid()) {
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
}
ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING));
ccx.SetArgsAndResultPtr(args.length(), args.array(), vp);
return ToStringGuts(ccx);
}
static bool XPC_WN_Shared_ToSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
static constexpr std::string_view empty = "({})";
JSString* str = JS_NewStringCopyN(cx, empty.data(), empty.length());
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject obj(cx);
if (!JS_ValueToObject(cx, args.thisv(), &obj)) {
return false;
}
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
JSType hint;
if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
return false;
}
if (hint == JSTYPE_NUMBER) {
args.rval().set(NaNValue());
return true;
}
MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_UNDEFINED);
ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING));
ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address());
XPCNativeMember* member = ccx.GetMember();
if (member && member->IsMethod()) {
if (!XPCWrappedNative::CallMethod(ccx)) {
return false;
}
if (args.rval().isPrimitive()) {
return true;
}
}
// else...
return ToStringGuts(ccx);
}
/***************************************************************************/
// A "double wrapped object" is a user JSObject that has been wrapped as a
// wrappedJS in order to be used by native code and then re-wrapped by a
// wrappedNative wrapper to be used by JS code. One might think of it as:
// wrappedNative(wrappedJS(underlying_JSObject))
// This is done (as opposed to just unwrapping the wrapped JS and automatically
// returning the underlying JSObject) so that JS callers will see what looks
// Like any other xpcom object - and be limited to use its interfaces.
//
/**
* When JavaScript code uses a component that is itself implemented in
* JavaScript then XPConnect will build a wrapper rather than directly
* expose the JSObject of the component. This allows components implemented
* in JavaScript to 'look' just like any other xpcom component (from the
* perspective of the JavaScript caller). This insulates the component from
* the caller and hides any properties or methods that are not part of the
* interface as declared in xpidl. Usually this is a good thing.
*
* However, in some cases it is useful to allow the JS caller access to the
* JS component's underlying implementation. In order to facilitate this
* XPConnect supports the 'wrappedJSObject' property. This 'wrappedJSObject'
* property is different than the XrayWrapper meaning. (The naming collision
* avoids having more than one magic XPConnect property name, but is
* confusing.)
*
* The caller code can do:
*
* // 'foo' is some xpcom component (that might be implemented in JS).
* var bar = foo.wrappedJSObject;
* if(bar) {
* // bar is the underlying JSObject. Do stuff with it here.
* }
*
* Recall that 'foo' above is an XPConnect wrapper, not the underlying JS
* object. The property get "foo.wrappedJSObject" will only succeed if three
* conditions are met:
*
* 1) 'foo' really is an XPConnect wrapper around a JSObject.
* 3) The caller must be system JS and not content. Double-wrapped XPCWJS should
* not be exposed to content except with a remote-XUL domain.
*
* Notes:
*
* a) If 'foo' above were the underlying JSObject and not a wrapper at all,
* then this all just works and XPConnect is not part of the picture at all.
* b) One might ask why 'foo' should not just implement an interface through
* which callers might get at the underlying object. There are two reasons:
* i) XPConnect would still have to do magic since JSObject is not a
* scriptable type.
* ii) Avoiding the explicit interface makes it easier for both the caller
* and the component.
*/
static JSObject* GetDoubleWrappedJSObject(XPCCallContext& ccx,
XPCWrappedNative* wrapper) {
RootedObject obj(ccx);
{
nsCOMPtr<nsIXPConnectWrappedJS> underware =
do_QueryInterface(wrapper->GetIdentityObject());
if (!underware) {
return nullptr;
}
RootedObject mainObj(ccx, underware->GetJSObject());
if (mainObj) {
JSAutoRealm ar(ccx, underware->GetJSObjectGlobal());
// We don't have to root this ID, as it's already rooted by our context.
HandleId id =
ccx.GetContext()->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT);
// If the `wrappedJSObject` property is defined, use the result of getting
// that property, otherwise fall back to the `mainObj` object which is
// directly being wrapped.
RootedValue val(ccx);
if (JS_GetPropertyById(ccx, mainObj, id, &val) && !val.isPrimitive()) {
obj = val.toObjectOrNull();
} else {
obj = mainObj;
}
}
}
return obj;
}
// This is the getter native function we use to handle 'wrappedJSObject' for
// double wrapped JSObjects.
static bool XPC_WN_DoubleWrappedGetter(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.thisv().isObject()) {
JS_ReportErrorASCII(
cx,
"xpconnect double wrapped getter called on incompatible non-object");
return false;
}
RootedObject obj(cx, &args.thisv().toObject());
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION,
"bad function");
RootedObject realObject(cx, GetDoubleWrappedJSObject(ccx, wrapper));
if (!realObject) {
// This is pretty unexpected at this point. The object originally
// responded to this get property call and now gives no object.
// XXX Should this throw something at the caller?
args.rval().setNull();
return true;
}
// It is a double wrapped object. This should really never appear in
// content these days, but addons still do it - see bug 965921.
if (MOZ_UNLIKELY(!nsContentUtils::IsSystemCaller(cx))) {
JS_ReportErrorASCII(cx,
"Attempt to use .wrappedJSObject in untrusted code");
return false;
}
args.rval().setObject(*realObject);
return JS_WrapValue(cx, args.rval());
}
/***************************************************************************/
// This is our shared function to define properties on our JSObjects.
/*
* NOTE:
* We *never* set the tearoff names (e.g. nsIFoo) as JS_ENUMERATE.
* We *never* set toString or toSource as JS_ENUMERATE.
*/
static bool DefinePropertyIfFound(
XPCCallContext& ccx, HandleObject obj, HandleId idArg, XPCNativeSet* set,
XPCNativeInterface* ifaceArg, XPCNativeMember* member,
XPCWrappedNativeScope* scope, bool reflectToStringAndToSource,
XPCWrappedNative* wrapperToReflectInterfaceNames,
XPCWrappedNative* wrapperToReflectDoubleWrap, nsIXPCScriptable* scr,
unsigned propFlags, bool* resolved) {
RootedId id(ccx, idArg);
RefPtr<XPCNativeInterface> iface = ifaceArg;
XPCJSContext* xpccx = ccx.GetContext();
bool found;
const char* name;
propFlags |= JSPROP_RESOLVING;
if (set) {
if (iface) {
found = true;
} else {
found = set->FindMember(id, &member, &iface);
}
} else
found = (nullptr != (member = iface->FindMember(id)));
if (!found) {
if (reflectToStringAndToSource) {
JSNative call;
if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING)) {
call = XPC_WN_Shared_ToString;
name = xpccx->GetStringName(XPCJSContext::IDX_TO_STRING);
} else if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE)) {
call = XPC_WN_Shared_ToSource;
name = xpccx->GetStringName(XPCJSContext::IDX_TO_SOURCE);
} else if (id.isWellKnownSymbol(JS::SymbolCode::toPrimitive)) {
call = XPC_WN_Shared_toPrimitive;
name = "[Symbol.toPrimitive]";
} else {
call = nullptr;
}
if (call) {
RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name));
if (!fun) {
JS_ReportOutOfMemory(ccx);
return false;
}
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
RootedObject value(ccx, JS_GetFunctionObject(fun));
return JS_DefinePropertyById(ccx, obj, id, value,
propFlags & ~JSPROP_ENUMERATE);
}
}
// This *might* be a tearoff name that is not yet part of our
// set. Let's lookup the name and see if it is the name of an
// interface. Then we'll see if the object actually *does* this
// interface and add a tearoff as necessary.
if (wrapperToReflectInterfaceNames) {
JS::UniqueChars name;
RefPtr<XPCNativeInterface> iface2;
XPCWrappedNativeTearOff* to;
RootedObject jso(ccx);
nsresult rv = NS_OK;
bool defineProperty = false;
do {
if (!id.isString()) {
break;
}
name = JS_EncodeStringToLatin1(ccx, id.toString());
if (!name) {
break;
}
iface2 = XPCNativeInterface::GetNewOrUsed(ccx, name.get());
if (!iface2) {
break;
}
to =
wrapperToReflectInterfaceNames->FindTearOff(ccx, iface2, true, &rv);
if (!to) {
break;
}
jso = to->GetJSObject();
if (!jso) {
break;
}
defineProperty = true;
} while (false);
if (defineProperty) {
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return JS_DefinePropertyById(ccx, obj, id, jso,
propFlags & ~JSPROP_ENUMERATE);
} else if (NS_FAILED(rv) && rv != NS_ERROR_NO_INTERFACE) {
return Throw(rv, ccx);
}
}
// This *might* be a double wrapped JSObject
if (wrapperToReflectDoubleWrap &&
id == xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT) &&
GetDoubleWrappedJSObject(ccx, wrapperToReflectDoubleWrap)) {
// We build and add a getter function.
// A security check is done on a per-get basis.
JSFunction* fun;
id = xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT);
name = xpccx->GetStringName(XPCJSContext::IDX_WRAPPED_JSOBJECT);
fun = JS_NewFunction(ccx, XPC_WN_DoubleWrappedGetter, 0, 0, name);
if (!fun) {
return false;
}
RootedObject funobj(ccx, JS_GetFunctionObject(fun));
if (!funobj) {
return false;
}
propFlags &= ~JSPROP_ENUMERATE;
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return JS_DefinePropertyById(ccx, obj, id, funobj, nullptr, propFlags);
}
if (resolved) {
*resolved = false;
}
return true;
}
if (!member) {
if (wrapperToReflectInterfaceNames) {
XPCWrappedNativeTearOff* to =
wrapperToReflectInterfaceNames->FindTearOff(ccx, iface, true);
if (!to) {
return false;
}
RootedObject jso(ccx, to->GetJSObject());
if (!jso) {
return false;
}
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return JS_DefinePropertyById(ccx, obj, id, jso,
propFlags & ~JSPROP_ENUMERATE);
}
if (resolved) {
*resolved = false;
}
return true;
}
if (member->IsConstant()) {
RootedValue val(ccx);
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return member->GetConstantValue(ccx, iface, val.address()) &&
JS_DefinePropertyById(ccx, obj, id, val, propFlags);
}
if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING) ||
id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE) ||
(scr && scr->DontEnumQueryInterface() &&
id == xpccx->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE)))
propFlags &= ~JSPROP_ENUMERATE;
RootedValue funval(ccx);
if (!member->NewFunctionObject(ccx, iface, obj, funval.address())) {
return false;
}
if (member->IsMethod()) {
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return JS_DefinePropertyById(ccx, obj, id, funval, propFlags);
}
// else...
MOZ_ASSERT(member->IsAttribute(), "way broken!");
propFlags &= ~JSPROP_READONLY;
RootedObject funobjGetter(ccx, funval.toObjectOrNull());
RootedObject funobjSetter(ccx);
if (member->IsWritableAttribute()) {
funobjSetter = funobjGetter;
}
AutoResolveName arn(ccx, id);
if (resolved) {
*resolved = true;
}
return JS_DefinePropertyById(ccx, obj, id, funobjGetter, funobjSetter,
propFlags);
}
/***************************************************************************/
/***************************************************************************/
static bool XPC_WN_OnlyIWrite_AddPropertyStub(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v) {
XPCCallContext ccx(cx, obj, nullptr, id);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
// Allow only XPConnect to add/set the property
if (ccx.GetResolveName() == id) {
return true;
}
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
bool XPC_WN_CannotModifyPropertyStub(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v) {
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
bool XPC_WN_CannotDeletePropertyStub(JSContext* cx, HandleObject obj,
HandleId id, ObjectOpResult& result) {
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
bool XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
// Since we aren't going to enumerate tearoff names and the prototype
// handles non-mutated members, we can do this potential short-circuit.
if (!wrapper->HasMutatedSet()) {
return true;
}
XPCNativeSet* set = wrapper->GetSet();
XPCNativeSet* protoSet =
wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr;
uint16_t interface_count = set->GetInterfaceCount();
XPCNativeInterface** interfaceArray = set->GetInterfaceArray();
for (uint16_t i = 0; i < interface_count; i++) {
XPCNativeInterface* iface = interfaceArray[i];
uint16_t member_count = iface->GetMemberCount();
for (uint16_t k = 0; k < member_count; k++) {
XPCNativeMember* member = iface->GetMemberAt(k);
jsid name = member->GetName();
// Skip if this member is going to come from the proto.
uint16_t index;
if (protoSet && protoSet->FindMember(name, nullptr, &index) && index == i)
continue;
JS_MarkCrossZoneId(cx, name);
if (!xpc_ForcePropertyResolve(cx, obj, name)) {
return false;
}
}
}
return true;
}
/***************************************************************************/
enum WNHelperType { WN_NOHELPER, WN_HELPER };
static void WrappedNativeFinalize(JS::GCContext* gcx, JSObject* obj,
WNHelperType helperType) {
const JSClass* clazz = JS::GetClass(obj);
if (clazz->flags & JSCLASS_DOM_GLOBAL) {
mozilla::dom::DestroyProtoAndIfaceCache(obj);
}
XPCWrappedNative* wrapper = JS::GetObjectISupports<XPCWrappedNative>(obj);
if (!wrapper) {
return;
}
if (helperType == WN_HELPER) {
wrapper->GetScriptable()->Finalize(wrapper, gcx, obj);
}
wrapper->FlatJSObjectFinalized();
}
static size_t WrappedNativeObjectMoved(JSObject* obj, JSObject* old) {
XPCWrappedNative* wrapper = JS::GetObjectISupports<XPCWrappedNative>(obj);
if (!wrapper) {
return 0;
}
wrapper->FlatJSObjectMoved(obj, old);
return 0;
}
void XPC_WN_NoHelper_Finalize(JS::GCContext* gcx, JSObject* obj) {
WrappedNativeFinalize(gcx, obj, WN_NOHELPER);
}
/*
* General comment about XPConnect tracing: Given a C++ object |wrapper| and its
* corresponding JS object |obj|, calling |wrapper->TraceSelf| will ask the JS
* engine to mark |obj|. Eventually, this will lead to the trace hook being
* called for |obj|. The trace hook should call |wrapper->TraceInside|, which
* should mark any JS objects held by |wrapper| as members.
*/
/* static */
void XPCWrappedNative::Trace(JSTracer* trc, JSObject* obj) {
const JSClass* clazz = JS::GetClass(obj);
if (clazz->flags & JSCLASS_DOM_GLOBAL) {
mozilla::dom::TraceProtoAndIfaceCache(trc, obj);
}
MOZ_ASSERT(clazz->isWrappedNative());
XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj);
if (wrapper && wrapper->IsValid()) {
wrapper->TraceInside(trc);
}
}
void XPCWrappedNative_Trace(JSTracer* trc, JSObject* obj) {
XPCWrappedNative::Trace(trc, obj);
}
static bool XPC_WN_NoHelper_Resolve(JSContext* cx, HandleObject obj,
HandleId id, bool* resolvedp) {
XPCCallContext ccx(cx, obj, nullptr, id);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
XPCNativeSet* set = ccx.GetSet();
if (!set) {
return true;
}
// Don't resolve properties that are on our prototype.
if (ccx.GetInterface() && !ccx.GetStaticMemberIsLocal()) {
return true;
}
return DefinePropertyIfFound(
ccx, obj, id, set, nullptr, nullptr, wrapper->GetScope(), true, wrapper,
wrapper, nullptr, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT,
resolvedp);
}
static const JSClassOps XPC_WN_NoHelper_JSClassOps = {
XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
XPC_WN_CannotDeletePropertyStub, // delProperty
XPC_WN_Shared_Enumerate, // enumerate
nullptr, // newEnumerate
XPC_WN_NoHelper_Resolve, // resolve
nullptr, // mayResolve
XPC_WN_NoHelper_Finalize, // finalize
nullptr, // call
nullptr, // construct
XPCWrappedNative::Trace, // trace
};
const js::ClassExtension XPC_WN_JSClassExtension = {
WrappedNativeObjectMoved, // objectMovedOp
};
const JSClass XPC_WN_NoHelper_JSClass = {
"XPCWrappedNative_NoHelper",
JSCLASS_IS_WRAPPED_NATIVE | JSCLASS_HAS_RESERVED_SLOTS(1) |
JSCLASS_SLOT0_IS_NSISUPPORTS | JSCLASS_FOREGROUND_FINALIZE,
&XPC_WN_NoHelper_JSClassOps,
JS_NULL_CLASS_SPEC,
&XPC_WN_JSClassExtension,
JS_NULL_OBJECT_OPS};
/***************************************************************************/
bool XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj,
HandleId id, HandleValue v) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
if (ccx.GetResolvingWrapper() == wrapper) {
return true;
}
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
bool XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, HandleObject obj,
HandleId id,
ObjectOpResult& result) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
if (ccx.GetResolvingWrapper() == wrapper) {
return result.succeed();
}
return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx);
}
// macro fun!
#define PRE_HELPER_STUB \
/* It's very important for "unwrapped" to be rooted here. */ \
RootedObject unwrapped(cx, js::CheckedUnwrapDynamic(obj, cx, false)); \
if (!unwrapped) { \
JS_ReportErrorASCII(cx, "Permission denied to operate on object."); \
return false; \
} \
if (!IsWrappedNativeReflector(unwrapped)) { \
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \
} \
XPCWrappedNative* wrapper = XPCWrappedNative::Get(unwrapped); \
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); \
bool retval = true; \
nsresult rv = wrapper->GetScriptable()->
#define POST_HELPER_STUB \
if (NS_FAILED(rv)) return Throw(rv, cx); \
return retval;
bool XPC_WN_Helper_Call(JSContext* cx, unsigned argc, Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
// N.B. we want obj to be the callee, not JS_THIS(cx, vp)
RootedObject obj(cx, &args.callee());
XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(),
args.array(), args.rval().address());
if (!ccx.IsValid()) {
return false;
}
PRE_HELPER_STUB
Call(wrapper, cx, obj, args, &retval);
POST_HELPER_STUB
}
bool XPC_WN_Helper_Construct(JSContext* cx, unsigned argc, Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
RootedObject obj(cx, &args.callee());
if (!obj) {
return false;
}
XPCCallContext ccx(cx, obj, nullptr, JS::VoidHandlePropertyKey, args.length(),
args.array(), args.rval().address());
if (!ccx.IsValid()) {
return false;
}
PRE_HELPER_STUB
Construct(wrapper, cx, obj, args, &retval);
POST_HELPER_STUB
}
static bool XPC_WN_Helper_HasInstance(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.requireAtLeast(cx, "WrappedNative[Symbol.hasInstance]", 1)) {
return false;
}
if (!args.thisv().isObject()) {
JS_ReportErrorASCII(
cx, "WrappedNative[Symbol.hasInstance]: unexpected this value");
return false;
}
RootedObject obj(cx, &args.thisv().toObject());
RootedValue val(cx, args.get(0));
bool retval2;
PRE_HELPER_STUB
HasInstance(wrapper, cx, obj, val, &retval2, &retval);
args.rval().setBoolean(retval2);
POST_HELPER_STUB
}
void XPC_WN_Helper_Finalize(JS::GCContext* gcx, JSObject* obj) {
WrappedNativeFinalize(gcx, obj, WN_HELPER);
}
// RAII class used to store the wrapper in the context when resolving a lazy
// property on its JS reflector. This is used by XPC_WN_MaybeResolving to allow
// adding properties while resolving.
class MOZ_RAII AutoSetResolvingWrapper {
public:
AutoSetResolvingWrapper(XPCCallContext& ccx, XPCWrappedNative* wrapper)
: mCcx(ccx), mOldResolvingWrapper(ccx.SetResolvingWrapper(wrapper)) {}
~AutoSetResolvingWrapper() {
(void)mCcx.SetResolvingWrapper(mOldResolvingWrapper);
}
private:
XPCCallContext& mCcx;
XPCWrappedNative* mOldResolvingWrapper;
};
bool XPC_WN_Helper_Resolve(JSContext* cx, HandleObject obj, HandleId id,
bool* resolvedp) {
nsresult rv = NS_OK;
bool retval = true;
bool resolved = false;
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
RootedId old(cx, ccx.SetResolveName(id));
nsCOMPtr<nsIXPCScriptable> scr = wrapper->GetScriptable();
// Resolve a Symbol.hasInstance property if we want custom `instanceof`
// behavior.
if (scr && scr->WantHasInstance() &&
id.isWellKnownSymbol(SymbolCode::hasInstance)) {
mozilla::Maybe<AutoSetResolvingWrapper> asrw;
if (scr->AllowPropModsDuringResolve()) {
asrw.emplace(ccx, wrapper);
}
if (!JS_DefineFunctionById(
cx, obj, id, XPC_WN_Helper_HasInstance, 1,
JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) {
rv = NS_ERROR_FAILURE;
} else {
resolved = true;
}
}
if (scr && scr->WantResolve()) {
mozilla::Maybe<AutoSetResolvingWrapper> asrw;
if (scr->AllowPropModsDuringResolve()) {
asrw.emplace(ccx, wrapper);
}
rv = scr->Resolve(wrapper, cx, obj, id, &resolved, &retval);
}
old = ccx.SetResolveName(old);
MOZ_ASSERT(old == id, "bad nest");
if (NS_FAILED(rv)) {
return Throw(rv, cx);
}
if (resolved) {
*resolvedp = true;
} else if (wrapper->HasMutatedSet()) {
// We are here if scriptable did not resolve this property and
// it *might* be in the instance set but not the proto set.
XPCNativeSet* set = wrapper->GetSet();
XPCNativeSet* protoSet =
wrapper->HasProto() ? wrapper->GetProto()->GetSet() : nullptr;
XPCNativeMember* member = nullptr;
RefPtr<XPCNativeInterface> iface;
bool IsLocal = false;
if (set->FindMember(id, &member, &iface, protoSet, &IsLocal) && IsLocal) {
XPCWrappedNative* wrapperForInterfaceNames =
(scr && scr->DontReflectInterfaceNames()) ? nullptr : wrapper;
AutoSetResolvingWrapper asrw(ccx, wrapper);
retval = DefinePropertyIfFound(
ccx, obj, id, set, iface, member, wrapper->GetScope(), false,
wrapperForInterfaceNames, nullptr, scr, JSPROP_ENUMERATE, resolvedp);
}
}
return retval;
}
/***************************************************************************/
bool XPC_WN_NewEnumerate(JSContext* cx, HandleObject obj,
MutableHandleIdVector properties,
bool enumerableOnly) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
nsCOMPtr<nsIXPCScriptable> scr = wrapper->GetScriptable();
if (!scr || !scr->WantNewEnumerate()) {
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
}
if (!XPC_WN_Shared_Enumerate(cx, obj)) {
return false;
}
bool retval = true;
nsresult rv =
scr->NewEnumerate(wrapper, cx, obj, properties, enumerableOnly, &retval);
if (NS_FAILED(rv)) {
return Throw(rv, cx);
}
return retval;
}
/***************************************************************************/
/***************************************************************************/
// Compatibility hack.
//
// XPConnect used to do all sorts of funny tricks to find the "correct"
// |this| object for a given method (often to the detriment of proper
// call/apply). When these tricks were removed, a fair amount of chrome
// code broke, because it was relying on being able to grab methods off
// some XPCOM object (like the nsITelemetry service) and invoke them without
// a proper |this|. So, if it's quite clear that we're in this situation and
// about to use a |this| argument that just won't work, fix things up.
//
// This hack is only useful for getters/setters if someone sets an XPCOM object
// as the prototype for a vanilla JS object and expects the XPCOM attributes to
// work on the derived object, which we really don't want to support. But we
// handle it anyway, for now, to minimize regression risk on an already-risky
// landing.
//
// This hack is mainly useful for the NoHelper JSClass. We also fix up
// Components.utils because it implements nsIXPCScriptable (giving it a custom
// JSClass) but not nsIClassInfo (which would put the methods on a prototype).
#define IS_NOHELPER_CLASS(clasp) (clasp == &XPC_WN_NoHelper_JSClass)
#define IS_CU_CLASS(clasp) \
(clasp->name[0] == 'n' && !strcmp(clasp->name, "nsXPCComponents_Utils"))
MOZ_ALWAYS_INLINE JSObject* FixUpThisIfBroken(JSObject* obj, JSObject* funobj) {
if (funobj) {
JSObject* parentObj =
&js::GetFunctionNativeReserved(funobj, XPC_FUNCTION_PARENT_OBJECT_SLOT)
.toObject();
const JSClass* parentClass = JS::GetClass(parentObj);
if (MOZ_UNLIKELY(
(IS_NOHELPER_CLASS(parentClass) || IS_CU_CLASS(parentClass)) &&
(JS::GetClass(obj) != parentClass))) {
return parentObj;
}
}
return obj;
}
bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION,
"bad function");
RootedObject funobj(cx, &args.callee());
RootedObject obj(cx);
if (!args.computeThis(cx, &obj)) {
return false;
}
obj = FixUpThisIfBroken(obj, funobj);
XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(),
args.array(), vp);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
RefPtr<XPCNativeInterface> iface;
XPCNativeMember* member;
if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) {
return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx);
}
ccx.SetCallInfo(iface, member, false);
return XPCWrappedNative::CallMethod(ccx);
}
bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, Value* vp) {
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION,
"bad function");
RootedObject funobj(cx, &args.callee());
if (!args.thisv().isObject()) {
JS_ReportErrorASCII(
cx, "xpconnect getter/setter called on incompatible non-object");
return false;
}
RootedObject obj(cx, &args.thisv().toObject());
obj = FixUpThisIfBroken(obj, funobj);
XPCCallContext ccx(cx, obj, funobj, JS::VoidHandlePropertyKey, args.length(),
args.array(), vp);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
RefPtr<XPCNativeInterface> iface;
XPCNativeMember* member;
if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) {
return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx);
}
if (args.length() != 0 && member->IsWritableAttribute()) {
ccx.SetCallInfo(iface, member, true);
bool retval = XPCWrappedNative::SetAttribute(ccx);
if (retval) {
args.rval().set(args[0]);
}
return retval;
}
// else...
ccx.SetCallInfo(iface, member, false);
return XPCWrappedNative::GetAttribute(ccx);
}
/***************************************************************************/
/* static */
XPCWrappedNativeProto* XPCWrappedNativeProto::Get(JSObject* obj) {
MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass);
return JS::GetMaybePtrFromReservedSlot<XPCWrappedNativeProto>(obj, ProtoSlot);
}
/* static */
XPCWrappedNativeTearOff* XPCWrappedNativeTearOff::Get(JSObject* obj) {
MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Tearoff_JSClass);
return JS::GetMaybePtrFromReservedSlot<XPCWrappedNativeTearOff>(obj,
TearOffSlot);
}
static bool XPC_WN_Proto_Enumerate(JSContext* cx, HandleObject obj) {
MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto");
XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj);
if (!self) {
return false;
}
XPCNativeSet* set = self->GetSet();
if (!set) {
return false;
}
XPCCallContext ccx(cx);
if (!ccx.IsValid()) {
return false;
}
uint16_t interface_count = set->GetInterfaceCount();
XPCNativeInterface** interfaceArray = set->GetInterfaceArray();
for (uint16_t i = 0; i < interface_count; i++) {
XPCNativeInterface* iface = interfaceArray[i];
uint16_t member_count = iface->GetMemberCount();
for (uint16_t k = 0; k < member_count; k++) {
jsid name = iface->GetMemberAt(k)->GetName();
JS_MarkCrossZoneId(cx, name);
if (!xpc_ForcePropertyResolve(cx, obj, name)) {
return false;
}
}
}
return true;
}
static void XPC_WN_Proto_Finalize(JS::GCContext* gcx, JSObject* obj) {
// This can be null if xpc shutdown has already happened
XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj);
if (p) {
p->JSProtoObjectFinalized(gcx, obj);
}
}
static size_t XPC_WN_Proto_ObjectMoved(JSObject* obj, JSObject* old) {
// This can be null if xpc shutdown has already happened
XPCWrappedNativeProto* p = XPCWrappedNativeProto::Get(obj);
if (!p) {
return 0;
}
p->JSProtoObjectMoved(obj, old);
return 0;
}
/*****************************************************/
static bool XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx,
HandleObject obj,
HandleId id,
HandleValue v) {
MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto");
XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj);
if (!self) {
return false;
}
XPCCallContext ccx(cx);
if (!ccx.IsValid()) {
return false;
}
// Allow XPConnect to add the property only
if (ccx.GetResolveName() == id) {
return true;
}
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
}
static bool XPC_WN_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id,
bool* resolvedp) {
MOZ_ASSERT(JS::GetClass(obj) == &XPC_WN_Proto_JSClass, "bad proto");
XPCWrappedNativeProto* self = XPCWrappedNativeProto::Get(obj);
if (!self) {
return false;
}
XPCCallContext ccx(cx);
if (!ccx.IsValid()) {
return false;
}
nsCOMPtr<nsIXPCScriptable> scr = self->GetScriptable();
return DefinePropertyIfFound(
ccx, obj, id, self->GetSet(), nullptr, nullptr, self->GetScope(), true,
nullptr, nullptr, scr,
JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE, resolvedp);
}
static const JSClassOps XPC_WN_Proto_JSClassOps = {
XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty
XPC_WN_CannotDeletePropertyStub, // delProperty
XPC_WN_Proto_Enumerate, // enumerate
nullptr, // newEnumerate
XPC_WN_Proto_Resolve, // resolve
nullptr, // mayResolve
XPC_WN_Proto_Finalize, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
static const js::ClassExtension XPC_WN_Proto_ClassExtension = {
XPC_WN_Proto_ObjectMoved, // objectMovedOp
};
const JSClass XPC_WN_Proto_JSClass = {
"XPC_WN_Proto_JSClass",
JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeProto::SlotCount) |
JSCLASS_FOREGROUND_FINALIZE,
&XPC_WN_Proto_JSClassOps,
JS_NULL_CLASS_SPEC,
&XPC_WN_Proto_ClassExtension,
JS_NULL_OBJECT_OPS};
/***************************************************************************/
static bool XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
XPCWrappedNativeTearOff* to = ccx.GetTearOff();
XPCNativeInterface* iface;
if (!to || nullptr == (iface = to->GetInterface())) {
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
}
uint16_t member_count = iface->GetMemberCount();
for (uint16_t k = 0; k < member_count; k++) {
jsid name = iface->GetMemberAt(k)->GetName();
JS_MarkCrossZoneId(cx, name);
if (!xpc_ForcePropertyResolve(cx, obj, name)) {
return false;
}
}
return true;
}
static bool XPC_WN_TearOff_Resolve(JSContext* cx, HandleObject obj, HandleId id,
bool* resolvedp) {
XPCCallContext ccx(cx, obj);
XPCWrappedNative* wrapper = ccx.GetWrapper();
THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
XPCWrappedNativeTearOff* to = ccx.GetTearOff();
XPCNativeInterface* iface;
if (!to || nullptr == (iface = to->GetInterface())) {
return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
}
return DefinePropertyIfFound(
ccx, obj, id, nullptr, iface, nullptr, wrapper->GetScope(), true, nullptr,
nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE,
resolvedp);
}
static void XPC_WN_TearOff_Finalize(JS::GCContext* gcx, JSObject* obj) {
XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj);
if (!p) {
return;
}
p->JSObjectFinalized();
}
static size_t XPC_WN_TearOff_ObjectMoved(JSObject* obj, JSObject* old) {
XPCWrappedNativeTearOff* p = XPCWrappedNativeTearOff::Get(obj);
if (!p) {
return 0;
}
p->JSObjectMoved(obj, old);
return 0;
}
static const JSClassOps XPC_WN_Tearoff_JSClassOps = {
XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty
XPC_WN_CannotDeletePropertyStub, // delProperty
XPC_WN_TearOff_Enumerate, // enumerate
nullptr, // newEnumerate
XPC_WN_TearOff_Resolve, // resolve
nullptr, // mayResolve
XPC_WN_TearOff_Finalize, // finalize
nullptr, // call
nullptr, // construct
nullptr, // trace
};
static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = {
XPC_WN_TearOff_ObjectMoved, // objectMovedOp
};
const JSClass XPC_WN_Tearoff_JSClass = {
"WrappedNative_TearOff",
JSCLASS_HAS_RESERVED_SLOTS(XPCWrappedNativeTearOff::SlotCount) |
JSCLASS_FOREGROUND_FINALIZE,
&XPC_WN_Tearoff_JSClassOps, JS_NULL_CLASS_SPEC,
&XPC_WN_Tearoff_JSClassExtension};