/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 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 "mozilla/jsipc/ObjectWrapperParent.h" #include "mozilla/jsipc/ContextWrapperParent.h" #include "mozilla/jsipc/CPOWTypes.h" #include "mozilla/unused.h" #include "nsJSUtils.h" #include "jsutil.h" #include "jsfriendapi.h" using namespace mozilla::jsipc; namespace { // Only need one reserved slot because the ObjectWrapperParent* is // stored in the private slot. static const unsigned sFlagsSlot = 0; static const unsigned sNumSlots = 1; static const unsigned CPOW_FLAG_RESOLVING = 1 << 0; class AutoResolveFlag { JSObject* mObj; unsigned mOldFlags; JS_DECL_USE_GUARD_OBJECT_NOTIFIER static unsigned GetFlags(JSObject* obj) { jsval v = JS_GetReservedSlot(obj, sFlagsSlot); return JSVAL_TO_INT(v); } static unsigned SetFlags(JSObject* obj, unsigned flags) { unsigned oldFlags = GetFlags(obj); if (oldFlags != flags) JS_SetReservedSlot(obj, sFlagsSlot, INT_TO_JSVAL(flags)); return oldFlags; } public: AutoResolveFlag(JSObject* obj JS_GUARD_OBJECT_NOTIFIER_PARAM) : mObj(obj) , mOldFlags(SetFlags(obj, GetFlags(obj) | CPOW_FLAG_RESOLVING)) { JS_GUARD_OBJECT_NOTIFIER_INIT; } ~AutoResolveFlag() { SetFlags(mObj, mOldFlags); } static JSBool IsSet(JSObject* obj) { return GetFlags(obj) & CPOW_FLAG_RESOLVING; } }; class StatusMemberOwner { OperationStatus mStatus; public: StatusMemberOwner() : mStatus(JS_FALSE) {} OperationStatus* StatusPtr() { return &mStatus; } }; typedef AutoCheckOperationBase ACOBase; class AutoCheckOperation : public ACOBase { JS_DECL_USE_GUARD_OBJECT_NOTIFIER public: AutoCheckOperation(JSContext* cx, ObjectWrapperParent* owp JS_GUARD_OBJECT_NOTIFIER_PARAM) : ACOBase(cx, owp) { JS_GUARD_OBJECT_NOTIFIER_INIT; } }; } void ObjectWrapperParent::CheckOperation(JSContext* cx, OperationStatus* status) { NS_PRECONDITION(status->type() != OperationStatus::T__None, "Checking an uninitialized operation."); switch (status->type()) { case OperationStatus::TJSVariant: { jsval thrown; if (jsval_from_JSVariant(cx, status->get_JSVariant(), &thrown)) JS_SetPendingException(cx, thrown); *status = JS_FALSE; } break; case OperationStatus::TJSBool: if (!status->get_JSBool() && !JS_IsExceptionPending(cx)) { NS_WARNING("CPOW operation failed without setting an exception."); } break; default: NS_NOTREACHED("Invalid or uninitialized OperationStatus type."); break; } } template static RType with_error(JSContext* cx, RType rval, const char* error = NULL) { if (!JS_IsExceptionPending(cx)) JS_ReportError(cx, error ? error : "Unspecified CPOW error"); return rval; } const js::Class ObjectWrapperParent::sCPOW_JSClass = { "CrossProcessObjectWrapper", JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE | JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(sNumSlots), ObjectWrapperParent::CPOW_AddProperty, ObjectWrapperParent::CPOW_DelProperty, ObjectWrapperParent::CPOW_GetProperty, ObjectWrapperParent::CPOW_SetProperty, (JSEnumerateOp) ObjectWrapperParent::CPOW_NewEnumerate, (JSResolveOp) ObjectWrapperParent::CPOW_NewResolve, ObjectWrapperParent::CPOW_Convert, ObjectWrapperParent::CPOW_Finalize, nullptr, // checkAccess ObjectWrapperParent::CPOW_Call, ObjectWrapperParent::CPOW_HasInstance, ObjectWrapperParent::CPOW_Construct, nullptr, // trace { ObjectWrapperParent::CPOW_Equality, nullptr, // outerObject nullptr, // innerObject nullptr, // iteratorObject nullptr, // wrappedObject } }; void ObjectWrapperParent::ActorDestroy(ActorDestroyReason) { if (mObj) { JS_SetPrivate(mObj, NULL); mObj = NULL; } } ContextWrapperParent* ObjectWrapperParent::Manager() { PContextWrapperParent* pcwp = PObjectWrapperParent::Manager(); return static_cast(pcwp); } JSObject* ObjectWrapperParent::GetJSObject(JSContext* cx) const { if (!mObj) { js::Class *clasp = const_cast(&ObjectWrapperParent::sCPOW_JSClass); mObj = JS_NewObject(cx, js::Jsvalify(clasp), NULL, NULL); if (mObj) { JS_SetPrivate(mObj, (void*)this); JS_SetReservedSlot(mObj, sFlagsSlot, JSVAL_ZERO); } } return mObj; } static ObjectWrapperParent* Unwrap(JSContext* cx, JSObject* obj) { while (js::GetObjectClass(obj) != &ObjectWrapperParent::sCPOW_JSClass) { if (!js::GetObjectProto(cx, obj, &obj) || !obj) return NULL; } ObjectWrapperParent* self = static_cast(JS_GetPrivate(obj)); NS_ASSERTION(!self || self->GetJSObjectOrNull() == obj, "Wrapper and wrapped object disagree?"); return self; } /*static*/ bool ObjectWrapperParent::jsval_to_JSVariant(JSContext* cx, jsval from, JSVariant* to) { switch (JS_TypeOfValue(cx, from)) { case JSTYPE_VOID: *to = void_t(); return true; case JSTYPE_NULL: if (from != JSVAL_NULL) return false; // fall through case JSTYPE_FUNCTION: // CPOWs can fool JS_TypeOfValue into returning JSTYPE_FUNCTION // because they have a call hook, but CPOWs are really objects, so // fall through to the JSTYPE_OBJECT case: case JSTYPE_OBJECT: { PObjectWrapperParent* powp; if (!JSObject_to_PObjectWrapperParent(cx, JSVAL_TO_OBJECT(from), &powp)) return with_error(cx, false, "Cannot pass parent-created object to child"); *to = powp; } return true; case JSTYPE_STRING: { nsDependentJSString depStr; if (!depStr.init(cx, from)) return false; *to = depStr; } return true; case JSTYPE_NUMBER: if (JSVAL_IS_INT(from)) *to = JSVAL_TO_INT(from); else if (JSVAL_IS_DOUBLE(from)) *to = JSVAL_TO_DOUBLE(from); else return false; return true; case JSTYPE_BOOLEAN: *to = !!JSVAL_TO_BOOLEAN(from); return true; case JSTYPE_XML: return with_error(cx, false, "CPOWs currently cannot handle JSTYPE_XML"); default: return with_error(cx, false, "Bad jsval type"); } } /*static*/ bool ObjectWrapperParent::jsval_from_JSVariant(JSContext* cx, const JSVariant& from, jsval* to) { switch (from.type()) { case JSVariant::Tvoid_t: *to = JSVAL_VOID; return true; case JSVariant::TPObjectWrapperParent: return jsval_from_PObjectWrapperParent(cx, from.get_PObjectWrapperParent(), to); case JSVariant::TnsString: { JSString* str = JS_NewUCStringCopyZ(cx, from.get_nsString().BeginReading()); if (!str) return false; *to = STRING_TO_JSVAL(str); return true; } case JSVariant::Tint: *to = INT_TO_JSVAL(from.get_int()); return true; case JSVariant::Tdouble: *to = JS_NumberValue(from.get_double()); return true; case JSVariant::Tbool: *to = BOOLEAN_TO_JSVAL(from.get_bool()); return true; default: return false; } } /*static*/ bool ObjectWrapperParent:: JSObject_to_PObjectWrapperParent(JSContext* cx, JSObject* from, PObjectWrapperParent** to) { if (!from) { *to = NULL; return true; } ObjectWrapperParent* owp = Unwrap(cx, from); if (!owp) return false; *to = owp; return true; } /*static*/ bool ObjectWrapperParent:: JSObject_from_PObjectWrapperParent(JSContext* cx, const PObjectWrapperParent* from, JSMutableHandleObject to) { const ObjectWrapperParent* owp = static_cast(from); to.set(owp ? owp->GetJSObject(cx) : JSVAL_TO_OBJECT(JSVAL_NULL)); return true; } /*static*/ bool ObjectWrapperParent:: jsval_from_PObjectWrapperParent(JSContext* cx, const PObjectWrapperParent* from, jsval* to) { js::RootedObject obj(cx); if (!JSObject_from_PObjectWrapperParent(cx, from, &obj)) return false; *to = OBJECT_TO_JSVAL(obj); return true; } static bool jsid_from_int(JSContext* cx, int from, jsid* to) { jsval v = INT_TO_JSVAL(from); return JS_ValueToId(cx, v, to); } static bool jsid_from_nsString(JSContext* cx, const nsString& from, jsid* to) { JSString* str = JS_NewUCStringCopyZ(cx, from.BeginReading()); if (!str) return false; return JS_ValueToId(cx, STRING_TO_JSVAL(str), to); } static bool jsval_to_nsString(JSContext* cx, jsid from, nsString* to) { JSString* str; const jschar* chars; jsval idval; if (JS_IdToValue(cx, from, &idval) && (str = JS_ValueToString(cx, idval)) && (chars = JS_GetStringCharsZ(cx, str))) { *to = chars; return true; } return false; } /*static*/ JSBool ObjectWrapperParent::CPOW_AddProperty(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp) { CPOW_LOG(("Calling CPOW_AddProperty (%s)...", JSVAL_TO_CSTR(cx, id))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_AddProperty"); if (AutoResolveFlag::IsSet(obj)) return JS_TRUE; AutoCheckOperation aco(cx, self); nsString in_id; if (!jsval_to_nsString(cx, id, &in_id)) return JS_FALSE; return (self->Manager()->RequestRunToCompletion() && self->CallAddProperty(in_id, aco.StatusPtr()) && aco.Ok()); } /*static*/ JSBool ObjectWrapperParent::CPOW_GetProperty(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp) { CPOW_LOG(("Calling CPOW_GetProperty (%s)...", JSVAL_TO_CSTR(cx, id))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_GetProperty"); AutoCheckOperation aco(cx, self); nsString in_id; if (!jsval_to_nsString(cx, id, &in_id)) return JS_FALSE; JSVariant out_v; return (self->Manager()->RequestRunToCompletion() && self->CallGetProperty(in_id, aco.StatusPtr(), &out_v) && aco.Ok() && self->jsval_from_JSVariant(cx, out_v, vp.address())); } /*static*/ JSBool ObjectWrapperParent::CPOW_SetProperty(JSContext *cx, JSHandleObject obj, JSHandleId id, JSBool strict, JSMutableHandleValue vp) { CPOW_LOG(("Calling CPOW_SetProperty (%s)...", JSVAL_TO_CSTR(cx, id))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_SetProperty"); AutoCheckOperation aco(cx, self); nsString in_id; JSVariant in_v; if (!jsval_to_nsString(cx, id, &in_id) || !self->jsval_to_JSVariant(cx, vp, &in_v)) return JS_FALSE; JSVariant out_v; return (self->Manager()->RequestRunToCompletion() && self->CallSetProperty(in_id, in_v, aco.StatusPtr(), &out_v) && aco.Ok() && self->jsval_from_JSVariant(cx, out_v, vp.address())); } /*static*/ JSBool ObjectWrapperParent::CPOW_DelProperty(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp) { CPOW_LOG(("Calling CPOW_DelProperty (%s)...", JSVAL_TO_CSTR(cx, id))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_DelProperty"); AutoCheckOperation aco(cx, self); nsString in_id; if (!jsval_to_nsString(cx, id, &in_id)) return JS_FALSE; JSVariant out_v; return (self->Manager()->RequestRunToCompletion() && self->CallDelProperty(in_id, aco.StatusPtr(), &out_v) && aco.Ok() && jsval_from_JSVariant(cx, out_v, vp.address())); } JSBool ObjectWrapperParent::NewEnumerateInit(JSContext* cx, jsval* statep, jsid* idp) { AutoCheckOperation aco(cx, this); JSVariant out_state; int out_id; return (CallNewEnumerateInit(aco.StatusPtr(), &out_state, &out_id) && aco.Ok() && jsval_from_JSVariant(cx, out_state, statep) && (!idp || jsid_from_int(cx, out_id, idp))); } JSBool ObjectWrapperParent::NewEnumerateNext(JSContext* cx, jsval* statep, jsid* idp) { AutoCheckOperation aco(cx, this); JSVariant in_state; if (!jsval_to_JSVariant(cx, *statep, &in_state)) return JS_FALSE; JSVariant out_state; nsString out_id; if (CallNewEnumerateNext(in_state, aco.StatusPtr(), &out_state, &out_id) && aco.Ok() && jsval_from_JSVariant(cx, out_state, statep) && jsid_from_nsString(cx, out_id, idp)) { JSObject* obj = GetJSObject(cx); AutoResolveFlag arf(obj); return JS_DefinePropertyById(cx, obj, *idp, JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE); } return JS_FALSE; } JSBool ObjectWrapperParent::NewEnumerateDestroy(JSContext* cx, jsval state) { AutoCheckOperation aco(cx, this); JSVariant in_state; if (!jsval_to_JSVariant(cx, state, &in_state)) return JS_FALSE; return SendNewEnumerateDestroy(in_state); } /*static*/ JSBool ObjectWrapperParent::CPOW_NewEnumerate(JSContext *cx, JSHandleObject obj, JSIterateOp enum_op, jsval *statep, jsid *idp) { CPOW_LOG(("Calling CPOW_NewEnumerate...")); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_NewEnumerate"); switch (enum_op) { case JSENUMERATE_INIT: case JSENUMERATE_INIT_ALL: self->Manager()->RequestRunToCompletion(); return self->NewEnumerateInit(cx, statep, idp); case JSENUMERATE_NEXT: return self->NewEnumerateNext(cx, statep, idp); case JSENUMERATE_DESTROY: return self->NewEnumerateDestroy(cx, *statep); } NS_NOTREACHED("Unknown enum_op value in CPOW_NewEnumerate"); return JS_FALSE; } /*static*/ JSBool ObjectWrapperParent::CPOW_NewResolve(JSContext *cx, JSHandleObject obj, JSHandleId id, unsigned flags, JSMutableHandleObject objp) { CPOW_LOG(("Calling CPOW_NewResolve (%s)...", JSVAL_TO_CSTR(cx, id))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_NewResolve"); AutoCheckOperation aco(cx, self); nsString in_id; if (!jsval_to_nsString(cx, id, &in_id)) return JS_FALSE; PObjectWrapperParent* out_pobj; if (!self->Manager()->RequestRunToCompletion() || !self->CallNewResolve(in_id, flags, aco.StatusPtr(), &out_pobj) || !aco.Ok() || !JSObject_from_PObjectWrapperParent(cx, out_pobj, objp)) return JS_FALSE; if (objp) { AutoResolveFlag arf(objp); js::RootedObject obj2(cx, objp); JS_DefinePropertyById(cx, obj2, id, JSVAL_VOID, NULL, NULL, JSPROP_ENUMERATE); } return JS_TRUE; } /*static*/ JSBool ObjectWrapperParent::CPOW_Convert(JSContext *cx, JSHandleObject obj, JSType type, JSMutableHandleValue vp) { CPOW_LOG(("Calling CPOW_Convert (to %s)...", JS_GetTypeName(cx, type))); ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_Convert"); vp.set(OBJECT_TO_JSVAL(obj)); return JS_TRUE; } /*static*/ void ObjectWrapperParent::CPOW_Finalize(js::FreeOp* fop, JSObject* obj) { CPOW_LOG(("Calling CPOW_Finalize...")); MOZ_ASSERT(js::GetObjectClass(obj) == &ObjectWrapperParent::sCPOW_JSClass); ObjectWrapperParent* self = static_cast(JS_GetPrivate(obj)); if (self) { self->mObj = NULL; unused << ObjectWrapperParent::Send__delete__(self); } } /*static*/ JSBool ObjectWrapperParent::CPOW_Call(JSContext* cx, unsigned argc, jsval* vp) { CPOW_LOG(("Calling CPOW_Call...")); JSObject* thisobj = JS_THIS_OBJECT(cx, vp); if (!thisobj) return JS_FALSE; ObjectWrapperParent* function = Unwrap(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp))); if (!function) return with_error(cx, JS_FALSE, "Could not unwrap CPOW function"); AutoCheckOperation aco(cx, function); ObjectWrapperParent* receiver = Unwrap(cx, thisobj); if (!receiver) { // Substitute child global for parent global object. // TODO First make sure we're really replacing the global object? ContextWrapperParent* manager = static_cast(function->Manager()); receiver = manager->GetGlobalObjectWrapper(); } InfallibleTArray in_argv(argc); jsval* argv = JS_ARGV(cx, vp); for (unsigned i = 0; i < argc; i++) if (!jsval_to_JSVariant(cx, argv[i], in_argv.AppendElement())) return JS_FALSE; JSVariant out_rval; return (function->Manager()->RequestRunToCompletion() && function->CallCall(receiver, in_argv, aco.StatusPtr(), &out_rval) && aco.Ok() && jsval_from_JSVariant(cx, out_rval, vp)); } /*static*/ JSBool ObjectWrapperParent::CPOW_Construct(JSContext* cx, unsigned argc, jsval* vp) { CPOW_LOG(("Calling CPOW_Construct...")); ObjectWrapperParent* constructor = Unwrap(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp))); if (!constructor) return with_error(cx, JS_FALSE, "Could not unwrap CPOW constructor function"); AutoCheckOperation aco(cx, constructor); InfallibleTArray in_argv(argc); jsval* argv = JS_ARGV(cx, vp); for (unsigned i = 0; i < argc; i++) if (!jsval_to_JSVariant(cx, argv[i], in_argv.AppendElement())) return JS_FALSE; PObjectWrapperParent* out_powp; return (constructor->Manager()->RequestRunToCompletion() && constructor->CallConstruct(in_argv, aco.StatusPtr(), &out_powp) && aco.Ok() && jsval_from_PObjectWrapperParent(cx, out_powp, vp)); } /*static*/ JSBool ObjectWrapperParent::CPOW_HasInstance(JSContext *cx, JSHandleObject obj, JSMutableHandleValue v, JSBool *bp) { CPOW_LOG(("Calling CPOW_HasInstance...")); *bp = JS_FALSE; ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_HasInstance"); AutoCheckOperation aco(cx, self); JSVariant in_v; if (!jsval_to_JSVariant(cx, v, &in_v)) return JS_FALSE; return (self->Manager()->RequestRunToCompletion() && self->CallHasInstance(in_v, aco.StatusPtr(), bp) && aco.Ok()); } /*static*/ JSBool ObjectWrapperParent::CPOW_Equality(JSContext *cx, JSHandleObject obj, JSHandleValue v, JSBool *bp) { CPOW_LOG(("Calling CPOW_Equality...")); *bp = JS_FALSE; ObjectWrapperParent* self = Unwrap(cx, obj); if (!self) return with_error(cx, JS_FALSE, "Unwrapping failed in CPOW_Equality"); if (JSVAL_IS_PRIMITIVE(v)) return JS_TRUE; ObjectWrapperParent* other = Unwrap(cx, JSVAL_TO_OBJECT(v)); if (!other) return JS_TRUE; *bp = (self == other); return JS_TRUE; }