summaryrefslogtreecommitdiffstats
path: root/js/ipc/JavaScriptShared.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/ipc/JavaScriptShared.cpp')
-rw-r--r--js/ipc/JavaScriptShared.cpp760
1 files changed, 760 insertions, 0 deletions
diff --git a/js/ipc/JavaScriptShared.cpp b/js/ipc/JavaScriptShared.cpp
new file mode 100644
index 000000000..9786243f2
--- /dev/null
+++ b/js/ipc/JavaScriptShared.cpp
@@ -0,0 +1,760 @@
+/* -*- 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 "JavaScriptShared.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/CPOWManagerGetter.h"
+#include "mozilla/dom/TabChild.h"
+#include "jsfriendapi.h"
+#include "xpcprivate.h"
+#include "WrapperFactory.h"
+#include "mozilla/Preferences.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+IdToObjectMap::IdToObjectMap()
+ : table_(SystemAllocPolicy())
+{
+}
+
+bool
+IdToObjectMap::init()
+{
+ if (table_.initialized())
+ return true;
+ return table_.init(32);
+}
+
+void
+IdToObjectMap::trace(JSTracer* trc, uint64_t minimimId)
+{
+ for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
+ if (r.front().key().serialNumber() >= minimimId)
+ JS::TraceEdge(trc, &r.front().value(), "ipc-object");
+ }
+}
+
+void
+IdToObjectMap::sweep()
+{
+ for (Table::Enum e(table_); !e.empty(); e.popFront()) {
+ JS::Heap<JSObject*>* objp = &e.front().value();
+ JS_UpdateWeakPointerAfterGC(objp);
+ if (!*objp)
+ e.removeFront();
+ }
+}
+
+JSObject*
+IdToObjectMap::find(ObjectId id)
+{
+ Table::Ptr p = table_.lookup(id);
+ if (!p)
+ return nullptr;
+ return p->value();
+}
+
+bool
+IdToObjectMap::add(ObjectId id, JSObject* obj)
+{
+ return table_.put(id, obj);
+}
+
+void
+IdToObjectMap::remove(ObjectId id)
+{
+ table_.remove(id);
+}
+
+void
+IdToObjectMap::clear()
+{
+ table_.clear();
+}
+
+bool
+IdToObjectMap::empty() const
+{
+ return table_.empty();
+}
+
+#ifdef DEBUG
+bool
+IdToObjectMap::has(const ObjectId& id, const JSObject* obj) const
+{
+ auto p = table_.lookup(id);
+ if (!p)
+ return false;
+ return p->value().unbarrieredGet() == obj;
+}
+#endif
+
+bool
+ObjectToIdMap::init()
+{
+ return table_.initialized() || table_.init(32);
+}
+
+void
+ObjectToIdMap::trace(JSTracer* trc)
+{
+ table_.trace(trc);
+}
+
+void
+ObjectToIdMap::sweep()
+{
+ table_.sweep();
+}
+
+ObjectId
+ObjectToIdMap::find(JSObject* obj)
+{
+ Table::Ptr p = table_.lookup(obj);
+ if (!p)
+ return ObjectId::nullId();
+ return p->value();
+}
+
+bool
+ObjectToIdMap::add(JSContext* cx, JSObject* obj, ObjectId id)
+{
+ return table_.put(obj, id);
+}
+
+void
+ObjectToIdMap::remove(JSObject* obj)
+{
+ table_.remove(obj);
+}
+
+void
+ObjectToIdMap::clear()
+{
+ table_.clear();
+}
+
+bool JavaScriptShared::sLoggingInitialized;
+bool JavaScriptShared::sLoggingEnabled;
+bool JavaScriptShared::sStackLoggingEnabled;
+
+JavaScriptShared::JavaScriptShared()
+ : refcount_(1),
+ nextSerialNumber_(1),
+ nextCPOWNumber_(1)
+{
+ if (!sLoggingInitialized) {
+ sLoggingInitialized = true;
+
+ if (PR_GetEnv("MOZ_CPOW_LOG")) {
+ sLoggingEnabled = true;
+ sStackLoggingEnabled = strstr(PR_GetEnv("MOZ_CPOW_LOG"), "stacks");
+ } else {
+ Preferences::AddBoolVarCache(&sLoggingEnabled,
+ "dom.ipc.cpows.log.enabled", false);
+ Preferences::AddBoolVarCache(&sStackLoggingEnabled,
+ "dom.ipc.cpows.log.stack", false);
+ }
+ }
+}
+
+JavaScriptShared::~JavaScriptShared()
+{
+ MOZ_RELEASE_ASSERT(cpows_.empty());
+}
+
+bool
+JavaScriptShared::init()
+{
+ if (!objects_.init())
+ return false;
+ if (!cpows_.init())
+ return false;
+ if (!unwaivedObjectIds_.init())
+ return false;
+ if (!waivedObjectIds_.init())
+ return false;
+
+ return true;
+}
+
+void
+JavaScriptShared::decref()
+{
+ refcount_--;
+ if (!refcount_)
+ delete this;
+}
+
+void
+JavaScriptShared::incref()
+{
+ refcount_++;
+}
+
+bool
+JavaScriptShared::convertIdToGeckoString(JSContext* cx, JS::HandleId id, nsString* to)
+{
+ RootedValue idval(cx);
+ if (!JS_IdToValue(cx, id, &idval))
+ return false;
+
+ RootedString str(cx, ToString(cx, idval));
+ if (!str)
+ return false;
+
+ return AssignJSString(cx, *to, str);
+}
+
+bool
+JavaScriptShared::convertGeckoStringToId(JSContext* cx, const nsString& from, JS::MutableHandleId to)
+{
+ RootedString str(cx, JS_NewUCStringCopyN(cx, from.BeginReading(), from.Length()));
+ if (!str)
+ return false;
+
+ return JS_StringToId(cx, str, to);
+}
+
+bool
+JavaScriptShared::toVariant(JSContext* cx, JS::HandleValue from, JSVariant* to)
+{
+ switch (JS_TypeOfValue(cx, from)) {
+ case JSTYPE_VOID:
+ *to = UndefinedVariant();
+ return true;
+
+ case JSTYPE_OBJECT:
+ case JSTYPE_FUNCTION:
+ {
+ RootedObject obj(cx, from.toObjectOrNull());
+ if (!obj) {
+ MOZ_ASSERT(from.isNull());
+ *to = NullVariant();
+ return true;
+ }
+
+ if (xpc_JSObjectIsID(cx, obj)) {
+ JSIID iid;
+ const nsID* id = xpc_JSObjectToID(cx, obj);
+ ConvertID(*id, &iid);
+ *to = iid;
+ return true;
+ }
+
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, obj, &objVar))
+ return false;
+ *to = objVar;
+ return true;
+ }
+
+ case JSTYPE_SYMBOL:
+ {
+ RootedSymbol sym(cx, from.toSymbol());
+
+ SymbolVariant symVar;
+ if (!toSymbolVariant(cx, sym, &symVar))
+ return false;
+ *to = symVar;
+ return true;
+ }
+
+ case JSTYPE_STRING:
+ {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, from))
+ return false;
+ *to = autoStr;
+ return true;
+ }
+
+ case JSTYPE_NUMBER:
+ if (from.isInt32())
+ *to = double(from.toInt32());
+ else
+ *to = from.toDouble();
+ return true;
+
+ case JSTYPE_BOOLEAN:
+ *to = from.toBoolean();
+ return true;
+
+ default:
+ MOZ_ASSERT(false);
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::fromVariant(JSContext* cx, const JSVariant& from, MutableHandleValue to)
+{
+ switch (from.type()) {
+ case JSVariant::TUndefinedVariant:
+ to.set(UndefinedValue());
+ return true;
+
+ case JSVariant::TNullVariant:
+ to.set(NullValue());
+ return true;
+
+ case JSVariant::TObjectVariant:
+ {
+ JSObject* obj = fromObjectVariant(cx, from.get_ObjectVariant());
+ if (!obj)
+ return false;
+ to.set(ObjectValue(*obj));
+ return true;
+ }
+
+ case JSVariant::TSymbolVariant:
+ {
+ Symbol* sym = fromSymbolVariant(cx, from.get_SymbolVariant());
+ if (!sym)
+ return false;
+ to.setSymbol(sym);
+ return true;
+ }
+
+ case JSVariant::Tdouble:
+ to.set(JS_NumberValue(from.get_double()));
+ return true;
+
+ case JSVariant::Tbool:
+ to.setBoolean(from.get_bool());
+ return true;
+
+ case JSVariant::TnsString:
+ {
+ const nsString& old = from.get_nsString();
+ JSString* str = JS_NewUCStringCopyN(cx, old.BeginReading(), old.Length());
+ if (!str)
+ return false;
+ to.set(StringValue(str));
+ return true;
+ }
+
+ case JSVariant::TJSIID:
+ {
+ nsID iid;
+ const JSIID& id = from.get_JSIID();
+ ConvertID(id, &iid);
+
+ JSCompartment* compartment = GetContextCompartment(cx);
+ RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment));
+ JSObject* obj = xpc_NewIDObject(cx, global, iid);
+ if (!obj)
+ return false;
+ to.set(ObjectValue(*obj));
+ return true;
+ }
+
+ default:
+ MOZ_CRASH("NYI");
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::toJSIDVariant(JSContext* cx, HandleId from, JSIDVariant* to)
+{
+ if (JSID_IS_STRING(from)) {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, JSID_TO_STRING(from)))
+ return false;
+ *to = autoStr;
+ return true;
+ }
+ if (JSID_IS_INT(from)) {
+ *to = JSID_TO_INT(from);
+ return true;
+ }
+ if (JSID_IS_SYMBOL(from)) {
+ SymbolVariant symVar;
+ if (!toSymbolVariant(cx, JSID_TO_SYMBOL(from), &symVar))
+ return false;
+ *to = symVar;
+ return true;
+ }
+ MOZ_ASSERT(false);
+ return false;
+}
+
+bool
+JavaScriptShared::fromJSIDVariant(JSContext* cx, const JSIDVariant& from, MutableHandleId to)
+{
+ switch (from.type()) {
+ case JSIDVariant::TSymbolVariant: {
+ Symbol* sym = fromSymbolVariant(cx, from.get_SymbolVariant());
+ if (!sym)
+ return false;
+ to.set(SYMBOL_TO_JSID(sym));
+ return true;
+ }
+
+ case JSIDVariant::TnsString:
+ return convertGeckoStringToId(cx, from.get_nsString(), to);
+
+ case JSIDVariant::Tint32_t:
+ to.set(INT_TO_JSID(from.get_int32_t()));
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::toSymbolVariant(JSContext* cx, JS::Symbol* symArg, SymbolVariant* symVarp)
+{
+ RootedSymbol sym(cx, symArg);
+ MOZ_ASSERT(sym);
+
+ SymbolCode code = GetSymbolCode(sym);
+ if (static_cast<uint32_t>(code) < WellKnownSymbolLimit) {
+ *symVarp = WellKnownSymbol(static_cast<uint32_t>(code));
+ return true;
+ }
+ if (code == SymbolCode::InSymbolRegistry) {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, GetSymbolDescription(sym)))
+ return false;
+ *symVarp = RegisteredSymbol(autoStr);
+ return true;
+ }
+
+ JS_ReportErrorASCII(cx, "unique symbol can't be used with CPOW");
+ return false;
+}
+
+JS::Symbol*
+JavaScriptShared::fromSymbolVariant(JSContext* cx, const SymbolVariant& symVar)
+{
+ switch (symVar.type()) {
+ case SymbolVariant::TWellKnownSymbol: {
+ uint32_t which = symVar.get_WellKnownSymbol().which();
+ if (which < WellKnownSymbolLimit)
+ return GetWellKnownSymbol(cx, static_cast<SymbolCode>(which));
+ MOZ_ASSERT(false, "bad child data");
+ return nullptr;
+ }
+
+ case SymbolVariant::TRegisteredSymbol: {
+ nsString key = symVar.get_RegisteredSymbol().key();
+ RootedString str(cx, JS_NewUCStringCopyN(cx, key.get(), key.Length()));
+ if (!str)
+ return nullptr;
+ return GetSymbolFor(cx, str);
+ }
+
+ default:
+ return nullptr;
+ }
+}
+
+/* static */ void
+JavaScriptShared::ConvertID(const nsID& from, JSIID* to)
+{
+ to->m0() = from.m0;
+ to->m1() = from.m1;
+ to->m2() = from.m2;
+ to->m3_0() = from.m3[0];
+ to->m3_1() = from.m3[1];
+ to->m3_2() = from.m3[2];
+ to->m3_3() = from.m3[3];
+ to->m3_4() = from.m3[4];
+ to->m3_5() = from.m3[5];
+ to->m3_6() = from.m3[6];
+ to->m3_7() = from.m3[7];
+}
+
+/* static */ void
+JavaScriptShared::ConvertID(const JSIID& from, nsID* to)
+{
+ to->m0 = from.m0();
+ to->m1 = from.m1();
+ to->m2 = from.m2();
+ to->m3[0] = from.m3_0();
+ to->m3[1] = from.m3_1();
+ to->m3[2] = from.m3_2();
+ to->m3[3] = from.m3_3();
+ to->m3[4] = from.m3_4();
+ to->m3[5] = from.m3_5();
+ to->m3[6] = from.m3_6();
+ to->m3[7] = from.m3_7();
+}
+
+JSObject*
+JavaScriptShared::findObjectById(JSContext* cx, const ObjectId& objId)
+{
+ RootedObject obj(cx, objects_.find(objId));
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "operation not possible on dead CPOW");
+ return nullptr;
+ }
+
+ // Each process has a dedicated compartment for CPOW targets. All CPOWs
+ // from the other process point to objects in this scope. From there, they
+ // can access objects in other compartments using cross-compartment
+ // wrappers.
+ JSAutoCompartment ac(cx, scopeForTargetObjects());
+ if (objId.hasXrayWaiver()) {
+ {
+ JSAutoCompartment ac2(cx, obj);
+ obj = js::ToWindowProxyIfWindow(obj);
+ MOZ_ASSERT(obj);
+ }
+ if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &obj))
+ return nullptr;
+ } else {
+ if (!JS_WrapObject(cx, &obj))
+ return nullptr;
+ }
+ return obj;
+}
+
+static const uint64_t UnknownPropertyOp = 1;
+
+bool
+JavaScriptShared::fromDescriptor(JSContext* cx, Handle<PropertyDescriptor> desc,
+ PPropertyDescriptor* out)
+{
+ out->attrs() = desc.attributes();
+ if (!toVariant(cx, desc.value(), &out->value()))
+ return false;
+
+ if (!toObjectOrNullVariant(cx, desc.object(), &out->obj()))
+ return false;
+
+ if (!desc.getter()) {
+ out->getter() = 0;
+ } else if (desc.hasGetterObject()) {
+ JSObject* getter = desc.getterObject();
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, getter, &objVar))
+ return false;
+ out->getter() = objVar;
+ } else {
+ MOZ_ASSERT(desc.getter() != JS_PropertyStub);
+ out->getter() = UnknownPropertyOp;
+ }
+
+ if (!desc.setter()) {
+ out->setter() = 0;
+ } else if (desc.hasSetterObject()) {
+ JSObject* setter = desc.setterObject();
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, setter, &objVar))
+ return false;
+ out->setter() = objVar;
+ } else {
+ MOZ_ASSERT(desc.setter() != JS_StrictPropertyStub);
+ out->setter() = UnknownPropertyOp;
+ }
+
+ return true;
+}
+
+bool
+UnknownPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
+{
+ JS_ReportErrorASCII(cx, "getter could not be wrapped via CPOWs");
+ return false;
+}
+
+bool
+UnknownStrictPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp,
+ ObjectOpResult& result)
+{
+ JS_ReportErrorASCII(cx, "setter could not be wrapped via CPOWs");
+ return false;
+}
+
+bool
+JavaScriptShared::toDescriptor(JSContext* cx, const PPropertyDescriptor& in,
+ MutableHandle<PropertyDescriptor> out)
+{
+ out.setAttributes(in.attrs());
+ if (!fromVariant(cx, in.value(), out.value()))
+ return false;
+ out.object().set(fromObjectOrNullVariant(cx, in.obj()));
+
+ if (in.getter().type() == GetterSetter::Tuint64_t && !in.getter().get_uint64_t()) {
+ out.setGetter(nullptr);
+ } else if (in.attrs() & JSPROP_GETTER) {
+ Rooted<JSObject*> getter(cx);
+ getter = fromObjectVariant(cx, in.getter().get_ObjectVariant());
+ if (!getter)
+ return false;
+ out.setGetter(JS_DATA_TO_FUNC_PTR(JSGetterOp, getter.get()));
+ } else {
+ out.setGetter(UnknownPropertyStub);
+ }
+
+ if (in.setter().type() == GetterSetter::Tuint64_t && !in.setter().get_uint64_t()) {
+ out.setSetter(nullptr);
+ } else if (in.attrs() & JSPROP_SETTER) {
+ Rooted<JSObject*> setter(cx);
+ setter = fromObjectVariant(cx, in.setter().get_ObjectVariant());
+ if (!setter)
+ return false;
+ out.setSetter(JS_DATA_TO_FUNC_PTR(JSSetterOp, setter.get()));
+ } else {
+ out.setSetter(UnknownStrictPropertyStub);
+ }
+
+ return true;
+}
+
+bool
+JavaScriptShared::toObjectOrNullVariant(JSContext* cx, JSObject* obj, ObjectOrNullVariant* objVarp)
+{
+ if (!obj) {
+ *objVarp = NullVariant();
+ return true;
+ }
+
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, obj, &objVar))
+ return false;
+
+ *objVarp = objVar;
+ return true;
+}
+
+JSObject*
+JavaScriptShared::fromObjectOrNullVariant(JSContext* cx, const ObjectOrNullVariant& objVar)
+{
+ if (objVar.type() == ObjectOrNullVariant::TNullVariant)
+ return nullptr;
+
+ return fromObjectVariant(cx, objVar.get_ObjectVariant());
+}
+
+CrossProcessCpowHolder::CrossProcessCpowHolder(dom::CPOWManagerGetter* managerGetter,
+ const InfallibleTArray<CpowEntry>& cpows)
+ : js_(nullptr),
+ cpows_(cpows),
+ unwrapped_(false)
+{
+ // Only instantiate the CPOW manager if we might need it later.
+ if (cpows.Length())
+ js_ = managerGetter->GetCPOWManager();
+}
+
+CrossProcessCpowHolder::~CrossProcessCpowHolder()
+{
+ if (cpows_.Length() && !unwrapped_) {
+ // This should only happen if a message manager message
+ // containing CPOWs gets ignored for some reason. We need to
+ // unwrap every incoming CPOW in this process to ensure that
+ // the corresponding part of the CPOW in the other process
+ // will eventually be collected. The scope for this object
+ // doesn't really matter, because it immediately becomes
+ // garbage.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::PrivilegedJunkScope()))
+ return;
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> cpows(cx);
+ js_->Unwrap(cx, cpows_, &cpows);
+ }
+}
+
+bool
+CrossProcessCpowHolder::ToObject(JSContext* cx, JS::MutableHandleObject objp)
+{
+ unwrapped_ = true;
+
+ if (!cpows_.Length())
+ return true;
+
+ return js_->Unwrap(cx, cpows_, objp);
+}
+
+bool
+JavaScriptShared::Unwrap(JSContext* cx, const InfallibleTArray<CpowEntry>& aCpows,
+ JS::MutableHandleObject objp)
+{
+ objp.set(nullptr);
+
+ if (!aCpows.Length())
+ return true;
+
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return false;
+
+ RootedValue v(cx);
+ RootedString str(cx);
+ for (size_t i = 0; i < aCpows.Length(); i++) {
+ const nsString& name = aCpows[i].name();
+
+ if (!fromVariant(cx, aCpows[i].value(), &v))
+ return false;
+
+ if (!JS_DefineUCProperty(cx,
+ obj,
+ name.BeginReading(),
+ name.Length(),
+ v,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ }
+
+ objp.set(obj);
+ return true;
+}
+
+bool
+JavaScriptShared::Wrap(JSContext* cx, HandleObject aObj, InfallibleTArray<CpowEntry>* outCpows)
+{
+ if (!aObj)
+ return true;
+
+ Rooted<IdVector> ids(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, aObj, &ids))
+ return false;
+
+ RootedId id(cx);
+ RootedValue v(cx);
+ for (size_t i = 0; i < ids.length(); i++) {
+ id = ids[i];
+
+ nsString str;
+ if (!convertIdToGeckoString(cx, id, &str))
+ return false;
+
+ if (!JS_GetPropertyById(cx, aObj, id, &v))
+ return false;
+
+ JSVariant var;
+ if (!toVariant(cx, v, &var))
+ return false;
+
+ outCpows->AppendElement(CpowEntry(str, var));
+ }
+
+ return true;
+}
+
+CPOWManager*
+mozilla::jsipc::CPOWManagerFor(PJavaScriptParent* aParent)
+{
+ return static_cast<JavaScriptParent*>(aParent);
+}
+
+CPOWManager*
+mozilla::jsipc::CPOWManagerFor(PJavaScriptChild* aChild)
+{
+ return static_cast<JavaScriptChild*>(aChild);
+}