gecko-dev/js/ipc/WrapperOwner.cpp

1238 lines
35 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=4 sw=4 et 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 "WrapperOwner.h"
#include "JavaScriptLogging.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/BindingUtils.h"
#include "jsfriendapi.h"
#include "js/CharacterEncoding.h"
#include "xpcprivate.h"
#include "CPOWTimer.h"
#include "WrapperFactory.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocument.h"
using namespace js;
using namespace JS;
using namespace mozilla;
using namespace mozilla::jsipc;
struct AuxCPOWData
{
ObjectId id;
bool isCallable;
bool isConstructor;
bool isDOMObject;
// The object tag is just some auxilliary information that clients can use
// however they see fit.
nsCString objectTag;
// The class name for WrapperOwner::className, below.
nsCString className;
AuxCPOWData(ObjectId id,
bool isCallable,
bool isConstructor,
bool isDOMObject,
const nsACString& objectTag)
: id(id),
isCallable(isCallable),
isConstructor(isConstructor),
isDOMObject(isDOMObject),
objectTag(objectTag)
{}
};
WrapperOwner::WrapperOwner()
: inactive_(false)
{
}
static inline AuxCPOWData*
AuxCPOWDataOf(JSObject* obj)
{
MOZ_ASSERT(IsCPOW(obj));
return static_cast<AuxCPOWData*>(GetProxyReservedSlot(obj, 1).toPrivate());
}
static inline WrapperOwner*
OwnerOf(JSObject* obj)
{
MOZ_ASSERT(IsCPOW(obj));
return reinterpret_cast<WrapperOwner*>(GetProxyReservedSlot(obj, 0).toPrivate());
}
ObjectId
WrapperOwner::idOfUnchecked(JSObject* obj)
{
MOZ_ASSERT(IsCPOW(obj));
AuxCPOWData* aux = AuxCPOWDataOf(obj);
MOZ_ASSERT(!aux->id.isNull());
return aux->id;
}
ObjectId
WrapperOwner::idOf(JSObject* obj)
{
ObjectId objId = idOfUnchecked(obj);
MOZ_ASSERT(hasCPOW(objId, obj));
return objId;
}
class CPOWProxyHandler : public BaseProxyHandler
{
public:
constexpr CPOWProxyHandler()
: BaseProxyHandler(&family) {}
virtual bool finalizeInBackground(const Value& priv) const override {
return false;
}
virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const override;
virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const override;
virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
AutoIdVector& props) const override;
virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
ObjectOpResult& result) const override;
virtual JSObject* enumerate(JSContext* cx, HandleObject proxy) const override;
virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
ObjectOpResult& result) const override;
virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
HandleId id, MutableHandleValue vp) const override;
virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result) const override;
virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const override;
virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
AutoIdVector& props) const override;
virtual bool hasInstance(JSContext* cx, HandleObject proxy,
MutableHandleValue v, bool* bp) const override;
virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override;
virtual bool isArray(JSContext* cx, HandleObject obj,
IsArrayAnswer* answer) const override;
virtual const char* className(JSContext* cx, HandleObject proxy) const override;
virtual RegExpShared* regexp_toShared(JSContext* cx, HandleObject proxy) const override;
virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
virtual size_t objectMoved(JSObject* proxy, JSObject* old) const override;
virtual bool isCallable(JSObject* obj) const override;
virtual bool isConstructor(JSObject* obj) const override;
virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject protop) const override;
static const char family;
static const CPOWProxyHandler singleton;
};
const char CPOWProxyHandler::family = 0;
const CPOWProxyHandler CPOWProxyHandler::singleton;
#define FORWARD(call, args, failRetVal) \
AUTO_PROFILER_LABEL(__func__, JS); \
WrapperOwner* owner = OwnerOf(proxy); \
if (!owner->active()) { \
JS_ReportErrorASCII(cx, "cannot use a CPOW whose process is gone"); \
return failRetVal; \
} \
if (!owner->allowMessage(cx)) { \
return failRetVal; \
} \
{ \
CPOWTimer timer(cx); \
return owner->call args; \
}
bool
CPOWProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
FORWARD(getPropertyDescriptor, (cx, proxy, id, desc), false);
}
bool
WrapperOwner::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
ReturnStatus status;
PPropertyDescriptor result;
if (!SendGetPropertyDescriptor(objId, idVar, &status, &result))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
return toDescriptor(cx, result, desc);
}
bool
CPOWProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc) const
{
FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc), false);
}
bool
WrapperOwner::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
MutableHandle<PropertyDescriptor> desc)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
ReturnStatus status;
PPropertyDescriptor result;
if (!SendGetOwnPropertyDescriptor(objId, idVar, &status, &result))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
return toDescriptor(cx, result, desc);
}
bool
CPOWProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result) const
{
FORWARD(defineProperty, (cx, proxy, id, desc, result), false);
}
bool
WrapperOwner::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
Handle<PropertyDescriptor> desc,
ObjectOpResult& result)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
PPropertyDescriptor descriptor;
if (!fromDescriptor(cx, desc, &descriptor))
return false;
ReturnStatus status;
if (!SendDefineProperty(objId, idVar, descriptor, &status))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status, result);
}
bool
CPOWProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
AutoIdVector& props) const
{
FORWARD(ownPropertyKeys, (cx, proxy, props), false);
}
bool
WrapperOwner::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
{
return getPropertyKeys(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
}
bool
CPOWProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
ObjectOpResult& result) const
{
FORWARD(delete_, (cx, proxy, id, result), false);
}
bool
WrapperOwner::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
ReturnStatus status;
if (!SendDelete(objId, idVar, &status))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status, result);
}
JSObject*
CPOWProxyHandler::enumerate(JSContext* cx, HandleObject proxy) const
{
// Using a CPOW for the Iterator would slow down for .. in performance, instead
// call the base hook, that will use our implementation of getOwnEnumerablePropertyKeys
// and follow the proto chain.
return BaseProxyHandler::enumerate(cx, proxy);
}
bool
CPOWProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
{
FORWARD(has, (cx, proxy, id, bp), false);
}
bool
WrapperOwner::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
ReturnStatus status;
if (!SendHas(objId, idVar, &status, bp))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status);
}
bool
CPOWProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
{
FORWARD(hasOwn, (cx, proxy, id, bp), false);
}
bool
WrapperOwner::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
ReturnStatus status;
if (!SendHasOwn(objId, idVar, &status, bp))
return ipcfail(cx);
LOG_STACK();
return !!ok(cx, status);
}
bool
CPOWProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
HandleId id, MutableHandleValue vp) const
{
FORWARD(get, (cx, proxy, receiver, id, vp), false);
}
static bool
CPOWDOMQI(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.thisv().isObject() || !IsCPOW(&args.thisv().toObject())) {
JS_ReportErrorASCII(cx, "bad this object passed to special QI");
return false;
}
RootedObject proxy(cx, &args.thisv().toObject());
FORWARD(DOMQI, (cx, proxy, args), false);
}
static bool
CPOWToString(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedObject callee(cx, &args.callee());
RootedValue cpowValue(cx);
if (!JS_GetProperty(cx, callee, "__cpow__", &cpowValue))
return false;
if (!cpowValue.isObject() || !IsCPOW(&cpowValue.toObject())) {
JS_ReportErrorASCII(cx, "CPOWToString called on an incompatible object");
return false;
}
RootedObject proxy(cx, &cpowValue.toObject());
FORWARD(toString, (cx, proxy, args), false);
}
bool
WrapperOwner::toString(JSContext* cx, HandleObject cpow, JS::CallArgs& args)
{
// Ask the other side to call its toString method. Update the callee so that
// it points to the CPOW and not to the synthesized CPOWToString function.
args.setCallee(ObjectValue(*cpow));
if (!callOrConstruct(cx, cpow, args, false))
return false;
if (!args.rval().isString())
return true;
RootedString cpowResult(cx, args.rval().toString());
nsAutoJSString toStringResult;
if (!toStringResult.init(cx, cpowResult))
return false;
// We don't want to wrap toString() results for things like the location
// object, where toString() is supposed to return a URL and nothing else.
nsAutoString result;
if (toStringResult[0] == '[') {
result.AppendLiteral("[object CPOW ");
result += toStringResult;
result.AppendLiteral("]");
} else {
result += toStringResult;
}
JSString* str = JS_NewUCStringCopyN(cx, result.get(), result.Length());
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
WrapperOwner::DOMQI(JSContext* cx, JS::HandleObject proxy, JS::CallArgs& args)
{
// Someone's calling us, handle nsISupports specially to avoid unnecessary
// CPOW traffic.
HandleValue id = args[0];
if (id.isObject()) {
RootedObject idobj(cx, &id.toObject());
nsCOMPtr<nsIJSID> jsid;
nsresult rv = UnwrapArg<nsIJSID>(cx, idobj, getter_AddRefs(jsid));
if (NS_SUCCEEDED(rv)) {
MOZ_ASSERT(jsid, "bad wrapJS");
const nsID* idptr = jsid->GetID();
if (idptr->Equals(NS_GET_IID(nsISupports))) {
args.rval().set(args.thisv());
return true;
}
// Webidl-implemented DOM objects never have nsIClassInfo.
if (idptr->Equals(NS_GET_IID(nsIClassInfo)))
return Throw(cx, NS_ERROR_NO_INTERFACE);
}
}
// It wasn't nsISupports, call into the other process to do the QI for us
// (since we don't know what other interfaces our object supports). Note
// that we have to use JS_GetPropertyDescriptor here to avoid infinite
// recursion back into CPOWDOMQI via WrapperOwner::get().
// We could stash the actual QI function on our own function object to avoid
// if we're called multiple times, but since we're transient, there's no
// point right now.
JS::Rooted<PropertyDescriptor> propDesc(cx);
if (!JS_GetPropertyDescriptor(cx, proxy, "QueryInterface", &propDesc))
return false;
if (!propDesc.value().isObject()) {
MOZ_ASSERT_UNREACHABLE("We didn't get QueryInterface off a node");
return Throw(cx, NS_ERROR_UNEXPECTED);
}
return JS_CallFunctionValue(cx, proxy, propDesc.value(), args, args.rval());
}
bool
WrapperOwner::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
HandleId id, MutableHandleValue vp)
{
ObjectId objId = idOf(proxy);
JSVariant receiverVar;
if (!toVariant(cx, receiver, &receiverVar))
return false;
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
AuxCPOWData* data = AuxCPOWDataOf(proxy);
if (data->isDOMObject &&
idVar.type() == JSIDVariant::TnsString &&
idVar.get_nsString().EqualsLiteral("QueryInterface"))
{
// Handle QueryInterface on DOM Objects specially since we can assume
// certain things about their implementation.
RootedFunction qi(cx, JS_NewFunction(cx, CPOWDOMQI, 1, 0,
"QueryInterface"));
if (!qi)
return false;
vp.set(ObjectValue(*JS_GetFunctionObject(qi)));
return true;
}
JSVariant val;
ReturnStatus status;
if (!SendGet(objId, receiverVar, idVar, &status, &val))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
if (!fromVariant(cx, val, vp))
return false;
if (idVar.type() == JSIDVariant::TnsString &&
idVar.get_nsString().EqualsLiteral("toString")) {
RootedFunction toString(cx, JS_NewFunction(cx, CPOWToString, 0, 0,
"toString"));
if (!toString)
return false;
RootedObject toStringObj(cx, JS_GetFunctionObject(toString));
if (!JS_DefineProperty(cx, toStringObj, "__cpow__", vp, JSPROP_PERMANENT | JSPROP_READONLY))
return false;
vp.set(ObjectValue(*toStringObj));
}
return true;
}
bool
CPOWProxyHandler::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result) const
{
FORWARD(set, (cx, proxy, id, v, receiver, result), false);
}
bool
WrapperOwner::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
JS::HandleValue receiver, JS::ObjectOpResult& result)
{
ObjectId objId = idOf(proxy);
JSIDVariant idVar;
if (!toJSIDVariant(cx, id, &idVar))
return false;
JSVariant val;
if (!toVariant(cx, v, &val))
return false;
JSVariant receiverVar;
if (!toVariant(cx, receiver, &receiverVar))
return false;
ReturnStatus status;
if (!SendSet(objId, idVar, val, receiverVar, &status))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status, result);
}
bool
CPOWProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
AutoIdVector& props) const
{
FORWARD(getOwnEnumerablePropertyKeys, (cx, proxy, props), false);
}
bool
WrapperOwner::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
{
return getPropertyKeys(cx, proxy, JSITER_OWNONLY, props);
}
bool
CPOWProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
{
FORWARD(preventExtensions, (cx, proxy, result), false);
}
bool
WrapperOwner::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result)
{
ObjectId objId = idOf(proxy);
ReturnStatus status;
if (!SendPreventExtensions(objId, &status))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status, result);
}
bool
CPOWProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
{
FORWARD(isExtensible, (cx, proxy, extensible), false);
}
bool
WrapperOwner::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible)
{
ObjectId objId = idOf(proxy);
ReturnStatus status;
if (!SendIsExtensible(objId, &status, extensible))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status);
}
bool
CPOWProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
{
FORWARD(callOrConstruct, (cx, proxy, args, false), false);
}
bool
CPOWProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
{
FORWARD(callOrConstruct, (cx, proxy, args, true), false);
}
bool
WrapperOwner::callOrConstruct(JSContext* cx, HandleObject proxy, const CallArgs& args,
bool construct)
{
ObjectId objId = idOf(proxy);
InfallibleTArray<JSParam> vals;
AutoValueVector outobjects(cx);
RootedValue v(cx);
for (size_t i = 0; i < args.length() + 2; i++) {
// The |this| value for constructors is a magic value that we won't be
// able to convert, so skip it.
if (i == 1 && construct)
v = UndefinedValue();
else
v = args.base()[i];
if (v.isObject()) {
RootedObject obj(cx, &v.toObject());
if (xpc::IsOutObject(cx, obj)) {
// Make sure it is not an in-out object.
bool found;
if (!JS_HasProperty(cx, obj, "value", &found))
return false;
if (found) {
JS_ReportErrorASCII(cx, "in-out objects cannot be sent via CPOWs yet");
return false;
}
vals.AppendElement(JSParam(void_t()));
if (!outobjects.append(ObjectValue(*obj)))
return false;
continue;
}
}
JSVariant val;
if (!toVariant(cx, v, &val))
return false;
vals.AppendElement(JSParam(val));
}
JSVariant result;
ReturnStatus status;
InfallibleTArray<JSParam> outparams;
if (!SendCallOrConstruct(objId, vals, construct, &status, &result, &outparams))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
if (outparams.Length() != outobjects.length())
return ipcfail(cx);
RootedObject obj(cx);
for (size_t i = 0; i < outparams.Length(); i++) {
// Don't bother doing anything for outparams that weren't set.
if (outparams[i].type() == JSParam::Tvoid_t)
continue;
// Take the value the child process returned, and set it on the XPC
// object.
if (!fromVariant(cx, outparams[i], &v))
return false;
obj = &outobjects[i].toObject();
if (!JS_SetProperty(cx, obj, "value", v))
return false;
}
if (!fromVariant(cx, result, args.rval()))
return false;
return true;
}
bool
CPOWProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const
{
FORWARD(hasInstance, (cx, proxy, v, bp), false);
}
bool
WrapperOwner::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp)
{
ObjectId objId = idOf(proxy);
JSVariant vVar;
if (!toVariant(cx, v, &vVar))
return false;
ReturnStatus status;
if (!SendHasInstance(objId, vVar, &status, bp))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status);
}
bool
CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
{
FORWARD(getBuiltinClass, (cx, proxy, cls), false);
}
bool
WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
{
ObjectId objId = idOf(proxy);
uint32_t classValue = uint32_t(ESClass::Other);
ReturnStatus status;
if (!SendGetBuiltinClass(objId, &status, &classValue))
return ipcfail(cx);
*cls = ESClass(classValue);
LOG_STACK();
return ok(cx, status);
}
bool
CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
IsArrayAnswer* answer) const
{
FORWARD(isArray, (cx, proxy, answer), false);
}
bool
WrapperOwner::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer)
{
ObjectId objId = idOf(proxy);
uint32_t ans;
ReturnStatus status;
if (!SendIsArray(objId, &status, &ans))
return ipcfail(cx);
LOG_STACK();
*answer = IsArrayAnswer(ans);
MOZ_ASSERT(*answer == IsArrayAnswer::Array ||
*answer == IsArrayAnswer::NotArray ||
*answer == IsArrayAnswer::RevokedProxy);
return ok(cx, status);
}
const char*
CPOWProxyHandler::className(JSContext* cx, HandleObject proxy) const
{
WrapperOwner* parent = OwnerOf(proxy);
if (!parent->active())
return "<dead CPOW>";
return parent->className(cx, proxy);
}
const char*
WrapperOwner::className(JSContext* cx, HandleObject proxy)
{
AuxCPOWData* data = AuxCPOWDataOf(proxy);
if (data->className.IsEmpty()) {
ObjectId objId = idOf(proxy);
if (!SendClassName(objId, &data->className))
return "<error>";
LOG_STACK();
}
return data->className.get();
}
bool
CPOWProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
{
FORWARD(getPrototype, (cx, proxy, objp), false);
}
bool
WrapperOwner::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
{
ObjectId objId = idOf(proxy);
ObjectOrNullVariant val;
ReturnStatus status;
if (!SendGetPrototype(objId, &status, &val))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
objp.set(fromObjectOrNullVariant(cx, val));
return true;
}
bool
CPOWProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject objp) const
{
FORWARD(getPrototypeIfOrdinary, (cx, proxy, isOrdinary, objp), false);
}
bool
WrapperOwner::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
MutableHandleObject objp)
{
ObjectId objId = idOf(proxy);
ObjectOrNullVariant val;
ReturnStatus status;
if (!SendGetPrototypeIfOrdinary(objId, &status, isOrdinary, &val))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
objp.set(fromObjectOrNullVariant(cx, val));
return true;
}
RegExpShared*
CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy) const
{
FORWARD(regexp_toShared, (cx, proxy), nullptr);
}
RegExpShared*
WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy)
{
ObjectId objId = idOf(proxy);
ReturnStatus status;
nsString source;
unsigned flags = 0;
if (!SendRegExpToShared(objId, &status, &source, &flags)) {
MOZ_ALWAYS_FALSE(ipcfail(cx));
return nullptr;
}
LOG_STACK();
if (!ok(cx, status))
return nullptr;
RootedObject regexp(cx);
regexp = JS_NewUCRegExpObject(cx, source.get(), source.Length(), flags);
if (!regexp)
return nullptr;
return js::RegExpToSharedNonInline(cx, regexp);
}
void
CPOWProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
{
AuxCPOWData* aux = AuxCPOWDataOf(proxy);
OwnerOf(proxy)->drop(proxy);
if (aux)
delete aux;
}
size_t
CPOWProxyHandler::objectMoved(JSObject* proxy, JSObject* old) const
{
OwnerOf(proxy)->updatePointer(proxy, old);
return 0;
}
bool
CPOWProxyHandler::isCallable(JSObject* proxy) const
{
AuxCPOWData* aux = AuxCPOWDataOf(proxy);
return aux->isCallable;
}
bool
CPOWProxyHandler::isConstructor(JSObject* proxy) const
{
AuxCPOWData* aux = AuxCPOWDataOf(proxy);
return aux->isConstructor;
}
void
WrapperOwner::drop(JSObject* obj)
{
// The association may have already been swept from the table but if it's
// there then remove it.
ObjectId objId = idOfUnchecked(obj);
if (cpows_.findPreserveColor(objId) == obj)
cpows_.remove(objId);
if (active())
Unused << SendDropObject(objId);
decref();
}
void
WrapperOwner::updatePointer(JSObject* obj, const JSObject* old)
{
ObjectId objId = idOfUnchecked(obj);
MOZ_ASSERT(hasCPOW(objId, old));
cpows_.add(objId, obj);
}
bool
WrapperOwner::init()
{
if (!JavaScriptShared::init())
return false;
return true;
}
bool
WrapperOwner::getPropertyKeys(JSContext* cx, HandleObject proxy, uint32_t flags, AutoIdVector& props)
{
ObjectId objId = idOf(proxy);
ReturnStatus status;
InfallibleTArray<JSIDVariant> ids;
if (!SendGetPropertyKeys(objId, flags, &status, &ids))
return ipcfail(cx);
LOG_STACK();
if (!ok(cx, status))
return false;
for (size_t i = 0; i < ids.Length(); i++) {
RootedId id(cx);
if (!fromJSIDVariant(cx, ids[i], &id))
return false;
if (!props.append(id))
return false;
}
return true;
}
namespace mozilla {
namespace jsipc {
bool
IsCPOW(JSObject* obj)
{
return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
}
bool
IsWrappedCPOW(JSObject* obj)
{
JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
if (!unwrapped)
return false;
return IsCPOW(unwrapped);
}
void
GetWrappedCPOWTag(JSObject* obj, nsACString& out)
{
JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
MOZ_ASSERT(IsCPOW(unwrapped));
AuxCPOWData* aux = AuxCPOWDataOf(unwrapped);
if (aux)
out = aux->objectTag;
}
nsresult
InstanceOf(JSObject* proxy, const nsID* id, bool* bp)
{
WrapperOwner* parent = OwnerOf(proxy);
if (!parent->active())
return NS_ERROR_UNEXPECTED;
return parent->instanceOf(proxy, id, bp);
}
bool
DOMInstanceOf(JSContext* cx, JSObject* proxyArg, int prototypeID, int depth, bool* bp)
{
RootedObject proxy(cx, proxyArg);
FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp), false);
}
} /* namespace jsipc */
} /* namespace mozilla */
nsresult
WrapperOwner::instanceOf(JSObject* obj, const nsID* id, bool* bp)
{
ObjectId objId = idOf(obj);
JSIID iid;
ConvertID(*id, &iid);
ReturnStatus status;
if (!SendInstanceOf(objId, iid, &status, bp))
return NS_ERROR_UNEXPECTED;
if (status.type() != ReturnStatus::TReturnSuccess)
return NS_ERROR_UNEXPECTED;
return NS_OK;
}
bool
WrapperOwner::domInstanceOf(JSContext* cx, JSObject* obj, int prototypeID, int depth, bool* bp)
{
ObjectId objId = idOf(obj);
ReturnStatus status;
if (!SendDOMInstanceOf(objId, prototypeID, depth, &status, bp))
return ipcfail(cx);
LOG_STACK();
return ok(cx, status);
}
void
WrapperOwner::ActorDestroy(ActorDestroyReason why)
{
inactive_ = true;
objects_.clear();
unwaivedObjectIds_.clear();
waivedObjectIds_.clear();
}
bool
WrapperOwner::ipcfail(JSContext* cx)
{
JS_ReportErrorASCII(cx, "cross-process JS call failed");
return false;
}
bool
WrapperOwner::ok(JSContext* cx, const ReturnStatus& status)
{
if (status.type() == ReturnStatus::TReturnSuccess)
return true;
if (status.type() == ReturnStatus::TReturnDeadCPOW) {
JS_ReportErrorASCII(cx, "operation not possible on dead CPOW");
return false;
}
RootedValue exn(cx);
if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
return false;
JS_SetPendingException(cx, exn);
return false;
}
bool
WrapperOwner::ok(JSContext* cx, const ReturnStatus& status, ObjectOpResult& result)
{
if (status.type() == ReturnStatus::TReturnObjectOpResult)
return result.fail(status.get_ReturnObjectOpResult().code());
if (!ok(cx, status))
return false;
return result.succeed();
}
// CPOWs can have a tag string attached to them, originating in the local
// process from this function. It's sent with the CPOW to the remote process,
// where it can be fetched with Components.utils.getCrossProcessWrapperTag.
static nsCString
GetRemoteObjectTag(JS::Handle<JSObject*> obj)
{
if (nsCOMPtr<nsISupports> supports = xpc::UnwrapReflectorToISupports(obj)) {
nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(supports));
if (treeItem)
return NS_LITERAL_CSTRING("ContentDocShellTreeItem");
nsCOMPtr<nsIDocument> doc(do_QueryInterface(supports));
if (doc)
return NS_LITERAL_CSTRING("ContentDocument");
}
return NS_LITERAL_CSTRING("generic");
}
static RemoteObject
MakeRemoteObject(JSContext* cx, ObjectId id, HandleObject obj)
{
return RemoteObject(id.serialize(),
JS::IsCallable(obj),
JS::IsConstructor(obj),
dom::IsDOMObject(obj),
GetRemoteObjectTag(obj));
}
bool
WrapperOwner::toObjectVariant(JSContext* cx, JSObject* objArg, ObjectVariant* objVarp)
{
RootedObject obj(cx, objArg);
MOZ_ASSERT(obj);
// We always save objects unwrapped in the CPOW table. If we stored
// wrappers, then the wrapper might be GCed while the target remained alive.
// Whenever operating on an object that comes from the table, we wrap it
// in findObjectById.
unsigned wrapperFlags = 0;
obj = js::UncheckedUnwrap(obj, true, &wrapperFlags);
if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
*objVarp = LocalObject(idOf(obj).serialize());
return true;
}
bool waiveXray = wrapperFlags & xpc::WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG;
ObjectId id = objectIdMap(waiveXray).find(obj);
if (!id.isNull()) {
MOZ_ASSERT(id.hasXrayWaiver() == waiveXray);
*objVarp = MakeRemoteObject(cx, id, obj);
return true;
}
// Need to call PreserveWrapper on |obj| in case it's a reflector.
// FIXME: What if it's an XPCWrappedNative?
if (mozilla::dom::IsDOMObject(obj))
mozilla::dom::TryPreserveWrapper(obj);
id = ObjectId(nextSerialNumber_++, waiveXray);
if (!objects_.add(id, obj))
return false;
if (!objectIdMap(waiveXray).add(cx, obj, id))
return false;
*objVarp = MakeRemoteObject(cx, id, obj);
return true;
}
JSObject*
WrapperOwner::fromObjectVariant(JSContext* cx, const ObjectVariant& objVar)
{
if (objVar.type() == ObjectVariant::TRemoteObject) {
return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
} else {
return fromLocalObjectVariant(cx, objVar.get_LocalObject());
}
}
JSObject*
WrapperOwner::fromRemoteObjectVariant(JSContext* cx, const RemoteObject& objVar)
{
ObjectId objId = ObjectId::deserialize(objVar.serializedId());
RootedObject obj(cx, findCPOWById(objId));
if (!obj) {
// All CPOWs live in the privileged junk scope.
RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
JSAutoRealm ar(cx, junkScope);
RootedValue v(cx, UndefinedValue());
// We need to setLazyProto for the getPrototype/getPrototypeIfOrdinary
// hooks.
ProxyOptions options;
options.setLazyProto(true);
obj = NewProxyObject(cx,
&CPOWProxyHandler::singleton,
v,
nullptr,
options);
if (!obj)
return nullptr;
if (!cpows_.add(objId, obj))
return nullptr;
nextCPOWNumber_ = objId.serialNumber() + 1;
// Incref once we know the decref will be called.
incref();
AuxCPOWData* aux = new AuxCPOWData(objId,
objVar.isCallable(),
objVar.isConstructor(),
objVar.isDOMObject(),
objVar.objectTag());
SetProxyReservedSlot(obj, 0, PrivateValue(this));
SetProxyReservedSlot(obj, 1, PrivateValue(aux));
}
if (!JS_WrapObject(cx, &obj))
return nullptr;
return obj;
}
JSObject*
WrapperOwner::fromLocalObjectVariant(JSContext* cx, const LocalObject& objVar)
{
ObjectId id = ObjectId::deserialize(objVar.serializedId());
Rooted<JSObject*> obj(cx, findObjectById(cx, id));
if (!obj)
return nullptr;
if (!JS_WrapObject(cx, &obj))
return nullptr;
return obj;
}