michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=4 sw=4 et tw=80: michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "JavaScriptShared.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "jsfriendapi.h" michael@0: #include "xpcprivate.h" michael@0: michael@0: using namespace js; michael@0: using namespace JS; michael@0: using namespace mozilla; michael@0: using namespace mozilla::jsipc; michael@0: michael@0: ObjectStore::ObjectStore() michael@0: : table_(SystemAllocPolicy()) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: ObjectStore::init() michael@0: { michael@0: return table_.init(32); michael@0: } michael@0: michael@0: void michael@0: ObjectStore::trace(JSTracer *trc) michael@0: { michael@0: for (ObjectTable::Range r(table_.all()); !r.empty(); r.popFront()) { michael@0: DebugOnly prior = r.front().value().get(); michael@0: JS_CallHeapObjectTracer(trc, &r.front().value(), "ipc-object"); michael@0: MOZ_ASSERT(r.front().value() == prior); michael@0: } michael@0: } michael@0: michael@0: JSObject * michael@0: ObjectStore::find(ObjectId id) michael@0: { michael@0: ObjectTable::Ptr p = table_.lookup(id); michael@0: if (!p) michael@0: return nullptr; michael@0: return p->value(); michael@0: } michael@0: michael@0: bool michael@0: ObjectStore::add(ObjectId id, JSObject *obj) michael@0: { michael@0: return table_.put(id, obj); michael@0: } michael@0: michael@0: void michael@0: ObjectStore::remove(ObjectId id) michael@0: { michael@0: table_.remove(id); michael@0: } michael@0: michael@0: ObjectIdCache::ObjectIdCache() michael@0: : table_(nullptr) michael@0: { michael@0: } michael@0: michael@0: ObjectIdCache::~ObjectIdCache() michael@0: { michael@0: if (table_) { michael@0: dom::AddForDeferredFinalization(table_); michael@0: table_ = nullptr; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ObjectIdCache::init() michael@0: { michael@0: MOZ_ASSERT(!table_); michael@0: table_ = new ObjectIdTable(SystemAllocPolicy()); michael@0: return table_ && table_->init(32); michael@0: } michael@0: michael@0: void michael@0: ObjectIdCache::trace(JSTracer *trc) michael@0: { michael@0: for (ObjectIdTable::Range r(table_->all()); !r.empty(); r.popFront()) { michael@0: JSObject *obj = r.front().key(); michael@0: JS_CallObjectTracer(trc, &obj, "ipc-id"); michael@0: MOZ_ASSERT(obj == r.front().key()); michael@0: } michael@0: } michael@0: michael@0: ObjectId michael@0: ObjectIdCache::find(JSObject *obj) michael@0: { michael@0: ObjectIdTable::Ptr p = table_->lookup(obj); michael@0: if (!p) michael@0: return 0; michael@0: return p->value(); michael@0: } michael@0: michael@0: bool michael@0: ObjectIdCache::add(JSContext *cx, JSObject *obj, ObjectId id) michael@0: { michael@0: if (!table_->put(obj, id)) michael@0: return false; michael@0: JS_StoreObjectPostBarrierCallback(cx, keyMarkCallback, obj, table_); michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * This function is called during minor GCs for each key in the HashMap that has michael@0: * been moved. michael@0: */ michael@0: /* static */ void michael@0: ObjectIdCache::keyMarkCallback(JSTracer *trc, JSObject *key, void *data) { michael@0: ObjectIdTable* table = static_cast(data); michael@0: JSObject *prior = key; michael@0: JS_CallObjectTracer(trc, &key, "ObjectIdCache::table_ key"); michael@0: table->rekeyIfMoved(prior, key); michael@0: } michael@0: michael@0: void michael@0: ObjectIdCache::remove(JSObject *obj) michael@0: { michael@0: table_->remove(obj); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::init() michael@0: { michael@0: if (!objects_.init()) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::convertIdToGeckoString(JSContext *cx, JS::HandleId id, nsString *to) michael@0: { michael@0: RootedValue idval(cx); michael@0: if (!JS_IdToValue(cx, id, &idval)) michael@0: return false; michael@0: michael@0: RootedString str(cx, ToString(cx, idval)); michael@0: if (!str) michael@0: return false; michael@0: michael@0: const jschar *chars = JS_GetStringCharsZ(cx, str); michael@0: if (!chars) michael@0: return false; michael@0: michael@0: *to = chars; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::convertGeckoStringToId(JSContext *cx, const nsString &from, JS::MutableHandleId to) michael@0: { michael@0: RootedString str(cx, JS_NewUCStringCopyN(cx, from.BeginReading(), from.Length())); michael@0: if (!str) michael@0: return false; michael@0: michael@0: return JS_StringToId(cx, str, to); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::toVariant(JSContext *cx, JS::HandleValue from, JSVariant *to) michael@0: { michael@0: switch (JS_TypeOfValue(cx, from)) { michael@0: case JSTYPE_VOID: michael@0: *to = void_t(); michael@0: return true; michael@0: michael@0: case JSTYPE_NULL: michael@0: { michael@0: *to = uint64_t(0); michael@0: return true; michael@0: } michael@0: michael@0: case JSTYPE_OBJECT: michael@0: case JSTYPE_FUNCTION: michael@0: { michael@0: RootedObject obj(cx, from.toObjectOrNull()); michael@0: if (!obj) { michael@0: MOZ_ASSERT(from == JSVAL_NULL); michael@0: *to = uint64_t(0); michael@0: return true; michael@0: } michael@0: michael@0: if (xpc_JSObjectIsID(cx, obj)) { michael@0: JSIID iid; michael@0: const nsID *id = xpc_JSObjectToID(cx, obj); michael@0: ConvertID(*id, &iid); michael@0: *to = iid; michael@0: return true; michael@0: } michael@0: michael@0: ObjectId id; michael@0: if (!makeId(cx, obj, &id)) michael@0: return false; michael@0: *to = uint64_t(id); michael@0: return true; michael@0: } michael@0: michael@0: case JSTYPE_STRING: michael@0: { michael@0: nsDependentJSString dep; michael@0: if (!dep.init(cx, from)) michael@0: return false; michael@0: *to = dep; michael@0: return true; michael@0: } michael@0: michael@0: case JSTYPE_NUMBER: michael@0: if (JSVAL_IS_INT(from)) michael@0: *to = double(from.toInt32()); michael@0: else michael@0: *to = from.toDouble(); michael@0: return true; michael@0: michael@0: case JSTYPE_BOOLEAN: michael@0: *to = from.toBoolean(); michael@0: return true; michael@0: michael@0: default: michael@0: MOZ_ASSERT(false); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::toValue(JSContext *cx, const JSVariant &from, MutableHandleValue to) michael@0: { michael@0: switch (from.type()) { michael@0: case JSVariant::Tvoid_t: michael@0: to.set(UndefinedValue()); michael@0: return true; michael@0: michael@0: case JSVariant::Tuint64_t: michael@0: { michael@0: ObjectId id = from.get_uint64_t(); michael@0: if (id) { michael@0: JSObject *obj = unwrap(cx, id); michael@0: if (!obj) michael@0: return false; michael@0: to.set(ObjectValue(*obj)); michael@0: } else { michael@0: to.set(JSVAL_NULL); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: case JSVariant::Tdouble: michael@0: to.set(JS_NumberValue(from.get_double())); michael@0: return true; michael@0: michael@0: case JSVariant::Tbool: michael@0: to.set(BOOLEAN_TO_JSVAL(from.get_bool())); michael@0: return true; michael@0: michael@0: case JSVariant::TnsString: michael@0: { michael@0: const nsString &old = from.get_nsString(); michael@0: JSString *str = JS_NewUCStringCopyN(cx, old.BeginReading(), old.Length()); michael@0: if (!str) michael@0: return false; michael@0: to.set(StringValue(str)); michael@0: return true; michael@0: } michael@0: michael@0: case JSVariant::TJSIID: michael@0: { michael@0: nsID iid; michael@0: const JSIID &id = from.get_JSIID(); michael@0: ConvertID(id, &iid); michael@0: michael@0: JSCompartment *compartment = GetContextCompartment(cx); michael@0: RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment)); michael@0: JSObject *obj = xpc_NewIDObject(cx, global, iid); michael@0: if (!obj) michael@0: return false; michael@0: to.set(ObjectValue(*obj)); michael@0: return true; michael@0: } michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /* static */ void michael@0: JavaScriptShared::ConvertID(const nsID &from, JSIID *to) michael@0: { michael@0: to->m0() = from.m0; michael@0: to->m1() = from.m1; michael@0: to->m2() = from.m2; michael@0: to->m3_0() = from.m3[0]; michael@0: to->m3_1() = from.m3[1]; michael@0: to->m3_2() = from.m3[2]; michael@0: to->m3_3() = from.m3[3]; michael@0: to->m3_4() = from.m3[4]; michael@0: to->m3_5() = from.m3[5]; michael@0: to->m3_6() = from.m3[6]; michael@0: to->m3_7() = from.m3[7]; michael@0: } michael@0: michael@0: /* static */ void michael@0: JavaScriptShared::ConvertID(const JSIID &from, nsID *to) michael@0: { michael@0: to->m0 = from.m0(); michael@0: to->m1 = from.m1(); michael@0: to->m2 = from.m2(); michael@0: to->m3[0] = from.m3_0(); michael@0: to->m3[1] = from.m3_1(); michael@0: to->m3[2] = from.m3_2(); michael@0: to->m3[3] = from.m3_3(); michael@0: to->m3[4] = from.m3_4(); michael@0: to->m3[5] = from.m3_5(); michael@0: to->m3[6] = from.m3_6(); michael@0: to->m3[7] = from.m3_7(); michael@0: } michael@0: michael@0: static const uint32_t DefaultPropertyOp = 1; michael@0: static const uint32_t GetterOnlyPropertyStub = 2; michael@0: static const uint32_t UnknownPropertyOp = 3; michael@0: michael@0: bool michael@0: JavaScriptShared::fromDescriptor(JSContext *cx, Handle desc, michael@0: PPropertyDescriptor *out) michael@0: { michael@0: out->attrs() = desc.attributes(); michael@0: if (!toVariant(cx, desc.value(), &out->value())) michael@0: return false; michael@0: michael@0: if (!makeId(cx, desc.object(), &out->objId())) michael@0: return false; michael@0: michael@0: if (!desc.getter()) { michael@0: out->getter() = 0; michael@0: } else if (desc.hasGetterObject()) { michael@0: JSObject *getter = desc.getterObject(); michael@0: if (!makeId(cx, getter, &out->getter())) michael@0: return false; michael@0: } else { michael@0: if (desc.getter() == JS_PropertyStub) michael@0: out->getter() = DefaultPropertyOp; michael@0: else michael@0: out->getter() = UnknownPropertyOp; michael@0: } michael@0: michael@0: if (!desc.setter()) { michael@0: out->setter() = 0; michael@0: } else if (desc.hasSetterObject()) { michael@0: JSObject *setter = desc.setterObject(); michael@0: if (!makeId(cx, setter, &out->setter())) michael@0: return false; michael@0: } else { michael@0: if (desc.setter() == JS_StrictPropertyStub) michael@0: out->setter() = DefaultPropertyOp; michael@0: else if (desc.setter() == js_GetterOnlyPropertyStub) michael@0: out->setter() = GetterOnlyPropertyStub; michael@0: else michael@0: out->setter() = UnknownPropertyOp; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: UnknownPropertyStub(JSContext *cx, HandleObject obj, HandleId id, MutableHandleValue vp) michael@0: { michael@0: JS_ReportError(cx, "getter could not be wrapped via CPOWs"); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: UnknownStrictPropertyStub(JSContext *cx, HandleObject obj, HandleId id, bool strict, MutableHandleValue vp) michael@0: { michael@0: JS_ReportError(cx, "setter could not be wrapped via CPOWs"); michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::toDescriptor(JSContext *cx, const PPropertyDescriptor &in, michael@0: MutableHandle out) michael@0: { michael@0: out.setAttributes(in.attrs()); michael@0: if (!toValue(cx, in.value(), out.value())) michael@0: return false; michael@0: Rooted obj(cx); michael@0: if (!unwrap(cx, in.objId(), &obj)) michael@0: return false; michael@0: out.object().set(obj); michael@0: michael@0: if (!in.getter()) { michael@0: out.setGetter(nullptr); michael@0: } else if (in.attrs() & JSPROP_GETTER) { michael@0: Rooted getter(cx); michael@0: if (!unwrap(cx, in.getter(), &getter)) michael@0: return false; michael@0: out.setGetter(JS_DATA_TO_FUNC_PTR(JSPropertyOp, getter.get())); michael@0: } else { michael@0: if (in.getter() == DefaultPropertyOp) michael@0: out.setGetter(JS_PropertyStub); michael@0: else michael@0: out.setGetter(UnknownPropertyStub); michael@0: } michael@0: michael@0: if (!in.setter()) { michael@0: out.setSetter(nullptr); michael@0: } else if (in.attrs() & JSPROP_SETTER) { michael@0: Rooted setter(cx); michael@0: if (!unwrap(cx, in.setter(), &setter)) michael@0: return false; michael@0: out.setSetter(JS_DATA_TO_FUNC_PTR(JSStrictPropertyOp, setter.get())); michael@0: } else { michael@0: if (in.setter() == DefaultPropertyOp) michael@0: out.setSetter(JS_StrictPropertyStub); michael@0: else if (in.setter() == GetterOnlyPropertyStub) michael@0: out.setSetter(js_GetterOnlyPropertyStub); michael@0: else michael@0: out.setSetter(UnknownStrictPropertyStub); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: CpowIdHolder::ToObject(JSContext *cx, JS::MutableHandleObject objp) michael@0: { michael@0: return js_->Unwrap(cx, cpows_, objp); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::Unwrap(JSContext *cx, const InfallibleTArray &aCpows, michael@0: JS::MutableHandleObject objp) michael@0: { michael@0: objp.set(nullptr); michael@0: michael@0: if (!aCpows.Length()) michael@0: return true; michael@0: michael@0: RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedValue v(cx); michael@0: RootedString str(cx); michael@0: for (size_t i = 0; i < aCpows.Length(); i++) { michael@0: const nsString &name = aCpows[i].name(); michael@0: michael@0: if (!toValue(cx, aCpows[i].value(), &v)) michael@0: return false; michael@0: michael@0: if (!JS_DefineUCProperty(cx, michael@0: obj, michael@0: name.BeginReading(), michael@0: name.Length(), michael@0: v, michael@0: nullptr, michael@0: nullptr, michael@0: JSPROP_ENUMERATE)) michael@0: { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: objp.set(obj); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptShared::Wrap(JSContext *cx, HandleObject aObj, InfallibleTArray *outCpows) michael@0: { michael@0: if (!aObj) michael@0: return true; michael@0: michael@0: AutoIdArray ids(cx, JS_Enumerate(cx, aObj)); michael@0: if (!ids) michael@0: return false; michael@0: michael@0: RootedId id(cx); michael@0: RootedValue v(cx); michael@0: for (size_t i = 0; i < ids.length(); i++) { michael@0: id = ids[i]; michael@0: michael@0: nsString str; michael@0: if (!convertIdToGeckoString(cx, id, &str)) michael@0: return false; michael@0: michael@0: if (!JS_GetPropertyById(cx, aObj, id, &v)) michael@0: return false; michael@0: michael@0: JSVariant var; michael@0: if (!toVariant(cx, v, &var)) michael@0: return false; michael@0: michael@0: outCpows->AppendElement(CpowEntry(str, var)); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: