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 "JavaScriptChild.h" michael@0: #include "mozilla/dom/ContentChild.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "xpcprivate.h" michael@0: #include "jsfriendapi.h" michael@0: #include "nsCxPusher.h" michael@0: michael@0: using namespace JS; michael@0: using namespace mozilla; michael@0: using namespace mozilla::jsipc; michael@0: michael@0: using mozilla::AutoSafeJSContext; michael@0: michael@0: JavaScriptChild::JavaScriptChild(JSRuntime *rt) michael@0: : lastId_(0), michael@0: rt_(rt) michael@0: { michael@0: } michael@0: michael@0: static void michael@0: Trace(JSTracer *trc, void *data) michael@0: { michael@0: reinterpret_cast(data)->trace(trc); michael@0: } michael@0: michael@0: JavaScriptChild::~JavaScriptChild() michael@0: { michael@0: JS_RemoveExtraGCRootsTracer(rt_, Trace, this); michael@0: } michael@0: michael@0: void michael@0: JavaScriptChild::trace(JSTracer *trc) michael@0: { michael@0: objects_.trace(trc); michael@0: ids_.trace(trc); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::init() michael@0: { michael@0: if (!JavaScriptShared::init()) michael@0: return false; michael@0: if (!ids_.init()) michael@0: return false; michael@0: michael@0: JS_AddExtraGCRootsTracer(rt_, Trace, this); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::RecvDropObject(const ObjectId &objId) michael@0: { michael@0: JSObject *obj = findObject(objId); michael@0: if (obj) { michael@0: ids_.remove(obj); michael@0: objects_.remove(objId); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::makeId(JSContext *cx, JSObject *obj, ObjectId *idp) michael@0: { michael@0: if (!obj) { michael@0: *idp = 0; michael@0: return true; michael@0: } michael@0: michael@0: ObjectId id = ids_.find(obj); michael@0: if (id) { michael@0: *idp = id; michael@0: return true; michael@0: } michael@0: michael@0: id = ++lastId_; michael@0: if (id > MAX_CPOW_IDS) { michael@0: JS_ReportError(cx, "CPOW id limit reached"); michael@0: return false; michael@0: } michael@0: michael@0: id <<= OBJECT_EXTRA_BITS; michael@0: if (JS_ObjectIsCallable(cx, obj)) michael@0: id |= OBJECT_IS_CALLABLE; michael@0: michael@0: if (!objects_.add(id, obj)) michael@0: return false; michael@0: if (!ids_.add(cx, obj, id)) michael@0: return false; michael@0: michael@0: *idp = id; michael@0: return true; michael@0: } michael@0: michael@0: JSObject * michael@0: JavaScriptChild::unwrap(JSContext *cx, ObjectId id) michael@0: { michael@0: JSObject *obj = findObject(id); michael@0: MOZ_ASSERT(obj); michael@0: return obj; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::fail(JSContext *cx, ReturnStatus *rs) michael@0: { michael@0: // By default, we set |undefined| unless we can get a more meaningful michael@0: // exception. michael@0: *rs = ReturnStatus(ReturnException(JSVariant(void_t()))); michael@0: michael@0: // Note we always return true from this function, since this propagates michael@0: // to the IPC code, and we don't want a JS failure to cause the death michael@0: // of the child process. michael@0: michael@0: RootedValue exn(cx); michael@0: if (!JS_GetPendingException(cx, &exn)) michael@0: return true; michael@0: michael@0: // If we don't clear the pending exception, JS will try to wrap it as it michael@0: // leaves the current compartment. Since there is no previous compartment, michael@0: // that would crash. michael@0: JS_ClearPendingException(cx); michael@0: michael@0: if (JS_IsStopIteration(exn)) { michael@0: *rs = ReturnStatus(ReturnStopIteration()); michael@0: return true; michael@0: } michael@0: michael@0: // If this fails, we still don't want to exit. Just return an invalid michael@0: // exception. michael@0: (void) toVariant(cx, exn, &rs->get_ReturnException().exn()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::ok(ReturnStatus *rs) michael@0: { michael@0: *rs = ReturnStatus(ReturnSuccess()); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerPreventExtensions(const ObjectId &objId, ReturnStatus *rs) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: if (!JS_PreventExtensions(cx, obj)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: static void michael@0: EmptyDesc(PPropertyDescriptor *desc) michael@0: { michael@0: desc->objId() = 0; michael@0: desc->attrs() = 0; michael@0: desc->value() = void_t(); michael@0: desc->getter() = 0; michael@0: desc->setter() = 0; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerGetPropertyDescriptor(const ObjectId &objId, const nsString &id, michael@0: ReturnStatus *rs, PPropertyDescriptor *out) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: EmptyDesc(out); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc)) michael@0: return fail(cx, rs); michael@0: michael@0: if (!desc.object()) michael@0: return ok(rs); michael@0: michael@0: if (!fromDescriptor(cx, desc, out)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerGetOwnPropertyDescriptor(const ObjectId &objId, const nsString &id, michael@0: ReturnStatus *rs, PPropertyDescriptor *out) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: EmptyDesc(out); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc)) michael@0: return fail(cx, rs); michael@0: michael@0: if (desc.object() != obj) michael@0: return ok(rs); michael@0: michael@0: if (!fromDescriptor(cx, desc, out)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerDefineProperty(const ObjectId &objId, const nsString &id, michael@0: const PPropertyDescriptor &descriptor, ReturnStatus *rs) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: Rooted desc(cx); michael@0: if (!toDescriptor(cx, descriptor, &desc)) michael@0: return false; michael@0: michael@0: if (!js::CheckDefineProperty(cx, obj, internedId, desc.value(), desc.getter(), michael@0: desc.setter(), desc.attributes())) michael@0: { michael@0: return fail(cx, rs); michael@0: } michael@0: michael@0: if (!JS_DefinePropertyById(cx, obj, internedId, desc.value(), desc.getter(), michael@0: desc.setter(), desc.attributes())) michael@0: { michael@0: return fail(cx, rs); michael@0: } michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerDelete(const ObjectId &objId, const nsString &id, ReturnStatus *rs, michael@0: bool *success) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *success = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: if (!JS_DeletePropertyById2(cx, obj, internedId, success)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerHas(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *bp = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: bool found; michael@0: if (!JS_HasPropertyById(cx, obj, internedId, &found)) michael@0: return fail(cx, rs); michael@0: *bp = !!found; michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerHasOwn(const ObjectId &objId, const nsString &id, ReturnStatus *rs, bool *bp) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *bp = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: Rooted desc(cx); michael@0: if (!JS_GetPropertyDescriptorById(cx, obj, internedId, &desc)) michael@0: return fail(cx, rs); michael@0: *bp = (desc.object() == obj); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerGet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id, michael@0: ReturnStatus *rs, JSVariant *result) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: // The outparam will be written to the buffer, so it must be set even if michael@0: // the parent won't read it. michael@0: *result = void_t(); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedObject receiver(cx, findObject(receiverId)); michael@0: if (!receiver) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: JS::RootedValue val(cx); michael@0: if (!JS_ForwardGetPropertyTo(cx, obj, internedId, receiver, &val)) michael@0: return fail(cx, rs); michael@0: michael@0: if (!toVariant(cx, val, result)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerSet(const ObjectId &objId, const ObjectId &receiverId, const nsString &id, michael@0: const bool &strict, const JSVariant &value, ReturnStatus *rs, michael@0: JSVariant *result) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: // The outparam will be written to the buffer, so it must be set even if michael@0: // the parent won't read it. michael@0: *result = void_t(); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: RootedObject receiver(cx, findObject(receiverId)); michael@0: if (!receiver) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: RootedId internedId(cx); michael@0: if (!convertGeckoStringToId(cx, id, &internedId)) michael@0: return fail(cx, rs); michael@0: michael@0: MOZ_ASSERT(obj == receiver); michael@0: michael@0: RootedValue val(cx); michael@0: if (!toValue(cx, value, &val)) michael@0: return fail(cx, rs); michael@0: michael@0: if (!JS_SetPropertyById(cx, obj, internedId, val)) michael@0: return fail(cx, rs); michael@0: michael@0: if (!toVariant(cx, val, result)) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *result = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: bool extensible; michael@0: if (!JS_IsExtensible(cx, obj, &extensible)) michael@0: return fail(cx, rs); michael@0: michael@0: *result = !!extensible; michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerCall(const ObjectId &objId, const nsTArray &argv, ReturnStatus *rs, michael@0: JSVariant *result, nsTArray *outparams) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: // The outparam will be written to the buffer, so it must be set even if michael@0: // the parent won't read it. michael@0: *result = void_t(); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: MOZ_ASSERT(argv.Length() >= 2); michael@0: michael@0: RootedValue objv(cx); michael@0: if (!toValue(cx, argv[0], &objv)) michael@0: return fail(cx, rs); michael@0: michael@0: JSAutoCompartment comp(cx, &objv.toObject()); michael@0: michael@0: *result = JSVariant(void_t()); michael@0: michael@0: AutoValueVector vals(cx); michael@0: AutoValueVector outobjects(cx); michael@0: for (size_t i = 0; i < argv.Length(); i++) { michael@0: if (argv[i].type() == JSParam::Tvoid_t) { michael@0: // This is an outparam. michael@0: JSCompartment *compartment = js::GetContextCompartment(cx); michael@0: RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment)); michael@0: RootedObject obj(cx, xpc::NewOutObject(cx, global)); michael@0: if (!obj) michael@0: return fail(cx, rs); michael@0: if (!outobjects.append(ObjectValue(*obj))) michael@0: return fail(cx, rs); michael@0: if (!vals.append(ObjectValue(*obj))) michael@0: return fail(cx, rs); michael@0: } else { michael@0: RootedValue v(cx); michael@0: if (!toValue(cx, argv[i].get_JSVariant(), &v)) michael@0: return fail(cx, rs); michael@0: if (!vals.append(v)) michael@0: return fail(cx, rs); michael@0: } michael@0: } michael@0: michael@0: RootedValue rval(cx); michael@0: { michael@0: AutoSaveContextOptions asco(cx); michael@0: ContextOptionsRef(cx).setDontReportUncaught(true); michael@0: michael@0: HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2); michael@0: bool success = JS::Call(cx, vals.handleAt(1), vals.handleAt(0), args, &rval); michael@0: if (!success) michael@0: return fail(cx, rs); michael@0: } michael@0: michael@0: if (!toVariant(cx, rval, result)) michael@0: return fail(cx, rs); michael@0: michael@0: // Prefill everything with a dummy jsval. michael@0: for (size_t i = 0; i < outobjects.length(); i++) michael@0: outparams->AppendElement(JSParam(void_t())); michael@0: michael@0: // Go through each argument that was an outparam, retrieve the "value" michael@0: // field, and add it to a temporary list. We need to do this separately michael@0: // because the outparams vector is not rooted. michael@0: vals.clear(); michael@0: for (size_t i = 0; i < outobjects.length(); i++) { michael@0: RootedObject obj(cx, &outobjects[i].toObject()); michael@0: michael@0: RootedValue v(cx); michael@0: bool found; michael@0: if (JS_HasProperty(cx, obj, "value", &found)) { michael@0: if (!JS_GetProperty(cx, obj, "value", &v)) michael@0: return fail(cx, rs); michael@0: } else { michael@0: v = UndefinedValue(); michael@0: } michael@0: if (!vals.append(v)) michael@0: return fail(cx, rs); michael@0: } michael@0: michael@0: // Copy the outparams. If any outparam is already set to a void_t, we michael@0: // treat this as the outparam never having been set. michael@0: for (size_t i = 0; i < vals.length(); i++) { michael@0: JSVariant variant; michael@0: if (!toVariant(cx, vals.handleAt(i), &variant)) michael@0: return fail(cx, rs); michael@0: outparams->ReplaceElementAt(i, JSParam(variant)); michael@0: } michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerObjectClassIs(const ObjectId &objId, const uint32_t &classValue, michael@0: bool *result) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: *result = js_ObjectClassIs(cx, obj, (js::ESClassValue)classValue); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerClassName(const ObjectId &objId, nsString *name) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: *name = NS_ConvertASCIItoUTF16(js_ObjectClassName(cx, obj)); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerGetPropertyNames(const ObjectId &objId, const uint32_t &flags, michael@0: ReturnStatus *rs, nsTArray *names) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: AutoIdVector props(cx); michael@0: if (!js::GetPropertyNames(cx, obj, flags, &props)) michael@0: return fail(cx, rs); michael@0: michael@0: for (size_t i = 0; i < props.length(); i++) { michael@0: nsString name; michael@0: if (!convertIdToGeckoString(cx, props.handleAt(i), &name)) michael@0: return fail(cx, rs); michael@0: michael@0: names->AppendElement(name); michael@0: } michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerInstanceOf(const ObjectId &objId, const JSIID &iid, ReturnStatus *rs, michael@0: bool *instanceof) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *instanceof = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: nsID nsiid; michael@0: ConvertID(iid, &nsiid); michael@0: michael@0: nsresult rv = xpc::HasInstance(cx, obj, &nsiid, instanceof); michael@0: if (rv != NS_OK) michael@0: return fail(cx, rs); michael@0: michael@0: return ok(rs); michael@0: } michael@0: michael@0: bool michael@0: JavaScriptChild::AnswerDOMInstanceOf(const ObjectId &objId, const int &prototypeID, michael@0: const int &depth, michael@0: ReturnStatus *rs, bool *instanceof) michael@0: { michael@0: AutoSafeJSContext cx; michael@0: JSAutoRequest request(cx); michael@0: michael@0: *instanceof = false; michael@0: michael@0: RootedObject obj(cx, findObject(objId)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: JSAutoCompartment comp(cx, obj); michael@0: michael@0: bool tmp; michael@0: if (!mozilla::dom::InterfaceHasInstance(cx, prototypeID, depth, obj, &tmp)) michael@0: return fail(cx, rs); michael@0: *instanceof = tmp; michael@0: michael@0: return ok(rs); michael@0: }