|  | /* | 
|  | * Copyright 2007 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. | 
|  | */ | 
|  |  | 
|  | // Mozilla-specific hosted-mode methods | 
|  |  | 
|  | #define DEBUG | 
|  |  | 
|  | #include <cstdio> | 
|  |  | 
|  | //#define JS_GetClass JS_GetClassOld | 
|  |  | 
|  | // Mozilla header files | 
|  | #include "mozilla-headers.h" | 
|  |  | 
|  | #include <jni.h> | 
|  | #include "gwt-jni.h" | 
|  | #include "JsRootedValue.h" | 
|  | #include "ExternalWrapper.h" | 
|  | #include "Tracer.h" | 
|  | #include "JsStringWrap.h" | 
|  |  | 
|  | // include javah-generated header to make sure we match | 
|  | #include "LowLevelMoz.h" | 
|  |  | 
|  | //#define FILETRACE | 
|  | //#define JAVATRACE | 
|  |  | 
|  | // TODO(jat) should be in a header | 
|  | extern nsCID kGwtExternalCID; | 
|  |  | 
|  | JNIEnv* savedJNIEnv = 0; | 
|  | jclass lowLevelMozClass; | 
|  |  | 
|  | static void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { | 
|  | JSType type = JS_TypeOfValue(cx, val); | 
|  | const char* typeString=JS_GetTypeName(cx, type); | 
|  | char buf[256]; | 
|  | char* p = buf; | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), "%s%s", prefix, typeString); | 
|  | switch(type) { | 
|  | case JSTYPE_VOID: | 
|  | break; | 
|  | case JSTYPE_BOOLEAN: | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), ": %s", | 
|  | JSVAL_TO_BOOLEAN(val) ? "true" : "false"); | 
|  | break; | 
|  | case JSTYPE_NUMBER: | 
|  | if (JSVAL_IS_INT(val)) { | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), ": %d", JSVAL_TO_INT(val)); | 
|  | } else { | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), ": %lf", | 
|  | (double)*JSVAL_TO_DOUBLE(val)); | 
|  | } | 
|  | break; | 
|  | case JSTYPE_OBJECT: { | 
|  | JSObject* obj = JSVAL_TO_OBJECT(val); | 
|  | if (!JSVAL_IS_OBJECT(val)) break; | 
|  | JSClass* clazz = obj ? JS_GET_CLASS(cx, obj) : 0; | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), " @ %08x, class %s", | 
|  | (unsigned)obj, clazz ? clazz->name : "<null>"); | 
|  | break; | 
|  | } | 
|  | case JSTYPE_FUNCTION: | 
|  | case JSTYPE_LIMIT: | 
|  | break; | 
|  | case JSTYPE_STRING: { | 
|  | JsStringWrap str(cx, JSVAL_TO_STRING(val)); | 
|  | p += snprintf(p, sizeof(buf)-(p-buf), ": %.*s", str.length(), str.bytes()); | 
|  | break; | 
|  | } | 
|  | } | 
|  | Tracer::log("%s", buf); | 
|  | } | 
|  |  | 
|  |  | 
|  | static bool InitGlobals(JNIEnv* env, jclass llClass) { | 
|  | if (savedJNIEnv) | 
|  | return false; | 
|  |  | 
|  | #ifdef FILETRACE | 
|  | Tracer::setFile("gwt-ll.log"); | 
|  | #endif // FILETRACE | 
|  |  | 
|  | #ifdef JAVATRACE | 
|  | Tracer::setJava(env, llClass); | 
|  | #endif // JAVATRACE | 
|  |  | 
|  | #ifdef DEBUG | 
|  | Tracer::setLevel(Tracer::LEVEL_DEBUG); | 
|  | #endif | 
|  |  | 
|  | savedJNIEnv = env; | 
|  | lowLevelMozClass = static_cast<jclass>(env->NewGlobalRef(llClass)); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Print the current Java exception. | 
|  | */ | 
|  | static void PrintJavaException(JNIEnv* env) { | 
|  | jobject exception = env->ExceptionOccurred(); | 
|  | if (!exception) return; | 
|  | fprintf(stderr, "Exception occurred:\n"); | 
|  | env->ExceptionDescribe(); | 
|  | env->DeleteLocalRef(exception); | 
|  | } | 
|  |  | 
|  | /* Called from JavaScript to call a Java method that has previously been | 
|  | * wrapped. | 
|  | */ | 
|  | JSBool invokeJavaMethod(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, | 
|  | jsval *rval) | 
|  | { | 
|  | Tracer tracer("invokeJavaMethod"); | 
|  |  | 
|  | // I kid you not; this is how XPConnect gets their function object so they can | 
|  | // multiplex dispatch the call from a common site.  See XPCDispObject.cpp(466) | 
|  | // | 
|  | // I now have a secondary confirmation that this trick is legit. | 
|  | // brandon@mozilla.org writes: | 
|  | // | 
|  | // argv[-2] is part of the JS API, unabstracted.  Just as argv[0] is the | 
|  | // first argument (if argc != 0), argv[-1] is the |this| parameter (equal | 
|  | // to OBJECT_TO_JSVAL(obj) in a native method with the standard |obj| | 
|  | // second formal parameter name), and argv[-2] is the callee object, tagged | 
|  | // as a jsval. | 
|  | if (JS_TypeOfValue(cx, argv[-2]) != JSTYPE_FUNCTION) { | 
|  | tracer.setFail("not a function type"); | 
|  | return JS_FALSE; | 
|  | } | 
|  | JSObject* funObj = JSVAL_TO_OBJECT(argv[-2]); | 
|  |  | 
|  | // Pull the wrapper object out of the funObj's reserved slot | 
|  | jsval jsCleanupObj; | 
|  | if (!JS_GetReservedSlot(cx, funObj, 0, &jsCleanupObj)) { | 
|  | tracer.setFail("JS_GetReservedSlot failed"); | 
|  | return JS_FALSE; | 
|  | } | 
|  | JSObject* cleanupObj = JSVAL_TO_OBJECT(jsCleanupObj); | 
|  | if (!cleanupObj) { | 
|  | tracer.setFail("cleanupObj is null"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | // Get DispatchMethod instance out of the wrapper object | 
|  | jobject dispMeth = | 
|  | NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, cleanupObj)); | 
|  | if (!dispMeth) { | 
|  | tracer.setFail("dispMeth is null"); | 
|  | return JS_FALSE; | 
|  | } | 
|  | jclass dispClass = savedJNIEnv->GetObjectClass(dispMeth); | 
|  | if (!dispClass || savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("GetObjectClass returns null"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | // lookup the invoke method on the dispatch object | 
|  | jmethodID invokeID = | 
|  | savedJNIEnv->GetMethodID(dispClass, "invoke", "(II[II)V"); | 
|  | if (!invokeID || savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("GetMethodID failed"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | // create an array of integers to hold the JsRootedValue pointers passed | 
|  | // to the invoke method | 
|  | jintArray args = savedJNIEnv->NewIntArray(argc); | 
|  | if (!args || savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("NewIntArray failed"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | // these arguments are already rooted by the JS interpreter, but we | 
|  | // can't easily take advantage of that without complicating the JsRootedValue | 
|  | // interface. | 
|  |  | 
|  | // argv[-1] is OBJECT_TO_JSVAL(this) | 
|  | JsRootedValue* jsThis = new JsRootedValue(cx, argv[-1]); | 
|  | tracer.log("jsthis=%08x, RV=%08x", unsigned(argv[-1]), unsigned(jsThis)); | 
|  |  | 
|  | // create JsRootedValues for arguments | 
|  | JsRootedValue *jsArgs[argc]; | 
|  | for (uintN i = 0; i < argc; ++i) { | 
|  | jsArgs[i] = new JsRootedValue(cx, argv[i]); | 
|  | } | 
|  | savedJNIEnv->SetIntArrayRegion(args, 0, argc, | 
|  | reinterpret_cast<jint*>(jsArgs)); | 
|  | if (savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("SetIntArrayRegion failed"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | // slot for return value | 
|  | JsRootedValue* jsReturnVal = new JsRootedValue(cx); | 
|  |  | 
|  | // TODO(jat): small window here where invocation may fail before Java | 
|  | // takes ownership of the JsRootedValue objects.  One solution would be | 
|  | // to reference-count them between Java and C++ (so the reference count | 
|  | // would always be 0, 1, or 2).  Also setField has a similar problem. | 
|  | // I plan to fix this when switching away from Java holding pointers to | 
|  | // C++ objects as part of the fix for 64-bit support (which we could | 
|  | // accomplish inefficiently by changing int to long everywhere, but there | 
|  | // are other 64-bit issues to resolve and we need to reduce the number of | 
|  | // roots the JS interpreter has to search. | 
|  |  | 
|  | // call Java method | 
|  | savedJNIEnv->CallVoidMethod(dispMeth, invokeID, reinterpret_cast<int>(cx), | 
|  | reinterpret_cast<int>(jsThis), args, | 
|  | reinterpret_cast<int>(jsReturnVal)); | 
|  |  | 
|  | JSBool returnValue = JS_TRUE; | 
|  |  | 
|  | if (savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.log("dispMeth=%08x", unsigned(dispMeth)); | 
|  | tracer.setFail("java exception is active:"); | 
|  | PrintJavaException(savedJNIEnv); | 
|  | returnValue = JS_FALSE; | 
|  | } else if (JS_IsExceptionPending(cx)) { | 
|  | tracer.setFail("js exception is active"); | 
|  | returnValue = JS_FALSE; | 
|  | } | 
|  |  | 
|  | // extract return value | 
|  | *rval = jsReturnVal->getValue(); | 
|  |  | 
|  | #if 0 | 
|  | // NOTE: C++ objects are not cleaned up here because Java now owns them. | 
|  | // TODO(jat): if reference-counted, they *do* need to be Released here. | 
|  |  | 
|  | // free JsRootedValues | 
|  | for (uintN i = 0; i < argc; ++i) { | 
|  | delete jsArgs[i]; | 
|  | } | 
|  | delete jsThis; | 
|  | delete jsReturnVal; | 
|  | #endif | 
|  |  | 
|  | return returnValue; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Helper function to get reference Java attributes from Javascript. | 
|  | * | 
|  | * cx - JSContext pointer | 
|  | * obj - JavaScript object which is a wrapped Java object | 
|  | * id - property name, as a jsval string | 
|  | * dispClass - output parameter of DispatchMethod subclass | 
|  | * dispObj - output parameter of Java object | 
|  | * jident - output parameter of property name as a Java string | 
|  | */ | 
|  | JSBool getJavaPropertyStats(JSContext *cx, JSObject *obj, jsval id, | 
|  | jclass& dispClass, jobject& dispObj, jstring& jident) | 
|  | { | 
|  | Tracer tracer("getJavaPropertyStats"); | 
|  | if (!JSVAL_IS_STRING(id)) { | 
|  | tracer.setFail("id is not a string"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | jident = savedJNIEnv->NewString(JS_GetStringChars(JSVAL_TO_STRING(id)), | 
|  | JS_GetStringLength(JSVAL_TO_STRING(id))); | 
|  | if (!jident || savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("unable to create Java string"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | dispObj = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj)); | 
|  | if (!dispObj) { | 
|  | tracer.setFail("can't get dispatch object"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | dispClass = savedJNIEnv->GetObjectClass(dispObj); | 
|  | if (savedJNIEnv->ExceptionCheck()) { | 
|  | tracer.setFail("can't get class of dispatch object"); | 
|  | return JS_FALSE; | 
|  | } | 
|  |  | 
|  | return JS_TRUE; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Class:     com_google_gwt_dev_shell_moz_LowLevelMoz | 
|  | * Method:    _executeScriptWithInfo | 
|  | * Signature: (ILjava/lang/String;Ljava/lang/String;I)Z | 
|  | */ | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1executeScriptWithInfo | 
|  | (JNIEnv* env, jclass llClass, jint scriptObject, jstring code, | 
|  | jstring file, jint line) | 
|  | { | 
|  | Tracer tracer("LowLevelMoz._executeScriptWithInfo"); | 
|  | JStringWrap jcode(env, code); | 
|  | if (!jcode.jstr()) { | 
|  | tracer.setFail("null code string"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | JStringWrap jfile(env, file); | 
|  | if (!jfile.str()) { | 
|  | tracer.setFail("null file name"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | tracer.log("code=%s, file=%s, line=%d", jcode.str(), jfile.str(), line); | 
|  |  | 
|  | nsIScriptGlobalObject* globalObject = | 
|  | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObject); | 
|  | nsCOMPtr<nsIScriptContext> scriptContext(globalObject->GetContext()); | 
|  | nsXPIDLString scriptString; | 
|  | scriptString = jcode.jstr(); | 
|  |  | 
|  | nsXPIDLString aRetValue; | 
|  | PRBool aIsUndefined; | 
|  | if (NS_FAILED(scriptContext->EvaluateString(scriptString, | 
|  | globalObject->GetGlobalJSObject(), 0, jfile.str(), line, 0, | 
|  | aRetValue, &aIsUndefined))) { | 
|  | tracer.setFail("EvaluateString failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | return JNI_TRUE; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Class:     com_google_gwt_dev_shell_moz_LowLevelMoz | 
|  | * Method:    _invoke | 
|  | * Signature: (ILjava/lang/String;I[I)I | 
|  | */ | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1invoke | 
|  | (JNIEnv* env, jclass, jint scriptObjInt, jstring methodName, jint jsThisInt, | 
|  | jintArray jsArgsInt, jint jsRetValInt) | 
|  | { | 
|  | Tracer tracer("LowLevelMoz._invoke"); | 
|  |  | 
|  | JStringWrap methodStr(env, methodName); | 
|  | if (!methodStr.str()) { | 
|  | tracer.setFail("null method name"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | JsRootedValue* jsThisRV = reinterpret_cast<JsRootedValue*>(jsThisInt); | 
|  | jint jsArgc = env->GetArrayLength(jsArgsInt); | 
|  | tracer.log("method=%s, jsthis=%08x, #args=%d", methodStr.str(), jsThisInt, | 
|  | jsArgc); | 
|  |  | 
|  | nsIScriptGlobalObject* scriptObject = | 
|  | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObjInt); | 
|  | nsCOMPtr<nsIScriptContext> scriptContext(scriptObject->GetContext()); | 
|  | if (!scriptContext) { | 
|  | tracer.setFail("can't get script context"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | JSContext* cx | 
|  | = reinterpret_cast<JSContext*>(scriptContext->GetNativeContext()); | 
|  | JSObject* scriptWindow | 
|  | = reinterpret_cast<JSObject*>(scriptObject->GetGlobalJSObject()); | 
|  |  | 
|  | jsval fval; | 
|  | if (!JS_GetProperty(cx, scriptWindow, methodStr.str(), &fval)) { | 
|  | tracer.setFail("JS_GetProperty(method) failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | JSFunction* jsFunction = JS_ValueToFunction(cx, fval); | 
|  | if (!jsFunction) { | 
|  | tracer.setFail("JS_ValueToFunction failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  |  | 
|  | // extract arguments in jsval form | 
|  | nsAutoArrayPtr<jint> jsargvals(new jint[jsArgc]); | 
|  | if (!jsargvals) { | 
|  | tracer.setFail("failed to allocate arg array"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | env->GetIntArrayRegion(jsArgsInt, 0, jsArgc, jsargvals); | 
|  | if (env->ExceptionCheck()) { | 
|  | tracer.setFail("copy from Java array failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  | nsAutoArrayPtr<jsval> jsargs(new jsval[jsArgc]); | 
|  | for (int i = 0; i < jsArgc; ++i) { | 
|  | JsRootedValue* arg = reinterpret_cast<JsRootedValue*>(jsargvals[i]); | 
|  | jsargs[i] = arg->getValue(); | 
|  | } | 
|  |  | 
|  | jsval jsrval; | 
|  | JSObject* jsThis; | 
|  | if (jsThisRV->isNull()) { | 
|  | jsThis = scriptWindow; | 
|  | } else { | 
|  | jsThis = jsThisRV->getObject(); | 
|  | } | 
|  |  | 
|  | PrintJSValue(cx, OBJECT_TO_JSVAL(jsThis), "jsThis="); | 
|  | for (int i = 0; i < jsArgc; ++i) { | 
|  | char buf[256]; | 
|  | snprintf(buf, sizeof(buf), "arg[%d]=", i); | 
|  | PrintJSValue(cx, jsargs[i], buf); | 
|  | } | 
|  | //tracer.log("fval = %08x, args=%08x", fval, jsargs.get()); | 
|  | if (!JS_CallFunctionValue(cx, jsThis, fval, jsArgc, jsargs.get(), &jsrval)) { | 
|  | tracer.setFail("JS_CallFunctionValue failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  |  | 
|  | PrintJSValue(cx, jsrval, "return value="); | 
|  | JsRootedValue* returnVal = reinterpret_cast<JsRootedValue*>(jsRetValInt); | 
|  | returnVal->setValue(jsrval); | 
|  | return JNI_TRUE; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Class:     com_google_gwt_dev_shell_moz_LowLevelMoz | 
|  | * Method:    _raiseJavaScriptException | 
|  | * Signature: (I)Z | 
|  | */ | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1raiseJavaScriptException | 
|  | (JNIEnv* env, jclass, jint jscontext) | 
|  | { | 
|  | Tracer tracer("LowLevelMoz._raiseJavaScriptException"); | 
|  | JSContext* cx = reinterpret_cast<JSContext*>(jscontext); | 
|  | JS_SetPendingException(cx, JSVAL_NULL); | 
|  | return JNI_TRUE; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Class:     com_google_gwt_dev_shell_moz_LowLevelMoz | 
|  | * Method:    _registerExternalFactoryHandler | 
|  | * Signature: ()Z | 
|  | */ | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1registerExternalFactoryHandler | 
|  | (JNIEnv* env, jclass llClass) | 
|  | { | 
|  | if (!InitGlobals(env, llClass)) | 
|  | return JNI_FALSE; | 
|  |  | 
|  | // tracing isn't setup until after InitGlobals is called | 
|  | Tracer tracer("LowLevelMoz._registerExternalFactoryHandler"); | 
|  |  | 
|  | char buf[256]; | 
|  | sprintf(buf, " jniEnv=%08x, llClass=%08x", (unsigned)env, (unsigned)llClass); | 
|  | tracer.log(buf); | 
|  |  | 
|  | // Register "window.external" as our own class | 
|  | if (NS_FAILED(nsComponentManager::RegisterFactory( | 
|  | kGwtExternalCID, "externalFactory", GWT_EXTERNAL_CONTRACTID, | 
|  | new nsRpExternalFactory(), PR_TRUE))) { | 
|  | tracer.setFail("RegisterFactory failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  |  | 
|  | nsCOMPtr<nsICategoryManager> categoryManager = | 
|  | do_GetService(NS_CATEGORYMANAGER_CONTRACTID); | 
|  | if (!categoryManager) { | 
|  | tracer.setFail("unable to get category manager"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  |  | 
|  | nsXPIDLCString previous; | 
|  | if (NS_FAILED(categoryManager->AddCategoryEntry( | 
|  | JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY, "external", GWT_EXTERNAL_CONTRACTID, | 
|  | PR_TRUE, PR_TRUE, getter_Copies(previous)))) { | 
|  | tracer.setFail("AddCategoryEntry failed"); | 
|  | return JNI_FALSE; | 
|  | } | 
|  |  | 
|  | return JNI_TRUE; | 
|  | } |