Initial checkin of OOPHM plugins into trunk. Testing of non-XPCOM plugins
is still required, and more platforms need to be built.
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5868 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/plugins/xpcom/JavaObject.cpp b/plugins/xpcom/JavaObject.cpp
new file mode 100644
index 0000000..fd2af9f
--- /dev/null
+++ b/plugins/xpcom/JavaObject.cpp
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#include "JavaObject.h"
+#include "FFSessionHandler.h"
+#include "SessionData.h"
+#include "ServerMethods.h"
+#include "Debug.h"
+#include "XpcomDebug.h"
+#include "HostChannel.h"
+#include "InvokeMessage.h"
+#include "ReturnMessage.h"
+#include "scoped_ptr/scoped_ptr.h"
+
+static JSClass JavaObjectClass = {
+ "GWTJavaObject", /* class name */
+ JSCLASS_HAS_PRIVATE | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_NEW_ENUMERATE, /* flags */
+
+ JS_PropertyStub, /* add property */
+ JS_PropertyStub, /* delete property */
+ JavaObject::getProperty, /* get property */
+ JavaObject::setProperty, /* set property */
+
+ reinterpret_cast<JSEnumerateOp>(JavaObject::enumerate), /* enumerate */
+ JS_ResolveStub, /* resolve */
+ JS_ConvertStub, // JavaObject::convert, /* convert */
+ JavaObject::finalize, /* finalize */ //TODO
+
+ NULL, /* object hooks */
+ NULL, /* check access */
+ JavaObject::call, /* call */ //TODO
+ NULL, /* construct */
+ NULL, /* object serialization */
+ NULL, /* has instance */
+ NULL, /* mark */
+ NULL /* reserve slots */
+};
+
+int JavaObject::getObjectId(JSContext* ctx, JSObject* obj) {
+ jsval val;
+ JSClass* jsClass = JS_GET_CLASS(ctx, obj);
+#if 1
+ if (jsClass != &JavaObjectClass) {
+ Debug::log(Debug::Error)
+ << "JavaObject::getObjectId called on non-JavaObject: " << jsClass->name
+ << Debug::flush;
+ return -1;
+ }
+ if (JSCLASS_RESERVED_SLOTS(jsClass) < 1) {
+ Debug::log(Debug::Error)
+ << "JavaObject::getObjectId -- " << static_cast<void*>(obj)
+ << " has only " << (JSCLASS_RESERVED_SLOTS(jsClass))
+ << " reserved slots, no objectId present" << Debug::flush;
+ return -1;
+ }
+#endif
+ if (!JS_GetReservedSlot(ctx, obj, 0, &val)) {
+ Debug::log(Debug::Error) << "Error getting reserved slot" << Debug::flush;
+ return -1;
+ }
+ // TODO: assert JSVAL_IS_INT(val)
+ return JSVAL_TO_INT(val);
+}
+
+SessionData* JavaObject::getSessionData(JSContext* ctx, JSObject* obj) {
+ void* data = JS_GetInstancePrivate(ctx, obj, &JavaObjectClass, NULL);
+ return static_cast<SessionData*>(data);
+}
+
+
+bool JavaObject::isJavaObject(JSContext* ctx, JSObject* obj) {
+ return JS_GET_CLASS(ctx, obj) == &JavaObjectClass;
+}
+
+JSObject* JavaObject::construct(JSContext* ctx, SessionData* data, int objectRef) {
+ // TODO: prototype? parent?
+ Debug::log(Debug::Spam) << "JavaObject::construct objectId=" << objectRef << Debug::flush;
+ JSObject* obj = JS_NewObject(ctx, &JavaObjectClass, NULL, NULL);
+ Debug::log(Debug::Spam) << " obj=" << obj << Debug::flush;
+ if (!obj) {
+ return NULL;
+ }
+ // set the session data
+ if (!JS_SetPrivate(ctx, obj, data)) {
+ Debug::log(Debug::Error) << "Could not set private data" << Debug::flush;
+ return NULL;
+ }
+ // set the objectId
+ if (!JS_SetReservedSlot(ctx, obj, 0, INT_TO_JSVAL(objectRef))) {
+ Debug::log(Debug::Error) << "Could not set reserved slot" << Debug::flush;
+ return NULL;
+ }
+ // define toString (TODO: some way to avoid doing this each time)
+#if 1
+ if (!JS_DefineFunction(ctx, obj, "toString", JavaObject::toString, 0, 0)) {
+ Debug::log(Debug::Error) << "Could not define toString method on object"
+ << Debug::flush;
+ }
+#endif
+ return obj;
+}
+
+JSBool JavaObject::getProperty(JSContext* ctx, JSObject* obj, jsval id,
+ jsval* rval) {
+ Debug::log(Debug::Spam) << "JavaObject::getProperty obj=" << obj << Debug::flush;
+ SessionData* data = JavaObject::getSessionData(ctx, obj);
+ if (!data) {
+ // TODO: replace the frame with an error page instead?
+ *rval = JSVAL_VOID;
+ return JS_TRUE;
+ }
+ int objectRef = JavaObject::getObjectId(ctx, obj);
+ if (JSVAL_IS_STRING(id)) {
+ JSString* str = JSVAL_TO_STRING(id);
+ if ((JS_GetStringLength(str) == 8) && !strncmp("toString",
+ JS_GetStringBytes(str), 8)) {
+ *rval = data->getToStringTearOff();
+ return JS_TRUE;
+ }
+ if ((JS_GetStringLength(str) == 2) && !strncmp("id",
+ JS_GetStringBytes(str), 2)) {
+ *rval = INT_TO_JSVAL(objectRef);
+ return JS_TRUE;
+ }
+ Debug::log(Debug::Error) << "Getting unexpected string property "
+ << dumpJsVal(ctx, id) << Debug::flush;
+ // TODO: throw a better exception here
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_INT(id)) {
+ Debug::log(Debug::Error) << "Getting non-int/non-string property "
+ << dumpJsVal(ctx, id) << Debug::flush;
+ // TODO: throw a better exception here
+ return JS_FALSE;
+ }
+ int dispId = JSVAL_TO_INT(id);
+
+ HostChannel* channel = data->getHostChannel();
+ SessionHandler* handler = data->getSessionHandler();
+
+ Value value = ServerMethods::getProperty(*channel, handler, objectRef, dispId);
+ data->makeJsvalFromValue(*rval, ctx, value);
+ return JS_TRUE;
+}
+
+JSBool JavaObject::setProperty(JSContext* ctx, JSObject* obj, jsval id,
+ jsval* vp) {
+ Debug::log(Debug::Spam) << "JavaObject::setProperty obj=" << obj << Debug::flush;
+ if (!JSVAL_IS_INT(id)) {
+ Debug::log(Debug::Error) << " Error: setting string property id" << Debug::flush;
+ // TODO: throw a better exception here
+ return JS_FALSE;
+ }
+
+ SessionData* data = JavaObject::getSessionData(ctx, obj);
+ if (!data) {
+ return JS_TRUE;
+ }
+
+ int objectRef = JavaObject::getObjectId(ctx, obj);
+ int dispId = JSVAL_TO_INT(id);
+
+ Value value;
+ data->makeValueFromJsval(value, ctx, *vp);
+
+ HostChannel* channel = data->getHostChannel();
+ SessionHandler* handler = data->getSessionHandler();
+
+ if (!ServerMethods::setProperty(*channel, handler, objectRef, dispId, value)) {
+ // TODO: throw a better exception here
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+}
+
+// TODO: can this be removed now?
+JSBool JavaObject::convert(JSContext* ctx, JSObject* obj, JSType type, jsval* vp) {
+ Debug::log(Debug::Spam) << "JavaObject::convert obj=" << obj
+ << " type=" << type << Debug::flush;
+ switch (type) {
+ case JSTYPE_STRING:
+ return toString(ctx, obj, 0, NULL, vp);
+ case JSTYPE_VOID:
+ *vp = JSVAL_VOID;
+ return JS_TRUE;
+ case JSTYPE_NULL:
+ *vp = JSVAL_NULL;
+ return JS_TRUE;
+ case JSTYPE_OBJECT:
+ *vp = OBJECT_TO_JSVAL(obj);
+ return JS_TRUE;
+ default:
+ break;
+ }
+ return JS_FALSE;
+}
+
+/**
+ * List of property names we want to fake on wrapped Java objects.
+ */
+static const char* propertyNames[] = {
+ "toString",
+ "id",
+};
+#define NUM_PROPERTY_NAMES (sizeof(propertyNames) / sizeof(propertyNames[0]))
+
+JSBool JavaObject::enumerate(JSContext* ctx, JSObject* obj, JSIterateOp op,
+ jsval* statep, jsid* idp) {
+ int objectId = JavaObject::getObjectId(ctx, obj);
+ switch (op) {
+ case JSENUMERATE_INIT:
+ Debug::log(Debug::Spam) << "JavaObject::enumerate(oid=" << objectId
+ << ", INIT)" << Debug::flush;
+ *statep = JSVAL_ZERO;
+ if (idp) {
+ *idp = INT_TO_JSVAL(NUM_PROPERTY_NAMES);
+ }
+ break;
+ case JSENUMERATE_NEXT:
+ {
+ int idNum = JSVAL_TO_INT(*statep);
+ Debug::log(Debug::Spam) << "JavaObject::enumerate(oid=" << objectId
+ << ", NEXT " << idNum << ")" << Debug::flush;
+ *statep = INT_TO_JSVAL(idNum + 1);
+ if (idNum >= NUM_PROPERTY_NAMES) {
+ *statep = JSVAL_NULL;
+ *idp = JSVAL_NULL;
+ } else {
+ const char* propName = propertyNames[idNum];
+ JSString* str = JS_NewStringCopyZ(ctx, propName);
+ return JS_ValueToId(ctx, STRING_TO_JSVAL(str), idp);
+ }
+ break;
+ }
+ case JSENUMERATE_DESTROY:
+ Debug::log(Debug::Spam) << "JavaObject::enumerate(oid=" << objectId
+ << ", DESTROY)" << Debug::flush;
+ *statep = JSVAL_NULL;
+ break;
+ default:
+ Debug::log(Debug::Error) << "Unknown Enumerate op " <<
+ static_cast<int>(op) << Debug::flush;
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+}
+
+void JavaObject::finalize(JSContext* ctx, JSObject* obj) {
+ Debug::log(Debug::Debugging) << "JavaObject::finalize obj=" << obj
+ << " objId=" << JavaObject::getObjectId(ctx, obj) << Debug::flush;
+ SessionData* data = JavaObject::getSessionData(ctx, obj);
+ if (data) {
+ int objectId = JavaObject::getObjectId(ctx, obj);
+ data->freeJavaObject(objectId);
+ JS_SetPrivate(ctx, obj, NULL);
+ }
+}
+
+JSBool JavaObject::toString(JSContext* ctx, JSObject* obj, uintN argc,
+ jsval* argv, jsval* rval) {
+ SessionData* data = JavaObject::getSessionData(ctx, obj);
+ if (!data) {
+ *rval = JSVAL_VOID;
+ return JS_TRUE;
+ }
+ int oid = getObjectId(ctx, obj);
+ Debug::log(Debug::Spam) << "JavaObject::toString(id=" << oid << ")"
+ << Debug::flush;
+ Value javaThis;
+ javaThis.setJavaObject(oid);
+ // we ignore any supplied parameters
+ return invokeJava(ctx, data, javaThis, InvokeMessage::TOSTRING_DISP_ID, 0,
+ NULL, rval);
+}
+
+/**
+ * Called when the JavaObject is invoked as a function.
+ * We ignore the JSObject* argument, which is the 'this' context, which is
+ * usually the window object. The JavaObject instance is in argv[-2].
+ *
+ * Returns a JS array, with the first element being a boolean indicating that
+ * an exception occured, and the second element is either the return value or
+ * the exception which was thrown. In this case, we always return false and
+ * raise the exception ourselves.
+ */
+JSBool JavaObject::call(JSContext* ctx, JSObject*, uintN argc, jsval* argv,
+ jsval* rval) {
+ // Get the JavaObject called as a function
+ JSObject* obj = JSVAL_TO_OBJECT(argv[-2]);
+ if (argc < 2 || !JSVAL_IS_INT(argv[0]) || !JSVAL_IS_OBJECT(argv[1])) {
+ Debug::log(Debug::Error) << "JavaObject::call incorrect arguments" << Debug::flush;
+ return JS_FALSE;
+ }
+ int dispId = JSVAL_TO_INT(argv[0]);
+ if (Debug::level(Debug::Spam)) {
+ Debug::DebugStream& dbg = Debug::log(Debug::Spam) << "JavaObject::call oid="
+ << JavaObject::getObjectId(ctx, obj) << ",dispId=" << dispId << " (";
+ for (unsigned i = 2; i < argc; ++i) {
+ if (i > 2) {
+ dbg << ", ";
+ }
+ dbg << dumpJsVal(ctx, argv[i]);
+ }
+ dbg << ")" << Debug::flush;
+ }
+
+ SessionData* data = JavaObject::getSessionData(ctx, obj);
+ if (!data) {
+ *rval = JSVAL_VOID;
+ return JS_TRUE;
+ }
+ Debug::log(Debug::Spam) << "Data = " << data << Debug::flush;
+
+ Value javaThis;
+ if (!JSVAL_IS_NULL(argv[1])) {
+ JSObject* thisObj = JSVAL_TO_OBJECT(argv[1]);
+ if (isJavaObject(ctx, thisObj)) {
+ javaThis.setJavaObject(getObjectId(ctx, thisObj));
+ } else {
+ data->makeValueFromJsval(javaThis, ctx, argv[1]);
+ }
+ } else {
+ int oid = getObjectId(ctx, obj);
+ javaThis.setJavaObject(oid);
+ }
+ return invokeJava(ctx, data, javaThis, dispId, argc - 2, &argv[2], rval);
+}
+
+/**
+ * Calls a method on a Java object and returns a two-element JS array, with
+ * the first element being a boolean flag indicating an exception was thrown,
+ * and the second element is the actual return value or exception.
+ */
+JSBool JavaObject::invokeJava(JSContext* ctx, SessionData* data,
+ const Value& javaThis, int dispId, int numArgs, const jsval* jsargs,
+ jsval* rval) {
+ HostChannel* channel = data->getHostChannel();
+ SessionHandler* handler = data->getSessionHandler();
+ scoped_array<Value> args(new Value[numArgs]);
+ for (int i = 0; i < numArgs; ++i) {
+ data->makeValueFromJsval(args[i], ctx, jsargs[i]);
+ }
+ if (!InvokeMessage::send(*channel, javaThis, dispId, numArgs, args.get())) {
+ Debug::log(Debug::Error) << "JavaObject::call failed to send invoke message" << Debug::flush;
+ return false;
+ }
+ Debug::log(Debug::Spam) << " return from invoke" << Debug::flush;
+ scoped_ptr<ReturnMessage> retMsg(channel->reactToMessagesWhileWaitingForReturn(handler));
+ if (!retMsg.get()) {
+ Debug::log(Debug::Error) << "JavaObject::call failed to get return value" << Debug::flush;
+ return false;
+ }
+ Value returnValue = retMsg->getReturnValue();
+ // Since we can set exceptions normally, we always return false to the
+ // wrapper function and set the exception ourselves if one occurs.
+ // TODO: cleanup exception case
+ jsval retvalArray[] = {JSVAL_FALSE, JSVAL_VOID};
+ JSObject* retval = JS_NewArrayObject(ctx, 2, retvalArray);
+ *rval = OBJECT_TO_JSVAL(retval);
+ jsval retJsVal;
+ Debug::log(Debug::Spam) << " result is " << returnValue << Debug::flush;
+ data->makeJsvalFromValue(retJsVal, ctx, returnValue);
+ if (retMsg->isException()) {
+ JS_SetPendingException(ctx, retJsVal);
+ return false;
+ }
+ if (!JS_SetElement(ctx, retval, 1, &retJsVal)) {
+ Debug::log(Debug::Error) << "Error setting return value element in array"
+ << Debug::flush;
+ return false;
+ }
+ return true;
+}