/* -*- 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 (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 "jsclass.h" #include "jscntxt.h" #include "jsfriendapi.h" #include "mozilla/unused.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 Handle : public BaseType { Handle(Handle* parent) : mParent(parent) , mObj(NULL) , mCx(NULL) , mRooted(false) {} BaseType* AllocPHandle() { return new Handle(this); } bool DeallocPHandle(BaseType* actor) { delete actor; return true; } public: Handle() : mParent(NULL) , mObj(NULL) , mCx(NULL) , mRooted(false) {} ~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)); } void Root() { NS_ASSERTION(mObj && mCx, "Rooting with no object unexpected."); if (mRooted) return; if (!JS_AddNamedObjectRoot(mCx, &mObj, "Jetpack Handle")) { NS_RUNTIMEABORT("Failed to add root."); } mRooted = true; } void Unroot() { NS_ASSERTION(mCx, "Unrooting with no JSContext unexpected."); if (!mRooted) return; JS_RemoveObjectRoot(mCx, &mObj); mRooted = false; } JSObject* ToJSObject(JSContext* cx) { if (!mObj && !mCx) { JSClass* clasp = const_cast(&sHandle_JSClass); JSObject* obj = JS_NewObject(cx, clasp, NULL, NULL); if (!obj) return NULL; js::AutoObjectRooter root(cx, obj); JSPropertySpec* ps = const_cast(sHandle_Properties); JSFunctionSpec* fs = const_cast(sHandle_Functions); if (JS_SetPrivate(cx, obj, (void*)this) && JS_DefineProperties(cx, obj, ps) && JS_DefineFunctions(cx, obj, fs)) { mObj = obj; mCx = cx; Root(); } } 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 (mCx) { JSAutoRequest ar(mCx); if (mObj) { JS_SetPrivate(mCx, mObj, NULL); js::AutoObjectRooter obj(mCx, mObj); mObj = NULL; // If we can't enter the compartment, we won't run onInvalidate(). JSAutoEnterCompartment ac; if (ac.enter(mCx, obj.object())) { JSBool hasOnInvalidate; if (JS_HasProperty(mCx, obj.object(), "onInvalidate", &hasOnInvalidate) && hasOnInvalidate) { js::AutoValueRooter r(mCx); JSBool ok = JS_CallFunctionName(mCx, obj.object(), "onInvalidate", 0, NULL, r.jsval_addr()); if (!ok) JS_ReportPendingException(mCx); } } // By not nulling out mContext, we prevent ToJSObject from // reviving an invalidated/destroyed handle. } // Nulling out mObj effectively unroots the object, but we still // need to remove the root, else the JS engine will complain at // shutdown. Unroot(); } } 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. JSObject* mObj; JSContext* mCx; bool mRooted; static Handle* Unwrap(JSContext* cx, JSObject* obj) { while (obj && Jsvalify(js::GetObjectClass(obj)) != &sHandle_JSClass) obj = js::GetObjectProto(obj); if (!obj) return NULL; Handle* self = static_cast(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, jsid, 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, jsid, jsval* vp) { Handle* self = Unwrap(cx, obj); JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(!!self)); return JS_TRUE; } static JSBool GetIsRooted(JSContext* cx, JSObject* obj, jsid, jsval* vp) { Handle* self = Unwrap(cx, obj); bool rooted = self ? self->mRooted : false; JS_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(rooted)); return JS_TRUE; } static JSBool SetIsRooted(JSContext* cx, JSObject* obj, jsid, JSBool strict, jsval* vp) { Handle* self = Unwrap(cx, obj); JSBool v; if (!JS_ValueToBoolean(cx, *vp, &v)) return JS_FALSE; if (!self) { if (v) { JS_ReportError(cx, "Cannot root invalidated handle."); return JS_FALSE; } return JS_TRUE; } if (v) self->Root(); else self->Unroot(); *vp = BOOLEAN_TO_JSVAL(v); 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) unused << BaseType::Send__delete__(self); JS_SET_RVAL(cx, vp, JSVAL_VOID); 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(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); if (self) { NS_ASSERTION(!self->mRooted, "Finalizing a rooted object?"); self->mCx = NULL; self->mObj = NULL; unused << BaseType::Send__delete__(self); } } }; template const JSClass Handle::sHandle_JSClass = { "IPDL Handle", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, Handle::Finalize, JSCLASS_NO_OPTIONAL_MEMBERS }; #define HANDLE_PROP_FLAGS (JSPROP_PERMANENT | JSPROP_SHARED) template const JSPropertySpec Handle::sHandle_Properties[] = { { "parent", 0, HANDLE_PROP_FLAGS | JSPROP_READONLY, GetParent, NULL }, { "isValid", 0, HANDLE_PROP_FLAGS | JSPROP_READONLY, GetIsValid, NULL }, { "isRooted", 0, HANDLE_PROP_FLAGS, GetIsRooted, SetIsRooted }, { 0, 0, 0, NULL, NULL } }; #undef HANDLE_PROP_FLAGS #define HANDLE_FUN_FLAGS (JSPROP_READONLY | \ JSPROP_PERMANENT) template const JSFunctionSpec Handle::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 HandleParent; typedef Handle HandleChild; } // namespace jetpack } // namespace mozilla #endif