gecko/js/ipc/ObjectWrapperChild.cpp
2010-07-20 15:07:03 -07:00

652 lines
20 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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 <b{enjam,newma}n@mozilla.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 "base/basictypes.h"
#include "jscntxt.h"
#include "mozilla/jsipc/ContextWrapperChild.h"
#include "mozilla/jsipc/ObjectWrapperChild.h"
#include "mozilla/jsipc/CPOWTypes.h"
#include "jsapi.h"
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsContentUtils.h"
#include "nsIJSContextStack.h"
using namespace mozilla::jsipc;
using namespace js;
namespace {
class AutoContextPusher {
nsCxPusher mStack;
JSAutoRequest mRequest;
JSContext* const mContext;
const uint32 mSavedOptions;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER;
public:
AutoContextPusher(JSContext* cx
JS_GUARD_OBJECT_NOTIFIER_PARAM)
: mRequest(cx)
, mContext(cx)
, mSavedOptions(JS_SetOptions(cx, (JS_GetOptions(cx) |
JSOPTION_DONT_REPORT_UNCAUGHT)))
{
JS_GUARD_OBJECT_NOTIFIER_INIT;
mStack.Push(cx, PR_FALSE);
}
~AutoContextPusher() {
mStack.Pop();
JS_SetOptions(mContext, mSavedOptions);
}
};
class StatusPtrOwner
{
OperationStatus* mStatusPtr;
public:
StatusPtrOwner() : mStatusPtr(NULL) {}
void SetStatusPtr(OperationStatus* statusPtr) {
mStatusPtr = statusPtr;
// By default, initialize mStatusPtr to failure without an
// exception. Doing so only when the union is uninitialized
// allows AutoCheckOperation classes to be nested on the
// stack, just in case AnswerConstruct, for example, calls
// AnswerCall (as it once did, before there were unrelated
// problems with that approach).
if (mStatusPtr->type() == OperationStatus::T__None)
*mStatusPtr = JS_FALSE;
}
OperationStatus* StatusPtr() {
NS_ASSERTION(mStatusPtr, "Should have called SetStatusPtr by now.");
return mStatusPtr;
}
};
typedef AutoCheckOperationBase<StatusPtrOwner> ACOBase;
class AutoCheckOperation : public ACOBase
{
JS_DECL_USE_GUARD_OBJECT_NOTIFIER;
public:
AutoCheckOperation(ObjectWrapperChild* owc,
OperationStatus* statusPtr
JS_GUARD_OBJECT_NOTIFIER_PARAM)
: ACOBase(NULL, owc)
{
JS_GUARD_OBJECT_NOTIFIER_INIT;
SetStatusPtr(statusPtr);
}
};
}
void
ObjectWrapperChild::CheckOperation(JSContext*,
OperationStatus* status)
{
NS_PRECONDITION(status->type() != OperationStatus::T__None,
"Checking an uninitialized operation.");
JSContext* cx = Manager()->GetContext();
jsval thrown;
if (JS_GetPendingException(cx, &thrown)) {
NS_ASSERTION(!(status->type() == OperationStatus::TJSBool &&
status->get_JSBool()),
"Operation succeeded but exception was thrown?");
JSVariant exception;
if (!jsval_to_JSVariant(cx, thrown, &exception))
exception = void_t(); // XXX Useful?
*status = exception;
JS_ClearPendingException(cx);
}
}
ObjectWrapperChild::ObjectWrapperChild(JSContext* cx, JSObject* obj)
: mObj(obj)
{
JSAutoRequest request(cx);
#ifdef DEBUG
bool added =
#endif
JS_AddObjectRoot(cx, &mObj);
NS_ASSERTION(added, "ObjectWrapperChild constructor failed to root JSObject*");
}
void
ObjectWrapperChild::ActorDestroy(ActorDestroyReason why)
{
JSContext* cx = Manager()->GetContext();
JSAutoRequest request(cx);
JS_RemoveObjectRoot(cx, &mObj);
}
bool
ObjectWrapperChild::JSObject_to_JSVariant(JSContext* cx, JSObject* from,
JSVariant* to)
{
*to = Manager()->GetOrCreateWrapper(from);
return true;
}
bool
ObjectWrapperChild::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:
// fall through
case JSTYPE_OBJECT:
return JSObject_to_JSVariant(cx, JSVAL_TO_OBJECT(from), to);
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:
// fall through
default:
return false;
}
}
/*static*/ bool
ObjectWrapperChild::
JSObject_from_PObjectWrapperChild(JSContext*,
const PObjectWrapperChild* from,
JSObject** to)
{
const ObjectWrapperChild* owc =
static_cast<const ObjectWrapperChild*>(from);
*to = owc ? owc->mObj : NULL;
return true;
}
/*static*/ bool
ObjectWrapperChild::JSObject_from_JSVariant(JSContext* cx,
const JSVariant& from,
JSObject** to)
{
if (from.type() != JSVariant::TPObjectWrapperChild)
return false;
return JSObject_from_PObjectWrapperChild(cx,
from.get_PObjectWrapperChild(),
to);
}
/*static*/ bool
ObjectWrapperChild::jsval_from_JSVariant(JSContext* cx, const JSVariant& from,
jsval* to)
{
switch (from.type()) {
case JSVariant::Tvoid_t:
*to = JSVAL_VOID;
return true;
case JSVariant::TPObjectWrapperChild:
{
JSObject* obj;
if (!JSObject_from_JSVariant(cx, from, &obj))
return false;
*to = OBJECT_TO_JSVAL(obj);
return true;
}
case JSVariant::TnsString:
{
const nsString& str = from.get_nsString();
JSString* s = JS_NewUCStringCopyN(cx,
str.BeginReading(),
str.Length());
if (!s)
return false;
*to = STRING_TO_JSVAL(s);
}
return true;
case JSVariant::Tint:
*to = INT_TO_JSVAL(from.get_int());
return true;
case JSVariant::Tdouble:
return !!JS_NewNumberValue(cx, from.get_double(), to);
case JSVariant::Tbool:
*to = BOOLEAN_TO_JSVAL(from.get_bool());
return true;
default:
return false;
}
}
ContextWrapperChild*
ObjectWrapperChild::Manager()
{
PContextWrapperChild* pcwc = PObjectWrapperChild::Manager();
return static_cast<ContextWrapperChild*>(pcwc);
}
static bool
jsid_to_nsString(JSContext* cx, jsid from, nsString* to)
{
jsval v;
if (JS_IdToValue(cx, from, &v) && JSVAL_IS_STRING(v)) {
*to = nsDependentString((PRUnichar*)JS_GetStringChars(JSVAL_TO_STRING(v)),
JS_GetStringLength(JSVAL_TO_STRING(v)));
return true;
}
return false;
}
static bool
jsid_from_nsString(JSContext* cx, const nsString& from, jsid* to)
{
JSString* str = JS_NewUCStringCopyN(cx, from.BeginReading(), from.Length());
if (!str)
return false;
return JS_ValueToId(cx, STRING_TO_JSVAL(str), to);
}
#if 0
// The general schema for ObjectWrapperChild::Answer* methods:
bool
ObjectWrapperChild::AnswerSomething(/* in-parameters */
/* out-parameters */)
{
// initialize out-parameters for failure
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
// validate in-parameters, else return false
// successfully perform local JS operations, else return true
// perform out-parameter conversions, else return false
return true;
}
// There's an important subtlety here: though a local JS operation may
// fail, leaving out-parameters uninitialized, we must initialize all
// out-parameters when reporting success (returning true) to the IPC
// messaging system. See AnswerGetProperty for illustration.
#endif
bool
ObjectWrapperChild::AnswerAddProperty(const nsString& id,
OperationStatus* status)
{
jsid interned_id;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsid_from_nsString(cx, id, &interned_id))
return false;
*status = JS_DefinePropertyById(cx, mObj, interned_id, JSVAL_VOID,
NULL, NULL, 0);
return true;
}
bool
ObjectWrapperChild::AnswerGetProperty(const nsString& id,
OperationStatus* status, JSVariant* vp)
{
jsid interned_id;
jsval val;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsid_from_nsString(cx, id, &interned_id))
return false;
*status = JS_GetPropertyById(cx, mObj, interned_id, &val);
// Since we fully expect this call to jsval_to_JSVariant to return
// true, we can't just leave vp uninitialized when JS_GetPropertyById
// returns JS_FALSE. This pitfall could be avoided in general if IPDL
// ensured that outparams were pre-initialized to some default value
// (XXXfixme cjones?).
return jsval_to_JSVariant(cx, aco.Ok() ? val : JSVAL_VOID, vp);
}
bool
ObjectWrapperChild::AnswerSetProperty(const nsString& id, const JSVariant& v,
OperationStatus* status, JSVariant* vp)
{
jsid interned_id;
jsval val;
*vp = v;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsid_from_nsString(cx, id, &interned_id) ||
!jsval_from_JSVariant(cx, v, &val))
return false;
*status = JS_SetPropertyById(cx, mObj, interned_id, &val);
return jsval_to_JSVariant(cx, aco.Ok() ? val : JSVAL_VOID, vp);
}
bool
ObjectWrapperChild::AnswerDelProperty(const nsString& id,
OperationStatus* status, JSVariant* vp)
{
jsid interned_id;
jsval val;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsid_from_nsString(cx, id, &interned_id))
return false;
*status = JS_DeletePropertyById2(cx, mObj, interned_id, &val);
return jsval_to_JSVariant(cx, aco.Ok() ? val : JSVAL_VOID, vp);
}
static const PRUint32 sNextIdIndexSlot = 0;
static const PRUint32 sNumNewEnumerateStateSlots = 1;
static void
CPOW_NewEnumerateState_Finalize(JSContext* cx, JSObject* state)
{
nsTArray<nsString>* strIds =
static_cast<nsTArray<nsString>*>(JS_GetPrivate(cx, state));
if (strIds) {
delete strIds;
JS_SetPrivate(cx, state, NULL);
}
}
// Similar to IteratorClass in XPCWrapper.cpp
static const JSClass sCPOW_NewEnumerateState_JSClass = {
"CPOW NewEnumerate State",
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(sNumNewEnumerateStateSlots),
JS_PropertyStub, JS_PropertyStub,
JS_PropertyStub, JS_PropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, CPOW_NewEnumerateState_Finalize,
JSCLASS_NO_OPTIONAL_MEMBERS
};
bool
ObjectWrapperChild::AnswerNewEnumerateInit(/* no in-parameters */
OperationStatus* status, JSVariant* statep, int* idp)
{
*idp = 0;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
JSClass* clasp = const_cast<JSClass*>(&sCPOW_NewEnumerateState_JSClass);
JSObject* state = JS_NewObjectWithGivenProto(cx, clasp, NULL, NULL);
if (!state)
return false;
AutoObjectRooter tvr(cx, state);
for (JSObject* proto = mObj;
proto;
proto = JS_GetPrototype(cx, proto))
{
AutoIdArray ids(cx, JS_Enumerate(cx, proto));
for (uint i = 0; i < ids.length(); ++i)
JS_DefinePropertyById(cx, state, ids[i], JSVAL_VOID,
NULL, NULL, JSPROP_ENUMERATE | JSPROP_SHARED);
}
nsTArray<nsString>* strIds;
{
AutoIdArray ids(cx, JS_Enumerate(cx, state));
if (!ids)
return false;
strIds = new nsTArray<nsString>(ids.length());
for (uint i = 0; i < ids.length(); ++i)
if (!jsid_to_nsString(cx, ids[i], strIds->AppendElement())) {
delete strIds;
return false;
}
}
*idp = strIds->Length();
*status = (JS_SetPrivate(cx, state, strIds) &&
JS_SetReservedSlot(cx, state, sNextIdIndexSlot,
JSVAL_ZERO) &&
JSObject_to_JSVariant(cx, state, statep));
return true;
}
bool
ObjectWrapperChild::AnswerNewEnumerateNext(const JSVariant& in_state,
OperationStatus* status, JSVariant* statep, nsString* idp)
{
JSObject* state;
jsval v;
*statep = in_state;
idp->Truncate();
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!JSObject_from_JSVariant(cx, in_state, &state))
return false;
nsTArray<nsString>* strIds =
static_cast<nsTArray<nsString>*>(JS_GetPrivate(cx, state));
if (!strIds || !JS_GetReservedSlot(cx, state, sNextIdIndexSlot, &v))
return false;
jsuint i = JSVAL_TO_INT(v);
NS_ASSERTION(i >= 0, "Index of next jsid negative?");
NS_ASSERTION(i <= strIds->Length(), "Index of next jsid too large?");
if (jsuint(i) == strIds->Length()) {
*status = JS_TRUE;
return JSObject_to_JSVariant(cx, NULL, statep);
}
*idp = strIds->ElementAt(i);
*status = JS_SetReservedSlot(cx, state, sNextIdIndexSlot,
INT_TO_JSVAL(i + 1));
return true;
}
bool
ObjectWrapperChild::RecvNewEnumerateDestroy(const JSVariant& in_state)
{
JSObject* state;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
if (!JSObject_from_JSVariant(cx, in_state, &state))
return false;
CPOW_NewEnumerateState_Finalize(cx, state);
return true;
}
bool
ObjectWrapperChild::AnswerNewResolve(const nsString& id, const int& flags,
OperationStatus* status, PObjectWrapperChild** obj2)
{
jsid interned_id;
*obj2 = NULL;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsid_from_nsString(cx, id, &interned_id))
return false;
CPOW_LOG(("new-resolving \"%s\"...",
NS_ConvertUTF16toUTF8(id).get()));
JSPropertyDescriptor desc;
if (!JS_GetPropertyDescriptorById(cx, mObj, interned_id, flags, &desc))
return true;
*status = JS_TRUE;
if (desc.obj)
*obj2 = Manager()->GetOrCreateWrapper(desc.obj);
return true;
}
bool
ObjectWrapperChild::AnswerConvert(const JSType& type,
OperationStatus* status, JSVariant* vp)
{
jsval v;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
*status = JS_ConvertValue(cx, OBJECT_TO_JSVAL(mObj), type, &v);
return jsval_to_JSVariant(cx, aco.Ok() ? v : JSVAL_VOID, vp);
}
namespace {
// Should be an overestimate of typical JS function arity.
typedef nsAutoTArray<jsval, 5> AutoJSArgs;
}
bool
ObjectWrapperChild::AnswerCall(PObjectWrapperChild* receiver, const nsTArray<JSVariant>& argv,
OperationStatus* status, JSVariant* rval)
{
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
JSObject* obj;
if (!JSObject_from_PObjectWrapperChild(cx, receiver, &obj))
return false;
AutoJSArgs args;
PRUint32 argc = argv.Length();
jsval *jsargs = args.AppendElements(argc);
if (!jsargs)
return false;
AutoArrayRooter tvr(cx, argc, jsargs);
for (PRUint32 i = 0; i < argc; ++i)
if (!jsval_from_JSVariant(cx, argv.ElementAt(i), jsargs + i))
return false;
jsval rv;
*status = JS_CallFunctionValue(cx, obj, OBJECT_TO_JSVAL(mObj),
argv.Length(), jsargs, &rv);
return jsval_to_JSVariant(cx, aco.Ok() ? rv : JSVAL_VOID, rval);
}
bool
ObjectWrapperChild::AnswerConstruct(const nsTArray<JSVariant>& argv,
OperationStatus* status, PObjectWrapperChild** rval)
{
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
AutoJSArgs args;
PRUint32 argc = argv.Length();
jsval* jsargs = args.AppendElements(argc);
if (!jsargs)
return false;
AutoArrayRooter tvr(cx, argc, jsargs);
for (PRUint32 i = 0; i < argc; ++i)
if (!jsval_from_JSVariant(cx, argv.ElementAt(i), jsargs + i))
return false;
JSObject* obj = JS_New(cx, mObj, argc, jsargs);
*status = !!obj;
*rval = Manager()->GetOrCreateWrapper(obj);
return true;
}
bool
ObjectWrapperChild::AnswerHasInstance(const JSVariant& v,
OperationStatus* status, JSBool* bp)
{
jsval candidate;
JSContext* cx = Manager()->GetContext();
AutoContextPusher acp(cx);
AutoCheckOperation aco(this, status);
if (!jsval_from_JSVariant(cx, v, &candidate))
return false;
*status = JS_HasInstance(cx, mObj, candidate, bp);
return true;
}