diff options
Diffstat (limited to 'js/ipc/WrapperOwner.cpp')
-rw-r--r-- | js/ipc/WrapperOwner.cpp | 1236 |
1 files changed, 1236 insertions, 0 deletions
diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp new file mode 100644 index 000000000..427ec6ab3 --- /dev/null +++ b/js/ipc/WrapperOwner.cpp @@ -0,0 +1,1236 @@ +/* -*- 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 "nsIDOMDocument.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*>(GetProxyExtra(obj, 1).toPrivate()); +} + +static inline WrapperOwner* +OwnerOf(JSObject* obj) +{ + MOZ_ASSERT(IsCPOW(obj)); + return reinterpret_cast<WrapperOwner*>(GetProxyExtra(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 bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) 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 bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override; + virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override; + virtual void objectMoved(JSObject* proxy, const 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) \ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS); \ + WrapperOwner* owner = OwnerOf(proxy); \ + if (!owner->active()) { \ + JS_ReportErrorASCII(cx, "cannot use a CPOW whose process is gone"); \ + return false; \ + } \ + if (!owner->allowMessage(cx)) { \ + return false; \ + } \ + { \ + 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)); +} + +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)); +} + +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)); +} + +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)); +} + +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)); +} + +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); +} + +bool +CPOWProxyHandler::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) 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, objp); +} + +bool +CPOWProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const +{ + FORWARD(has, (cx, proxy, id, bp)); +} + +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)); +} + +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)); +} + +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)); +} + +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)); +} + +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>(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)); +} + +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)); +} + +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)); +} + +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)); +} + +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)); +} + +bool +CPOWProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const +{ + FORWARD(callOrConstruct, (cx, proxy, args, true)); +} + +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)); +} + +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; + JSVariant result; + 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)); +} + +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)); +} + +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)); +} + +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)); +} + +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; +} + +bool +CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const +{ + FORWARD(regexp_toShared, (cx, proxy, g)); +} + +bool +WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) +{ + ObjectId objId = idOf(proxy); + + ReturnStatus status; + nsString source; + unsigned flags = 0; + if (!SendRegExpToShared(objId, &status, &source, &flags)) + return ipcfail(cx); + + LOG_STACK(); + + if (!ok(cx, status)) + return false; + + RootedObject regexp(cx); + regexp = JS_NewUCRegExpObject(cx, source.get(), source.Length(), flags); + if (!regexp) + return false; + + return js::RegExpToSharedNonInline(cx, regexp, g); +} + +void +CPOWProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const +{ + AuxCPOWData* aux = AuxCPOWDataOf(proxy); + + OwnerOf(proxy)->drop(proxy); + + if (aux) + delete aux; +} + +void +CPOWProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const +{ + OwnerOf(proxy)->updatePointer(proxy, old); +} + +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) +{ + ObjectId objId = idOf(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)); +} + +} /* 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::TReturnStopIteration) + return JS_ThrowStopIteration(cx); + + 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<nsIDOMDocument> 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()); + JSAutoCompartment ac(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()); + + SetProxyExtra(obj, 0, PrivateValue(this)); + SetProxyExtra(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; +} |