author | Ben Newman <bnewman@mozilla.com> |
Fri, 25 Jun 2010 08:00:37 -0700 | |
changeset 46232 | 810a18af65ae217133a203b481706258a52c9faf |
parent 46231 | 8ffaff868ccfb4d12bfcbfb3e086e7072114f30b |
child 46233 | 5ee6584ed7867e03b8f91d4b6ac2fdf13c13950d |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | bsmedberg, bent |
bugs | 556846 |
milestone | 1.9.3a6pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
new file mode 100644 --- /dev/null +++ b/js/jetpack/Handle.h @@ -0,0 +1,305 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=4 et tw=80: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Newman <mozilla@benjamn.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_jetpack_HandleParent_h +#define mozilla_jetpack_HandleParent_h + +#include "mozilla/jetpack/PHandleParent.h" +#include "mozilla/jetpack/PHandleChild.h" + +#include "jsapi.h" +#include "jsobj.h" +#include "jscntxt.h" + +namespace mozilla { +namespace jetpack { + +/** + * BaseType should be one of PHandleParent or PHandleChild; see the + * HandleParent and HandleChild typedefs at the bottom of this file. + */ +template <class BaseType> +class Handle + : public BaseType +{ + Handle(Handle* parent) + : mParent(parent) + , mObj(NULL) + , mRuntime(NULL) + {} + + BaseType* AllocPHandle() { + return new Handle(this); + } + + bool DeallocPHandle(BaseType* actor) { + delete actor; + return true; + } + +public: + + Handle() + : mParent(NULL) + , mObj(NULL) + , mRuntime(NULL) + {} + + ~Handle() { TearDown(); } + + static Handle* FromJSObject(JSContext* cx, JSObject* obj) { + // TODO Convert non-Handles to Handles somehow? + return Unwrap(cx, obj); + } + + static Handle* FromJSVal(JSContext* cx, jsval val) { + if (!JSVAL_IS_OBJECT(val)) + return NULL; + return Unwrap(cx, JSVAL_TO_OBJECT(val)); + } + + JSObject* ToJSObject(JSContext* cx) const { + if (!mObj && !mRuntime) { + JSAutoRequest request(cx); + + JSClass* clasp = const_cast<JSClass*>(&sHandle_JSClass); + JSObject* obj = JS_NewObject(cx, clasp, NULL, NULL); + if (!obj) + return NULL; + js::AutoObjectRooter root(cx, obj); + + JSPropertySpec* ps = const_cast<JSPropertySpec*>(sHandle_Properties); + JSFunctionSpec* fs = const_cast<JSFunctionSpec*>(sHandle_Functions); + JSRuntime* rt; + + const char* name = IsParent(this) + ? "mozilla::jetpack::Handle<PHandleParent>::mObj" + : "mozilla::jetpack::Handle<PHandleChild>::mObj"; + + if (JS_SetPrivate(cx, obj, (void*)this) && + JS_DefineProperties(cx, obj, ps) && + JS_DefineFunctions(cx, obj, fs) && + JS_AddNamedRootRT(rt = JS_GetRuntime(cx), (void*)&mObj, name)) + { + mObj = obj; + mRuntime = rt; + } + } + return mObj; + } + +protected: + + void ActorDestroy(typename Handle::ActorDestroyReason why) { + TearDown(); + } + +private: + + static bool IsParent(const PHandleParent* handle) { return true; } + static bool IsParent(const PHandleChild* handle) { return false; } + + void TearDown() { + if (mObj) { + mObj->setPrivate(NULL); + mObj = NULL; + // Nulling out mObj effectively unroots the object, but we still + // need to remove the root, else the JS engine will complain at + // shutdown. + NS_ASSERTION(mRuntime, "Should have a JSRuntime if we had an object"); + JS_RemoveRootRT(mRuntime, (void*)&mObj); + // By not nulling out mRuntime, we prevent ToJSObject from + // reviving an invalidated/destroyed handle. + } + } + + static const JSClass sHandle_JSClass; + static const JSPropertySpec sHandle_Properties[]; + static const JSFunctionSpec sHandle_Functions[]; + + Handle* const mParent; + + // Used to cache the JSObject returned by ToJSObject, which is + // otherwise a const method. + mutable JSObject* mObj; + mutable JSRuntime* mRuntime; + + static Handle* + Unwrap(JSContext* cx, JSObject* obj) { + while (obj && obj->getClass() != &sHandle_JSClass) + obj = obj->getProto(); + + if (!obj) + return NULL; + + Handle* self = static_cast<Handle*>(JS_GetPrivate(cx, obj)); + + NS_ASSERTION(!self || self->ToJSObject(cx) == obj, + "Wrapper and wrapped object disagree?"); + + return self; + } + + static JSBool + GetParent(JSContext* cx, JSObject* obj, jsval, jsval* vp) { + JS_SET_RVAL(cx, vp, JSVAL_NULL); + + Handle* self = Unwrap(cx, obj); + if (!self) + return JS_TRUE; + + Handle* parent = self->mParent; + if (!parent) + return JS_TRUE; + + JSObject* pobj = parent->ToJSObject(cx); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(pobj)); + + return JS_TRUE; + } + + static JSBool + GetIsValid(JSContext* cx, JSObject* obj, jsval, jsval* vp) { + Handle* self = Unwrap(cx, obj); + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(!!self)); + return JS_TRUE; + } + + static JSBool + Invalidate(JSContext* cx, uintN argc, jsval* vp) { + if (argc > 0) { + JS_ReportError(cx, "invalidate takes zero arguments"); + return JS_FALSE; + } + + Handle* self = Unwrap(cx, JS_THIS_OBJECT(cx, vp)); + if (self) { + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(JS_TRUE)); + if (!Send__delete__(self)) { + JS_ReportError(cx, "Failed to send __delete__ while invalidating"); + return JS_FALSE; + } + } else { + JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(JS_FALSE)); + } + + return JS_TRUE; + } + + static JSBool + CreateHandle(JSContext* cx, uintN argc, jsval* vp) { + if (argc > 0) { + JS_ReportError(cx, "createHandle takes zero arguments"); + return JS_FALSE; + } + + Handle* self = Unwrap(cx, JS_THIS_OBJECT(cx, vp)); + if (!self) { + JS_ReportError(cx, "Tried to create child from invalid handle"); + return JS_FALSE; + } + + BaseType* child = self->SendPHandleConstructor(); + if (!child) { + JS_ReportError(cx, "Failed to construct child"); + return JS_FALSE; + } + + JSObject* obj = static_cast<Handle*>(child)->ToJSObject(cx); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(obj)); + + return JS_TRUE; + } + + static void + Finalize(JSContext* cx, JSObject* obj) { + Handle* self = Unwrap(cx, obj); + // Avoid warnings about unused return values: + self && Send__delete__(self); + } + +}; + +template <class BaseType> +const JSClass +Handle<BaseType>::sHandle_JSClass = { + "IPDL Handle", JSCLASS_HAS_PRIVATE, + JS_PropertyStub, JS_PropertyStub, + JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, + JS_ConvertStub, Handle::Finalize, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + +#define HANDLE_PROP_FLAGS (JSPROP_READONLY | JSPROP_PERMANENT) + +template <class BaseType> +const JSPropertySpec +Handle<BaseType>::sHandle_Properties[] = { + { "parent", 0, HANDLE_PROP_FLAGS, GetParent, NULL }, + { "isValid", 0, HANDLE_PROP_FLAGS, GetIsValid, NULL }, + { 0, 0, 0, NULL, NULL } +}; + +#undef HANDLE_PROP_FLAGS + +#define HANDLE_FUN_FLAGS (JSFUN_FAST_NATIVE | \ + JSPROP_READONLY | \ + JSPROP_PERMANENT) + +template <class BaseType> +const JSFunctionSpec +Handle<BaseType>::sHandle_Functions[] = { + JS_FN("invalidate", Invalidate, 0, HANDLE_FUN_FLAGS), + JS_FN("createHandle", CreateHandle, 0, HANDLE_FUN_FLAGS), + JS_FS_END +}; + +#undef HANDLE_FUN_FLAGS + +// The payoff for using templates is that these two implementations are +// guaranteed to be perfectly symmetric: +typedef Handle<PHandleParent> HandleParent; +typedef Handle<PHandleChild> HandleChild; + +} // namespace jetpack +} // namespace mozilla + +#endif
new file mode 100644 --- /dev/null +++ b/js/jetpack/JetpackActorCommon.cpp @@ -0,0 +1,560 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Firefox. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation <http://www.mozilla.org>. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Newman <mozilla@benjamn.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "base/basictypes.h" +#include "jscntxt.h" + +#include "mozilla/jetpack/JetpackActorCommon.h" +#include "mozilla/jetpack/PJetpack.h" +#include "mozilla/jetpack/PHandleParent.h" +#include "mozilla/jetpack/PHandleChild.h" +#include "mozilla/jetpack/Handle.h" + +#include "jsapi.h" +#include "jstl.h" +#include "jshashtable.h" + +using mozilla::jetpack::JetpackActorCommon; +using mozilla::jetpack::PHandleParent; +using mozilla::jetpack::HandleParent; +using mozilla::jetpack::PHandleChild; +using mozilla::jetpack::HandleChild; +using mozilla::jetpack::KeyValue; +using mozilla::jetpack::PrimVariant; +using mozilla::jetpack::CompVariant; +using mozilla::jetpack::Variant; + +class JetpackActorCommon::OpaqueSeenType +{ +public: + typedef JSObject* KeyType; + typedef size_t IdType; + typedef js::HashMap< + KeyType, IdType, + js::DefaultHasher<KeyType>, + js::SystemAllocPolicy + > MapType; + + OpaqueSeenType() { + NS_ASSERTION(map.init(1), "Failed to initialize map"); + } + + bool ok() { return map.initialized(); } + + // Reserving 0 as an invalid ID means starting the valid IDs at 1. We + // could have reserved a dummy first element of rmap, but that would + // have wasted space. + static const IdType kInvalidId = 0; + + bool add(KeyType obj, IdType* id) { + MapType::AddPtr ap = map.lookupForAdd(obj); + if (!ap) { + if (!rmap.AppendElement(obj) || + !map.add(ap, obj, *id = rmap.Length())) + *id = kInvalidId; + return true; + } + *id = ap->value; + return false; + } + + KeyType reverseLookup(IdType id) { + return rmap.SafeElementAt(id - 1, NULL); + } + +private: + MapType map; + nsAutoTArray<KeyType, 4> rmap; + +}; + +bool +JetpackActorCommon::jsval_to_PrimVariant(JSContext* cx, JSType type, jsval from, + PrimVariant* to) +{ + // A false return from this function just means the value couldn't be + // converted to a PrimVariant (i.e., it wasn't a primitive value). + + switch (type) { + case JSTYPE_VOID: + *to = void_t(); + return true; + + case JSTYPE_NULL: + *to = null_t(); + return true; + + case JSTYPE_FUNCTION: + return false; + + case JSTYPE_OBJECT: { + HandleParent* hp = HandleParent::FromJSVal(cx, from); + HandleChild* hc = HandleChild::FromJSVal(cx, from); + NS_ASSERTION(!hc || !hp, "Can't be both a parent and a child"); + if (hp) { + *to = hp; + return true; + } + if (hc) { + *to = hc; + return true; + } + return false; + } + + case JSTYPE_STRING: + *to = nsDependentString((PRUnichar*)JS_GetStringChars(JSVAL_TO_STRING(from)), + JS_GetStringLength(JSVAL_TO_STRING(from))); + 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 false; + + default: + return false; + } +} + +bool +JetpackActorCommon::jsval_to_CompVariant(JSContext* cx, JSType type, jsval from, + CompVariant* to, OpaqueSeenType* seen) +{ + if (type != JSTYPE_OBJECT) + return false; + + js::LazilyConstructed<OpaqueSeenType> lost; + if (!seen) { + lost.construct(); + seen = lost.addr(); + if (!seen->ok()) + return false; + } + + OpaqueSeenType::KeyType obj = JSVAL_TO_OBJECT(from); + OpaqueSeenType::IdType id; + if (!seen->add(obj, &id)) { + if (OpaqueSeenType::kInvalidId == id) + return false; + *to = CompVariant(id); + return true; + } + + if (JS_IsArrayObject(cx, obj)) { + nsTArray<Variant> elems; + jsuint len; + if (!JS_GetArrayLength(cx, obj, &len) || + !elems.SetCapacity(len)) + return false; + for (jsuint i = 0; i < len; ++i) { + jsval val; + Variant* vp = elems.AppendElement(); + if (!JS_GetElement(cx, obj, i, &val) || + !jsval_to_Variant(cx, val, vp, seen)) + *vp = void_t(); + } + *to = elems; + return true; + } + + js::AutoIdArray ida(cx, JS_Enumerate(cx, obj)); + if (!ida) + return false; + + nsTArray<KeyValue> kvs; + for (size_t i = 0; i < ida.length(); ++i) { + jsval val; // reused for both key and value + if (!JS_IdToValue(cx, ida[i], &val)) + return false; + JSString* idStr = JS_ValueToString(cx, val); + if (!idStr) + return false; + if (!JS_GetPropertyById(cx, obj, ida[i], &val)) + return false; + KeyValue kv; + // Silently drop properties that can't be converted. + if (jsval_to_Variant(cx, val, &kv.value(), seen)) { + kv.key() = nsDependentString((PRUnichar*)JS_GetStringChars(idStr), + JS_GetStringLength(idStr)); + // If AppendElement fails, we lose this property, no big deal. + kvs.AppendElement(kv); + } + } + *to = kvs; + + return true; +} + +bool +JetpackActorCommon::jsval_to_Variant(JSContext* cx, jsval from, Variant* to, + OpaqueSeenType* seen) +{ + JSType type = JS_TypeOfValue(cx, from); + if (JSVAL_IS_NULL(from)) + type = JSTYPE_NULL; + + PrimVariant pv; + if (jsval_to_PrimVariant(cx, type, from, &pv)) { + *to = pv; + return true; + } + + CompVariant cv; + if (jsval_to_CompVariant(cx, type, from, &cv, seen)) { + *to = cv; + return true; + } + + return false; +} + +bool +JetpackActorCommon::jsval_from_PrimVariant(JSContext* cx, + const PrimVariant& from, + jsval* to) +{ + switch (from.type()) { + case PrimVariant::Tvoid_t: + *to = JSVAL_VOID; + return true; + + case PrimVariant::Tnull_t: + *to = JSVAL_NULL; + return true; + + case PrimVariant::Tbool: + *to = from.get_bool() ? JSVAL_TRUE : JSVAL_FALSE; + return true; + + case PrimVariant::Tint: + *to = INT_TO_JSVAL(from.get_int()); + return true; + + case PrimVariant::Tdouble: + return !!JS_NewDoubleValue(cx, from.get_double(), to); + + case PrimVariant::TnsString: { + const nsString& str = from.get_nsString(); + // TODO Use some sort of sharedstring/stringbuffer abstraction to + // exploit sharing opportunities more generally. + if (!str.Length()) { + *to = JS_GetEmptyStringValue(cx); + return true; + } + JSString* s = + JS_NewUCStringCopyN(cx, str.get(), str.Length()); + if (!s) + return false; + *to = STRING_TO_JSVAL(s); + return true; + } + + case PrimVariant::TPHandleParent: { + JSObject* hobj = + static_cast<const HandleParent*>(from.get_PHandleParent())->ToJSObject(cx); + if (!hobj) + return false; + *to = OBJECT_TO_JSVAL(hobj); + return true; + } + + case PrimVariant::TPHandleChild: { + JSObject* hobj = + static_cast<const HandleChild*>(from.get_PHandleChild())->ToJSObject(cx); + if (!hobj) + return false; + *to = OBJECT_TO_JSVAL(hobj); + return true; + } + + default: + return false; + } +} + +bool +JetpackActorCommon::jsval_from_CompVariant(JSContext* cx, + const CompVariant& from, + jsval* to, + OpaqueSeenType* seen) +{ + js::LazilyConstructed<OpaqueSeenType> lost; + if (!seen) { + lost.construct(); + seen = lost.addr(); + if (!seen->ok()) + return false; + } + + JSObject* obj = NULL; + + switch (from.type()) { + case CompVariant::TArrayOfKeyValue: { + if (!(obj = JS_NewObject(cx, NULL, NULL, NULL))) + return false; + js::AutoObjectRooter root(cx, obj); + + OpaqueSeenType::IdType ignored; + if (!seen->add(obj, &ignored)) + return false; + + const nsTArray<KeyValue>& kvs = from.get_ArrayOfKeyValue(); + for (PRUint32 i = 0; i < kvs.Length(); ++i) { + const KeyValue& kv = kvs.ElementAt(i); + js::AutoValueRooter toSet(cx); + if (!jsval_from_Variant(cx, kv.value(), toSet.addr(), seen) || + !JS_SetUCProperty(cx, obj, + kv.key().get(), + kv.key().Length(), + toSet.addr())) + return false; + } + + break; + } + + case CompVariant::TArrayOfVariant: { + const nsTArray<Variant>& vs = from.get_ArrayOfVariant(); + nsAutoTArray<jsval, 8> jsvals; + jsval* elems = jsvals.AppendElements(vs.Length()); + if (!elems) + return false; + for (PRUint32 i = 0; i < vs.Length(); ++i) + elems[i] = JSVAL_VOID; + js::AutoArrayRooter root(cx, vs.Length(), elems); + + OpaqueSeenType::IdType ignored; + if (!seen->add(obj, &ignored)) + return false; + + for (PRUint32 i = 0; i < vs.Length(); ++i) + if (!jsval_from_Variant(cx, vs.ElementAt(i), elems + i, seen)) + return false; + + if (!(obj = JS_NewArrayObject(cx, vs.Length(), elems))) + return false; + + break; + } + + case CompVariant::Tsize_t: + if (!(obj = seen->reverseLookup(from.get_size_t()))) + return false; + break; + + default: + return false; + } + + *to = OBJECT_TO_JSVAL(obj); + return true; +} + +bool +JetpackActorCommon::jsval_from_Variant(JSContext* cx, const Variant& from, + jsval* to, OpaqueSeenType* seen) +{ + switch (from.type()) { + case Variant::TPrimVariant: + return jsval_from_PrimVariant(cx, from, to); + case Variant::TCompVariant: + return jsval_from_CompVariant(cx, from, to, seen); + default: + return false; + } +} + +bool +JetpackActorCommon::RecvMessage(JSContext* cx, + const nsString& messageName, + const nsTArray<Variant>& data, + nsTArray<Variant>* results) +{ + if (results) + results->Clear(); + + RecList* list; + if (!mReceivers.Get(messageName, &list)) + return true; + nsAutoTArray<jsval, 4> snapshot; + list->copyTo(snapshot); + if (!snapshot.Length()) + return true; + + nsAutoTArray<jsval, 4> args; + PRUint32 argc = data.Length() + 1; + jsval* argv = args.AppendElements(argc); + if (!argv) + return false; + for (PRUint32 i = 0; i < argc; ++i) + argv[i] = JSVAL_VOID; + js::AutoArrayRooter argvRooter(cx, argc, argv); + + JSString* msgNameStr = + JS_NewUCStringCopyN(cx, + messageName.get(), + messageName.Length()); + if (!msgNameStr) + return false; + argv[0] = STRING_TO_JSVAL(msgNameStr); + + for (PRUint32 i = 0; i < data.Length(); ++i) + if (!jsval_from_Variant(cx, data.ElementAt(i), argv + i + 1)) + return false; + + JSObject* implGlobal = JS_GetGlobalObject(cx); + js::AutoValueRooter rval(cx); + + const uint32 savedOptions = + JS_SetOptions(cx, JS_GetOptions(cx) | JSOPTION_DONT_REPORT_UNCAUGHT); + + for (PRUint32 i = 0; i < snapshot.Length(); ++i) { + Variant* vp = results ? results->AppendElement() : NULL; + rval.set(JSVAL_VOID); + if (!JS_CallFunctionValue(cx, implGlobal, snapshot[i], argc, argv, + rval.addr())) { + // If a receiver throws, we drop the exception on the floor. + JS_ClearPendingException(cx); + if (vp) + *vp = void_t(); + } else if (vp && !jsval_to_Variant(cx, rval.value(), vp)) + *vp = void_t(); + } + + JS_SetOptions(cx, savedOptions); + + return true; +} + +JetpackActorCommon::RecList::~RecList() +{ + while (mHead) { + RecNode* old = mHead; + mHead = mHead->down; + delete old; + } +} + +void +JetpackActorCommon::RecList::add(jsval v) +{ + RecNode* node = mHead, *tail = NULL; + while (node) { + if (node->value() == v) + return; + node = (tail = node)->down; + } + node = new RecNode(mCx, v); + if (tail) + tail->down = node; + else + mHead = node; +} + +void +JetpackActorCommon::RecList::remove(jsval v) +{ + while (mHead && mHead->value() == v) { + RecNode* old = mHead; + mHead = mHead->down; + delete old; + } + if (!mHead) + return; + RecNode* prev = mHead, *node = prev->down; + while (node) { + if (node->value() == v) { + prev->down = node->down; + delete node; + } + node = (prev = node)->down; + } +} + +void +JetpackActorCommon::RecList::copyTo(nsTArray<jsval>& dst) const +{ + dst.Clear(); + for (RecNode* node = mHead; node; node = node->down) + dst.AppendElement(node->value()); +} + +nsresult +JetpackActorCommon::RegisterReceiver(JSContext* cx, + const nsString& messageName, + jsval receiver) +{ + if (!JSVAL_IS_OBJECT(receiver) || + !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(receiver))) + return NS_ERROR_INVALID_ARG; + + RecList* list; + if (!mReceivers.Get(messageName, &list)) { + list = new RecList(cx); + if (!list || !mReceivers.Put(messageName, list)) { + delete list; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + list->add(receiver); + + return NS_OK; +} + +void +JetpackActorCommon::UnregisterReceiver(const nsString& messageName, + jsval receiver) +{ + RecList* list; + if (!mReceivers.Get(messageName, &list)) + return; + list->remove(receiver); +}
new file mode 100644 --- /dev/null +++ b/js/jetpack/JetpackActorCommon.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Firefox. + * + * The Initial Developer of the Original Code is + * the Mozilla Foundation <http://www.mozilla.org>. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Newman <mozilla@benjamn.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_jetpack_JetpackActorCommon_h +#define mozilla_jetpack_JetpackActorCommon_h + +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsAutoJSValHolder.h" + +struct JSContext; + +namespace mozilla { +namespace jetpack { + +struct KeyValue; +class PrimVariant; +class CompVariant; +class Variant; + +class JetpackActorCommon +{ +public: + + bool + RecvMessage(JSContext* cx, + const nsString& messageName, + const nsTArray<Variant>& data, + nsTArray<Variant>* results); + + nsresult + RegisterReceiver(JSContext* cx, + const nsString& messageName, + jsval receiver); + + void + UnregisterReceiver(const nsString& messageName, + jsval receiver); + + void + UnregisterReceivers(const nsString& messageName) { + mReceivers.Remove(messageName); + } + + void ClearReceivers() { + mReceivers.Clear(); + } + + class OpaqueSeenType; + static bool jsval_to_Variant(JSContext* cx, jsval from, Variant* to, + OpaqueSeenType* seen = NULL); + static bool jsval_from_Variant(JSContext* cx, const Variant& from, jsval* to, + OpaqueSeenType* seen = NULL); + +protected: + + JetpackActorCommon() { + mReceivers.Init(); + NS_ASSERTION(mReceivers.IsInitialized(), + "Failed to initialize message receiver hash set"); + } + +private: + + static bool jsval_to_PrimVariant(JSContext* cx, JSType type, jsval from, + PrimVariant* to); + static bool jsval_to_CompVariant(JSContext* cx, JSType type, jsval from, + CompVariant* to, OpaqueSeenType* seen); + + static bool jsval_from_PrimVariant(JSContext* cx, const PrimVariant& from, + jsval* to); + static bool jsval_from_CompVariant(JSContext* cx, const CompVariant& from, + jsval* to, OpaqueSeenType* seen); + + // Don't want to be memcpy'ing nsAutoJSValHolders around, so we need a + // linked list of receivers. + class RecList + { + JSContext* mCx; + class RecNode + { + nsAutoJSValHolder mHolder; + public: + RecNode* down; + RecNode(JSContext* cx, jsval v) : down(NULL) { + mHolder.Hold(cx); + mHolder = v; + } + jsval value() { return mHolder; } + }* mHead; + public: + RecList(JSContext* cx) : mCx(cx), mHead(NULL) {} + ~RecList(); + void add(jsval v); + void remove(jsval v); + void copyTo(nsTArray<jsval>& dst) const; + }; + + nsClassHashtable<nsStringHashKey, RecList> mReceivers; + +}; + +} // namespace jetpack +} // namespace mozilla + +#endif
--- a/js/jetpack/JetpackChild.cpp +++ b/js/jetpack/JetpackChild.cpp @@ -30,54 +30,395 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#include "mozilla/jetpack/JetpackChild.h" +#include "base/basictypes.h" +#include "jscntxt.h" -#include <stdio.h> +#include "mozilla/jetpack/JetpackChild.h" +#include "mozilla/jetpack/Handle.h" + +#include "jsarray.h" namespace mozilla { namespace jetpack { JetpackChild::JetpackChild() { - NS_ASSERTION(!gInstance, "Something terribly wrong here!"); - gInstance = this; } JetpackChild::~JetpackChild() { - NS_ASSERTION(gInstance == this, "Something terribly wrong here!"); - gInstance = nsnull; } +#define IMPL_PROP_FLAGS (JSPROP_SHARED | \ + JSPROP_ENUMERATE | \ + JSPROP_READONLY | \ + JSPROP_PERMANENT) +const JSPropertySpec +JetpackChild::sImplProperties[] = { + { "jetpack", 0, IMPL_PROP_FLAGS, UserJetpackGetter, NULL }, + { 0, 0, 0, NULL, NULL } +}; + +#undef IMPL_PROP_FLAGS + +#define IMPL_METHOD_FLAGS (JSFUN_FAST_NATIVE | \ + JSPROP_ENUMERATE | \ + JSPROP_READONLY | \ + JSPROP_PERMANENT) +const JSFunctionSpec +JetpackChild::sImplMethods[] = { + JS_FN("sendMessage", SendMessage, 3, IMPL_METHOD_FLAGS), + JS_FN("callMessage", CallMessage, 2, IMPL_METHOD_FLAGS), + JS_FN("registerReceiver", RegisterReceiver, 2, IMPL_METHOD_FLAGS), + JS_FN("unregisterReceiver", UnregisterReceiver, 2, IMPL_METHOD_FLAGS), + JS_FN("unregisterReceivers", UnregisterReceivers, 1, IMPL_METHOD_FLAGS), + JS_FN("wrap", Wrap, 1, IMPL_METHOD_FLAGS), + JS_FN("createHandle", CreateHandle, 0, IMPL_METHOD_FLAGS), + JS_FS_END +}; + +#undef IMPL_METHOD_FLAGS + +const JSClass +JetpackChild::sGlobalClass = { + "JetpackChild::sGlobalClass", JSCLASS_GLOBAL_FLAGS, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + bool JetpackChild::Init(base::ProcessHandle aParentProcessHandle, MessageLoop* aIOLoop, IPC::Channel* aChannel) { if (!Open(aChannel, aParentProcessHandle, aIOLoop)) return false; + if (!(mRuntime = JS_NewRuntime(32L * 1024L * 1024L)) || + !(mImplCx = JS_NewContext(mRuntime, 8192)) || + !(mUserCx = JS_NewContext(mRuntime, 8192))) + return false; + + { + JSAutoRequest request(mImplCx); + JS_SetContextPrivate(mImplCx, this); + JSObject* implGlobal = + JS_NewGlobalObject(mImplCx, const_cast<JSClass*>(&sGlobalClass)); + if (!implGlobal || + !JS_InitStandardClasses(mImplCx, implGlobal) || + !JS_DefineProperties(mImplCx, implGlobal, + const_cast<JSPropertySpec*>(sImplProperties)) || + !JS_DefineFunctions(mImplCx, implGlobal, + const_cast<JSFunctionSpec*>(sImplMethods))) + return false; + } + + { + JSAutoRequest request(mUserCx); + JS_SetContextPrivate(mUserCx, this); + JSObject* userGlobal = + JS_NewGlobalObject(mUserCx, const_cast<JSClass*>(&sGlobalClass)); + if (!userGlobal || + !JS_InitStandardClasses(mUserCx, userGlobal)) + return false; + } + return true; } void JetpackChild::CleanUp() { + JS_DestroyContext(mUserCx); + JS_DestroyContext(mImplCx); + JS_DestroyRuntime(mRuntime); + JS_ShutDown(); +} + +bool +JetpackChild::RecvSendMessage(const nsString& messageName, + const nsTArray<Variant>& data) +{ + JSAutoRequest request(mImplCx); + return JetpackActorCommon::RecvMessage(mImplCx, messageName, data, NULL); +} + +static bool +Evaluate(JSContext* cx, const nsCString& code) +{ + JSAutoRequest request(cx); + js::AutoValueRooter ignored(cx); + JS_EvaluateScript(cx, JS_GetGlobalObject(cx), code.get(), + code.Length(), "", 1, ignored.addr()); + return true; +} + +bool +JetpackChild::RecvLoadImplementation(const nsCString& code) +{ + return Evaluate(mImplCx, code); +} + +bool +JetpackChild::RecvLoadUserScript(const nsCString& code) +{ + return Evaluate(mUserCx, code); +} + +PHandleChild* +JetpackChild::AllocPHandle() +{ + return new HandleChild(); } bool -JetpackChild::RecvLoadImplementation(const nsCString& script) +JetpackChild::DeallocPHandle(PHandleChild* actor) { - printf("Received LoadImplementation message: '%s'\n", script.get()); + delete actor; return true; } -JetpackChild* JetpackChild::gInstance; +JetpackChild* +JetpackChild::GetThis(JSContext* cx) +{ + JetpackChild* self = + static_cast<JetpackChild*>(JS_GetContextPrivate(cx)); + JS_ASSERT(cx == self->mImplCx || + cx == self->mUserCx); + return self; +} + +JSBool +JetpackChild::UserJetpackGetter(JSContext* cx, JSObject* obj, jsval id, + jsval* vp) +{ + JSObject* userGlobal = JS_GetGlobalObject(GetThis(cx)->mUserCx); + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(userGlobal)); + return JS_TRUE; +} + +struct MessageResult { + nsString msgName; + nsTArray<Variant> data; +}; + +static JSBool +MessageCommon(JSContext* cx, uintN argc, jsval* vp, + MessageResult* result) +{ + if (argc < 1) { + JS_ReportError(cx, "Message requires a name, at least"); + return JS_FALSE; + } + + jsval* argv = JS_ARGV(cx, vp); + + JSString* msgNameStr = JS_ValueToString(cx, argv[0]); + if (!msgNameStr) { + JS_ReportError(cx, "Could not convert value to string"); + return JS_FALSE; + } + + result->msgName.Assign((PRUnichar*)JS_GetStringChars(msgNameStr), + JS_GetStringLength(msgNameStr)); + + result->data.Clear(); + + if (!result->data.SetCapacity(argc)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + for (uintN i = 1; i < argc; ++i) { + Variant* vp = result->data.AppendElement(); + if (!JetpackActorCommon::jsval_to_Variant(cx, argv[i], vp)) { + JS_ReportError(cx, "Invalid message argument at position %d", i); + return JS_FALSE; + } + } + + return JS_TRUE; +} + +JSBool +JetpackChild::SendMessage(JSContext* cx, uintN argc, jsval* vp) +{ + MessageResult smr; + if (!MessageCommon(cx, argc, vp, &smr)) + return JS_FALSE; + + if (!GetThis(cx)->SendSendMessage(smr.msgName, smr.data)) { + JS_ReportError(cx, "Failed to sendMessage"); + return JS_FALSE; + } + + return JS_TRUE; +} + +JSBool +JetpackChild::CallMessage(JSContext* cx, uintN argc, jsval* vp) +{ + MessageResult smr; + if (!MessageCommon(cx, argc, vp, &smr)) + return JS_FALSE; + + nsTArray<Variant> results; + if (!GetThis(cx)->SendCallMessage(smr.msgName, smr.data, &results)) { + JS_ReportError(cx, "Failed to callMessage"); + return JS_FALSE; + } + + nsAutoTArray<jsval, 4> jsvals; + jsval* rvals = jsvals.AppendElements(results.Length()); + if (!rvals) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + for (PRUint32 i = 0; i < results.Length(); ++i) + rvals[i] = JSVAL_VOID; + js::AutoArrayRooter root(cx, results.Length(), rvals); + + for (PRUint32 i = 0; i < results.Length(); ++i) + if (!jsval_from_Variant(cx, results.ElementAt(i), rvals + i)) { + JS_ReportError(cx, "Invalid result from handler %d", i); + return JS_FALSE; + } + + JSObject* arrObj = JS_NewArrayObject(cx, results.Length(), rvals); + if (!arrObj) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(arrObj)); + + return JS_TRUE; +} + +struct ReceiverResult +{ + nsString msgName; + jsval receiver; +}; + +static JSBool +ReceiverCommon(JSContext* cx, uintN argc, jsval* vp, + const char* methodName, uintN arity, + ReceiverResult* result) +{ + if (argc != arity) { + JS_ReportError(cx, "%s requires exactly %d arguments", methodName, arity); + return JS_FALSE; + } + + // Not currently possible, but think of the future. + if (arity < 1) + return JS_TRUE; + + jsval* argv = JS_ARGV(cx, vp); + + JSString* str = JS_ValueToString(cx, argv[0]); + if (!str) { + JS_ReportError(cx, "%s expects a stringifiable value as its first argument", + methodName); + return JS_FALSE; + } + + result->msgName.Assign((PRUnichar*)JS_GetStringChars(str), + JS_GetStringLength(str)); + + if (arity < 2) + return JS_TRUE; + + if (!JSVAL_IS_OBJECT(argv[1]) || + !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[1]))) + { + JS_ReportError(cx, "%s expects a function as its second argument", + methodName); + return JS_FALSE; + } + + // GC-safe because argv is rooted. + result->receiver = argv[1]; + + return JS_TRUE; +} + +JSBool +JetpackChild::RegisterReceiver(JSContext* cx, uintN argc, jsval* vp) +{ + ReceiverResult rr; + if (!ReceiverCommon(cx, argc, vp, "registerReceiver", 2, &rr)) + return JS_FALSE; + + JetpackActorCommon* actor = GetThis(cx); + nsresult rv = actor->RegisterReceiver(cx, rr.msgName, rr.receiver); + if (NS_FAILED(rv)) { + JS_ReportOutOfMemory(cx); + return JS_FALSE; + } + + return JS_TRUE; +} + +JSBool +JetpackChild::UnregisterReceiver(JSContext* cx, uintN argc, jsval* vp) +{ + ReceiverResult rr; + if (!ReceiverCommon(cx, argc, vp, "unregisterReceiver", 2, &rr)) + return JS_FALSE; + + JetpackActorCommon* actor = GetThis(cx); + actor->UnregisterReceiver(rr.msgName, rr.receiver); + return JS_TRUE; +} + +JSBool +JetpackChild::UnregisterReceivers(JSContext* cx, uintN argc, jsval* vp) +{ + ReceiverResult rr; + if (!ReceiverCommon(cx, argc, vp, "unregisterReceivers", 1, &rr)) + return JS_FALSE; + + JetpackActorCommon* actor = GetThis(cx); + actor->UnregisterReceivers(rr.msgName); + return JS_TRUE; +} + +JSBool +JetpackChild::Wrap(JSContext* cx, uintN argc, jsval* vp) +{ + NS_NOTYETIMPLEMENTED("wrap not yet implemented (depends on bug 563010)"); + return JS_FALSE; +} + +JSBool +JetpackChild::CreateHandle(JSContext* cx, uintN argc, jsval* vp) +{ + if (argc > 0) { + JS_ReportError(cx, "createHandle takes zero arguments"); + return JS_FALSE; + } + + HandleChild* handle; + JSObject* hobj; + + PHandleChild* phc = GetThis(cx)->SendPHandleConstructor(); + if (!(handle = static_cast<HandleChild*>(phc)) || + !(hobj = handle->ToJSObject(cx))) { + JS_ReportError(cx, "Failed to construct Handle"); + return JS_FALSE; + } + + JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(hobj)); + + return JS_TRUE; +} } // namespace jetpack } // namespace mozilla
--- a/js/jetpack/JetpackChild.h +++ b/js/jetpack/JetpackChild.h @@ -34,39 +34,70 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef mozilla_jetpack_JetpackChild_h #define mozilla_jetpack_JetpackChild_h #include "mozilla/jetpack/PJetpackChild.h" +#include "mozilla/jetpack/JetpackActorCommon.h" + +#include "nsTArray.h" namespace mozilla { namespace jetpack { -class JetpackChild : public PJetpackChild +class PHandleChild; + +class JetpackChild + : public PJetpackChild + , private JetpackActorCommon { public: JetpackChild(); ~JetpackChild(); static JetpackChild* current(); bool Init(base::ProcessHandle aParentProcessHandle, MessageLoop* aIOLoop, IPC::Channel* aChannel); void CleanUp(); protected: - NS_OVERRIDE virtual bool RecvLoadImplementation(const nsCString& script); + NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName, + const nsTArray<Variant>& data); + NS_OVERRIDE virtual bool RecvLoadImplementation(const nsCString& code); + NS_OVERRIDE virtual bool RecvLoadUserScript(const nsCString& code); + + NS_OVERRIDE virtual PHandleChild* AllocPHandle(); + NS_OVERRIDE virtual bool DeallocPHandle(PHandleChild* actor); private: - static JetpackChild* gInstance; + JSRuntime* mRuntime; + JSContext *mImplCx, *mUserCx; + + static JetpackChild* GetThis(JSContext* cx); + + static const JSPropertySpec sImplProperties[]; + static JSBool UserJetpackGetter(JSContext* cx, JSObject* obj, jsval idval, + jsval* vp); + + static const JSFunctionSpec sImplMethods[]; + static JSBool SendMessage(JSContext* cx, uintN argc, jsval *vp); + static JSBool CallMessage(JSContext* cx, uintN argc, jsval *vp); + static JSBool RegisterReceiver(JSContext* cx, uintN argc, jsval *vp); + static JSBool UnregisterReceiver(JSContext* cx, uintN argc, jsval *vp); + static JSBool UnregisterReceivers(JSContext* cx, uintN argc, jsval *vp); + static JSBool Wrap(JSContext* cx, uintN argc, jsval *vp); + static JSBool CreateHandle(JSContext* cx, uintN argc, jsval *vp); + + static const JSClass sGlobalClass; DISALLOW_EVIL_CONSTRUCTORS(JetpackChild); }; } // namespace jetpack } // namespace mozilla
--- a/js/jetpack/JetpackParent.cpp +++ b/js/jetpack/JetpackParent.cpp @@ -31,40 +31,210 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "mozilla/jetpack/JetpackParent.h" +#include "mozilla/jetpack/Handle.h" + +#include "nsIURI.h" +#include "nsNetUtil.h" +#include "nsIVariant.h" +#include "nsIXPConnect.h" namespace mozilla { namespace jetpack { -JetpackParent::JetpackParent() +JetpackParent::JetpackParent(JSContext* cx) : mSubprocess(new JetpackProcessParent()) + , mContext(cx) { mSubprocess->Launch(); Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle()); } JetpackParent::~JetpackParent() { XRE_GetIOMessageLoop() ->PostTask(FROM_HERE, new DeleteTask<JetpackProcessParent>(mSubprocess)); } NS_IMPL_ISUPPORTS1(JetpackParent, nsIJetpack) -NS_IMETHODIMP -JetpackParent::LoadImplementation(const nsAString& aURI) +static nsresult +ReadFromURI(const nsAString& aURI, + nsCString* content) { - // this is all wrong, load the URI and send the data, but for now... - if (!SendLoadImplementation(NS_ConvertUTF16toUTF8(aURI))) + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), + NS_ConvertUTF16toUTF8(aURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + NS_NewChannel(getter_AddRefs(channel), uri); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInputStream> input; + rv = channel->Open(getter_AddRefs(input)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(input, "Channel opened successfully but stream was null?"); + + char buffer[256]; + PRUint32 avail = 0; + input->Available(&avail); + if (avail) { + PRUint32 read = 0; + while (NS_SUCCEEDED(input->Read(buffer, sizeof(buffer), &read)) && read) { + content->Append(buffer, read); + read = 0; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +JetpackParent::SendMessage(const nsAString& aMessageName) +{ + nsresult rv; + nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAXPCNativeCallContext* ncc = NULL; + rv = xpc->GetCurrentNativeCallContext(&ncc); + NS_ENSURE_SUCCESS(rv, rv); + + JSContext* cx; + rv = ncc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 argc; + rv = ncc->GetArgc(&argc); + NS_ENSURE_SUCCESS(rv, rv); + + jsval* argv; + rv = ncc->GetArgvPtr(&argv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<Variant> data; + NS_ENSURE_TRUE(data.SetCapacity(argc), NS_ERROR_OUT_OF_MEMORY); + + JSAutoRequest request(cx); + + for (PRUint32 i = 1; i < argc; ++i) + if (!jsval_to_Variant(cx, argv[i], data.AppendElement())) + return NS_ERROR_INVALID_ARG; + + if (!SendSendMessage(nsString(aMessageName), data)) return NS_ERROR_FAILURE; return NS_OK; } +NS_IMETHODIMP +JetpackParent::RegisterReceiver(const nsAString& aMessageName, + jsval aReceiver) +{ + return JetpackActorCommon::RegisterReceiver(mContext, + nsString(aMessageName), + aReceiver); +} + +NS_IMETHODIMP +JetpackParent::UnregisterReceiver(const nsAString& aMessageName, + jsval aReceiver) +{ + JetpackActorCommon::UnregisterReceiver(nsString(aMessageName), + aReceiver); + return NS_OK; +} + +NS_IMETHODIMP +JetpackParent::UnregisterReceivers(const nsAString& aMessageName) +{ + JetpackActorCommon::UnregisterReceivers(nsString(aMessageName)); + return NS_OK; +} + +NS_IMETHODIMP +JetpackParent::LoadImplementation(const nsAString& aURI) +{ + nsCString code; + nsresult rv = ReadFromURI(aURI, &code); + NS_ENSURE_SUCCESS(rv, rv); + + if (!code.IsEmpty() && + !SendLoadImplementation(code)) + rv = NS_ERROR_FAILURE; + + return rv; +} + +NS_IMETHODIMP +JetpackParent::LoadUserScript(const nsAString& aURI) +{ + nsCString code; + nsresult rv = ReadFromURI(aURI, &code); + NS_ENSURE_SUCCESS(rv, rv); + + if (!code.IsEmpty() && + !SendLoadUserScript(code)) + rv = NS_ERROR_FAILURE; + + return rv; +} + +bool +JetpackParent::RecvSendMessage(const nsString& messageName, + const nsTArray<Variant>& data) +{ + JSAutoRequest request(mContext); + return JetpackActorCommon::RecvMessage(mContext, messageName, data, NULL); +} + +bool +JetpackParent::RecvCallMessage(const nsString& messageName, + const nsTArray<Variant>& data, + nsTArray<Variant>* results) +{ + JSAutoRequest request(mContext); + return JetpackActorCommon::RecvMessage(mContext, messageName, data, results); +} + +NS_IMETHODIMP +JetpackParent::CreateHandle(nsIVariant** aResult) +{ + HandleParent* handle = + static_cast<HandleParent*>(SendPHandleConstructor()); + NS_ENSURE_TRUE(handle, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv; + nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + JSAutoRequest request(mContext); + + JSObject* hobj = handle->ToJSObject(mContext); + if (!hobj) + return NS_ERROR_FAILURE; + + return xpc->JSToVariant(mContext, OBJECT_TO_JSVAL(hobj), aResult); +} + +PHandleParent* +JetpackParent::AllocPHandle() +{ + return new HandleParent(); +} + +bool +JetpackParent::DeallocPHandle(PHandleParent* actor) +{ + delete actor; + return true; +} + } // namespace jetpack } // namespace mozilla
--- a/js/jetpack/JetpackParent.h +++ b/js/jetpack/JetpackParent.h @@ -15,16 +15,17 @@ * The Original Code is Mozilla Firefox. * * The Initial Developer of the Original Code is * the Mozilla Foundation <http://www.mozilla.org>. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Ben Newman <mozilla@benjamn.com> * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your @@ -35,34 +36,53 @@ * * ***** END LICENSE BLOCK ***** */ #ifndef mozilla_jetpack_JetpackParent_h #define mozilla_jetpack_JetpackParent_h #include "mozilla/jetpack/PJetpackParent.h" #include "mozilla/jetpack/JetpackProcessParent.h" +#include "mozilla/jetpack/JetpackActorCommon.h" #include "nsIJetpack.h" +#include "nsTArray.h" + +struct JSContext; + namespace mozilla { namespace jetpack { +class PHandleParent; + class JetpackParent : public PJetpackParent , public nsIJetpack + , private JetpackActorCommon { public: NS_DECL_ISUPPORTS NS_DECL_NSIJETPACK - JetpackParent(); + JetpackParent(JSContext* cx); ~JetpackParent(); +protected: + NS_OVERRIDE virtual bool RecvSendMessage(const nsString& messageName, + const nsTArray<Variant>& data); + NS_OVERRIDE virtual bool RecvCallMessage(const nsString& messageName, + const nsTArray<Variant>& data, + nsTArray<Variant>* results); + + NS_OVERRIDE virtual PHandleParent* AllocPHandle(); + NS_OVERRIDE virtual bool DeallocPHandle(PHandleParent* actor); + private: JetpackProcessParent* mSubprocess; + JSContext* mContext; DISALLOW_EVIL_CONSTRUCTORS(JetpackParent); }; } // namespace jetpack } // namespace mozilla #endif // mozilla_jetpack_JetpackParent_h
--- a/js/jetpack/JetpackService.cpp +++ b/js/jetpack/JetpackService.cpp @@ -38,27 +38,42 @@ #include "base/basictypes.h" #include "mozilla/jetpack/JetpackService.h" #include "mozilla/jetpack/JetpackParent.h" #include "nsIJetpack.h" #include "nsIGenericFactory.h" +#include "nsIXPConnect.h" + namespace mozilla { namespace jetpack { NS_IMPL_ISUPPORTS1(JetpackService, nsIJetpackService) NS_IMETHODIMP JetpackService::CreateJetpack(nsIJetpack** aResult) { - nsRefPtr<JetpackParent> j = new JetpackParent(); + nsresult rv; + nsCOMPtr<nsIXPConnect> xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAXPCNativeCallContext* ncc = NULL; + rv = xpc->GetCurrentNativeCallContext(&ncc); + NS_ENSURE_SUCCESS(rv, rv); + + JSContext* cx; + rv = ncc->GetJSContext(&cx); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr<JetpackParent> j = new JetpackParent(cx); *aResult = j.forget().get(); + return NS_OK; } NS_GENERIC_FACTORY_CONSTRUCTOR(JetpackService) } // namespace jetpack } // namespace mozilla
--- a/js/jetpack/Makefile.in +++ b/js/jetpack/Makefile.in @@ -56,21 +56,28 @@ XPIDLSRCS = \ EXPORTS_NAMESPACES = mozilla/jetpack EXPORTS_mozilla/jetpack = \ JetpackProcessChild.h \ JetpackProcessParent.h \ JetpackParent.h \ JetpackChild.h \ JetpackService.h \ + JetpackActorCommon.h \ + Handle.h \ $(NULL) CPPSRCS = \ JetpackParent.cpp \ JetpackChild.cpp \ JetpackProcessChild.cpp \ JetpackProcessParent.cpp \ JetpackService.cpp \ + JetpackActorCommon.cpp \ $(NULL) +ifdef ENABLE_TESTS +TOOL_DIRS += tests +endif + include $(topsrcdir)/config/config.mk include $(topsrcdir)/ipc/chromium/chromium-config.mk include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/js/jetpack/PHandle.ipdl @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=4 et tw=80: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Ben Newman <mozilla@benjamn.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +include protocol PJetpack; + +namespace mozilla { +namespace jetpack { + +async protocol PHandle +{ + manager PJetpack or PHandle; + manages PHandle; +both: + PHandle(); + __delete__(); +}; + +} // namespace jetpack +} // namespace mozilla
--- a/js/jetpack/PJetpack.ipdl +++ b/js/jetpack/PJetpack.ipdl @@ -30,19 +30,63 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +include protocol PHandle; + +using mozilla::void_t; +using mozilla::null_t; + namespace mozilla { namespace jetpack { -async protocol PJetpack -{ -child: - async LoadImplementation(nsCString script); +struct KeyValue { + nsString key; + Variant value; +}; + +union PrimVariant { + void_t; + null_t; + bool; + int; + double; + nsString; + PHandle; }; -} -} +union CompVariant { + KeyValue[]; + Variant[]; + size_t; // reference +}; + +union Variant { + PrimVariant; + CompVariant; +}; + +sync protocol PJetpack +{ + manages PHandle; +both: + async SendMessage(nsString messageName, + Variant[] data); + async PHandle(); + +child: + async LoadImplementation(nsCString code); + async LoadUserScript(nsCString code); + +parent: + sync CallMessage(nsString messageName, + Variant[] data) + returns (Variant[] results); + +}; + +} // namespace jetpack +} // namespace mozilla
--- a/js/jetpack/ipdl.mk +++ b/js/jetpack/ipdl.mk @@ -1,3 +1,4 @@ IPDLSRCS = \ PJetpack.ipdl \ + PHandle.ipdl \ $(NULL)
--- a/js/jetpack/nsIJetpack.idl +++ b/js/jetpack/nsIJetpack.idl @@ -32,13 +32,29 @@ * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" -[scriptable, uuid(be12cc9f-75d4-4f2d-b062-f549ea13446a)] +interface nsIVariant; + +[scriptable, uuid(563afdf8-5d2f-4bb2-9ac0-fb82461d2922)] interface nsIJetpack : nsISupports { + void sendMessage(in AString aMessageName + /* [optional] in jsval v1, + [optional] in jsval v2, + ... */); + + void registerReceiver(in AString aMessageName, + in jsval aReceiver); + void unregisterReceiver(in AString aMessageName, + in jsval aReceiver); + void unregisterReceivers(in AString aMessageName); + void loadImplementation(in AString aURI); + void loadUserScript(in AString aURI); + + nsIVariant createHandle(); };
new file mode 100644 --- /dev/null +++ b/js/jetpack/tests/Makefile.in @@ -0,0 +1,50 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is +# The Mozilla Foundation. +# Portions created by the Initial Developer are Copyright (C) 2010 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Ben Newman <mozilla@benjamn.com> (original author) +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = test_jetpack + +XPCSHELL_TESTS = unit + +include $(topsrcdir)/config/config.mk +include $(topsrcdir)/config/rules.mk
new file mode 100644 --- /dev/null +++ b/js/jetpack/tests/unit/handle_tests.js @@ -0,0 +1,99 @@ +function run_handle_tests() { + test_sanity(); + test_safe_iteration(); + test_local_invalidation(); + test_long_parent_chain(100); + test_invalid_creation(); +} + +function test_sanity() { + var parent = createHandle(), + child = parent.createHandle(), + grandchild = child.createHandle(); + + do_check_neq(child, parent); + do_check_eq(child.parent, parent); + do_check_eq(parent.parent, null); + do_check_eq(grandchild.parent.parent, parent); + + do_check_true(child.isValid); + do_check_true(parent.isValid); + + parent.invalidate(); +} + +function test_safe_iteration() { + var handle = createHandle(), + keys = []; + handle.foo = 42; + handle.self = handle; + for (var k in handle) + keys[keys.length] = k; + do_check_eq(keys.sort().join("~"), + "foo~self"); + handle.invalidate(); +} + +function test_local_invalidation() { + var parent = createHandle(), + child = parent.createHandle(); + + do_check_true(child.invalidate()); + do_check_false(child.isValid); + do_check_true(parent.isValid); + + child = parent.createHandle(); + do_check_true(child.isValid); + + do_check_true(parent.invalidate()); + do_check_false(parent.invalidate()); + do_check_false(child.isValid); + do_check_false(parent.isValid); + + parent = createHandle(); + child = parent.createHandle(); + child = child.createHandle(); + + var uncle = parent.createHandle(), + sibling = child.parent.createHandle(); + + do_check_eq(child.parent.parent, parent); + do_check_true(child.parent.isValid); + + do_check_true(child.parent.invalidate()); + do_check_false(child.isValid); + do_check_true(parent.isValid); + + do_check_false(sibling.isValid); + do_check_true(uncle.isValid); + + parent.invalidate(); +} + +function test_long_parent_chain(len) { + const ancestor = createHandle(); + for (var handle = ancestor, i = 0; i < len; ++i) + handle = handle.createHandle(); + const child = handle; + + while (handle != ancestor) + handle = handle.parent; + + do_check_true(child.isValid); + do_check_true(ancestor.invalidate()); + do_check_false(child.isValid); +} + +function test_invalid_creation() { + var parent = createHandle(), + child = parent.createHandle(); + + parent.invalidate(); + + do_check_eq(child.parent, null); + + var threw = false; + try { child.createHandle(); } + catch (x) { threw = true; } + do_check_true(threw); +}
new file mode 100644 --- /dev/null +++ b/js/jetpack/tests/unit/impl.js @@ -0,0 +1,43 @@ +function echo() { + sendMessage.apply(this, arguments); +} + +registerReceiver("echo", echo); + +registerReceiver("callback", + function(msgName, data, handle) { + sendMessage("sendback", + callMessage("callback", data)[0], + handle); + }); + +registerReceiver("gimmeHandle", + function(msgName) { + sendMessage("recvHandle", "ok", createHandle()); + }); + +registerReceiver("kthx", + function(msgName, data, child) { + sendMessage("recvHandleAgain", data + data, child.parent); + }); + +registerReceiver("echo2", echo); + +registerReceiver("multireturn begin", + function() { + var results = callMessage("multireturn"); + sendMessage.apply(null, ["multireturn check"].concat(results)); + }); + +registerReceiver("testarray", + function(msgName, array) { + sendMessage("testarray", array.reverse()); + }); + +registerReceiver("test primitive types", echo); + +registerReceiver("drop methods", echo); + +registerReceiver("exception coping", echo); + +registerReceiver("duplicate receivers", echo);
new file mode 100644 --- /dev/null +++ b/js/jetpack/tests/unit/test_jetpack.js @@ -0,0 +1,186 @@ +var jps = jps || Components.classes["@mozilla.org/jetpack/service;1"] + .getService(Components.interfaces.nsIJetpackService), + jetpack = jps.createJetpack(); + +load("handle_tests.js"); +function createHandle() { + return jetpack.createHandle(); +} + +function run_test() { + run_handle_tests(); + + jetpack.loadImplementation("file://" + do_get_file("impl.js").path); + + var circ1 = {}, + circ2 = {}, + circ3 = {}, + ok = false; + ((circ1.next = circ2).next = circ3).next = circ1; + try { + jetpack.sendMessage("ignored", circ3, circ1); + ok = true; + } catch (x) { + do_check_false(x); + } + do_check_true(ok); + + var echoHandle = jetpack.createHandle(); + echoHandle.payload = { weight: 10 }; + jetpack.registerReceiver("echo", + function(msgName, data, handle) { + do_check_eq(arguments.length, 3); + do_check_eq(msgName, "echo"); + do_check_eq(data, "echo this"); + do_check_true(handle.isValid); + do_check_eq(handle, echoHandle); + do_check_eq(handle.payload.weight, 10); + do_test_finished(); + }); + + jetpack.registerReceiver("callback", + function(msgName, data) { + do_check_eq(msgName, "callback"); + return "called back: " + data; + }); + + var callbackHandle = echoHandle.createHandle(); + jetpack.registerReceiver("sendback", + function(msgName, data, handle) { + do_check_eq(msgName, "sendback"); + do_check_eq(data, "called back: call me back"); + do_check_eq(handle, callbackHandle); + do_test_finished(); + }); + + var obj; + jetpack.registerReceiver("recvHandle", + function(msgName, data, handle) { + handle.mark = obj = {}; + jetpack.sendMessage("kthx", data + data, handle.createHandle()); + }); + jetpack.registerReceiver("recvHandleAgain", + function(msgName, data, handle) { + do_check_eq(data, "okokokok"); + do_check_eq(handle.mark, obj); + do_test_finished(); + }); + var obj1 = { + id: Math.random() + "" + }, obj2 = { + id: Math.random() + "", + obj: obj1 + }; + jetpack.registerReceiver("echo2", + function(msgName, a, b) { + do_check_neq(obj1, a); + do_check_neq(obj2, b); + do_check_eq(obj1.id, a.id); + do_check_eq(obj2.id, b.id); + do_check_eq(obj1.id, obj2.obj.id); + do_test_finished(); + }); + + jetpack.registerReceiver("multireturn", function() { return obj1 }); + jetpack.registerReceiver("multireturn", function() { return circ1 }); + jetpack.registerReceiver("multireturn", function() { return obj2 }); + jetpack.registerReceiver("multireturn check", + function(msgName, rval1, rval2, rval3) { + do_check_eq(rval1.id, obj1.id); + do_check_eq(rval2.next.next.next, rval2); + do_check_eq(rval3.id, obj2.id); + do_check_eq(rval3.obj.id, obj1.id); + do_test_finished(); + }); + + var testarray = [1, 1, 2, 3, 5, 8, 13]; + jetpack.registerReceiver("testarray", + function(msgName, reversed) { + for (var i = 0; i < testarray.length; ++i) + do_check_eq(testarray[i], + reversed[reversed.length - i - 1]); + do_test_finished(); + }); + + var undefined; + jetpack.registerReceiver("test primitive types", + function(msgName, + void_val, null_val, + bool_true, bool_false, + one, two, nine99, + one_quarter, + oyez_str) + { + do_check_true(void_val === undefined); + do_check_true(null_val === null); + do_check_true(bool_true === true); + do_check_true(bool_false === false); + do_check_eq(one, 1); + do_check_eq(two, 2); + do_check_eq(nine99, 999); + do_check_eq(one_quarter, 0.25); + do_check_eq(oyez_str, "oyez"); + + do_test_finished(); + }); + + var drop = { + nested: { + method: function() { return this.value }, + value: 42 + } + }; + jetpack.registerReceiver("drop methods", + function(msgName, echoed) { + do_check_true(!echoed.nested.method); + do_check_eq(echoed.nested.value, 42); + do_test_finished(); + }); + + var coped = "did not cope"; + jetpack.registerReceiver("exception coping", + function(msgName) { throw coped = "did cope" }); + jetpack.registerReceiver("exception coping", + function(msgName) { + do_check_eq(coped, "did cope"); + do_test_finished(); + }); + + var calls = ""; + function countCalls() { calls += "." } + jetpack.registerReceiver("duplicate receivers", countCalls); + jetpack.registerReceiver("duplicate receivers", countCalls); + jetpack.registerReceiver("duplicate receivers", + function() { do_check_eq(calls, ".") }); + jetpack.registerReceiver("duplicate receivers", countCalls); + jetpack.registerReceiver("duplicate receivers", + function() { + do_check_eq(calls, "."); + jetpack.unregisterReceivers("duplicate receivers"); + }); + jetpack.registerReceiver("duplicate receivers", + function() { do_test_finished() }); + + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + do_test_pending(); + + jetpack.sendMessage("echo", "echo this", echoHandle); + jetpack.sendMessage("callback", "call me back", callbackHandle); + jetpack.sendMessage("gimmeHandle"); + jetpack.sendMessage("echo2", obj1, obj2); + jetpack.sendMessage("multireturn begin"); + jetpack.sendMessage("testarray", testarray); + jetpack.sendMessage("test primitive types", + undefined, null, true, false, 1, 2, 999, 1/4, "oyez"); + jetpack.sendMessage("drop methods", drop); + jetpack.sendMessage("exception coping"); + jetpack.sendMessage("duplicate receivers"); +}