/* -*- 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(GetProxyReservedSlot(obj, 1).toPrivate()); } static inline WrapperOwner* OwnerOf(JSObject* obj) { MOZ_ASSERT(IsCPOW(obj)); return reinterpret_cast(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 desc) const override; virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle 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 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 desc) const { FORWARD(getPropertyDescriptor, (cx, proxy, id, desc), false); } bool WrapperOwner::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle 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 desc) const { FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc), false); } bool WrapperOwner::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id, MutableHandle 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 desc, ObjectOpResult& result) const { FORWARD(defineProperty, (cx, proxy, id, desc, result), false); } bool WrapperOwner::defineProperty(JSContext* cx, HandleObject proxy, HandleId id, Handle 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 jsid; nsresult rv = UnwrapArg(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 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 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 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 ""; 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 ""; 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 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 obj) { if (nsCOMPtr supports = xpc::UnwrapReflectorToISupports(obj)) { nsCOMPtr treeItem(do_QueryInterface(supports)); if (treeItem) return NS_LITERAL_CSTRING("ContentDocShellTreeItem"); nsCOMPtr 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()); 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 obj(cx, findObjectById(cx, id)); if (!obj) return nullptr; if (!JS_WrapObject(cx, &obj)) return nullptr; return obj; }