|  | /* | 
|  | * 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 to log debug-level output rather than just warnings. | 
|  | #define DEBUG | 
|  |  | 
|  | #include <cstdio> | 
|  | #include <cstdarg> | 
|  | #include <cwchar> | 
|  |  | 
|  | // 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" | 
|  |  | 
|  | /* | 
|  | * Debug definitions -- define FILETRACE to have debug output written to | 
|  | * a file named gwt-ll.log, or JAVATRACE to have debug output passed to the | 
|  | * Java LowLevelMoz.trace method. | 
|  | */ | 
|  | #ifdef ENABLE_TRACING | 
|  | #define FILETRACE | 
|  | //#define JAVATRACE | 
|  | #endif | 
|  |  | 
|  | // include javah-generated header to make sure we match | 
|  | #include "LowLevelMoz.h" | 
|  |  | 
|  | JNIEnv* savedJNIEnv = 0; | 
|  | jclass lowLevelMozClass; | 
|  |  | 
|  | // Only include debugging code if we are tracing somewhere. | 
|  | #ifdef ENABLE_TRACING | 
|  |  | 
|  | /* | 
|  | * Template so vsnprintf/vswprintf can be used interchangeably in the | 
|  | * append_sprintf template below. | 
|  | *   buf - pointer to the start of the output buffer | 
|  | *   len - maximum number of characters to write into the buffer | 
|  | *         (including the null terminator) | 
|  | *   fmt - printf-style format string | 
|  | *   args - stdarg-style variable arguments list | 
|  | *  Returns the number of characters written (excluding the null terminator) | 
|  | *   or -1 if an error occurred. | 
|  | * | 
|  | * Note that %lc and %ls are only legal in the wchar_t implementation. | 
|  | */ | 
|  | template<class charT> | 
|  | int safe_vsprintf(charT* buf, size_t len, const charT* fmt, va_list args); | 
|  |  | 
|  | // specialization for char that maps to vsnprintf | 
|  | template<> | 
|  | inline int safe_vsprintf<char>(char* buf, size_t len, const char* fmt, | 
|  | va_list args) { | 
|  | return ::vsnprintf(buf, len, fmt, args); | 
|  | } | 
|  |  | 
|  | // specialization for wchar_t that maps to vswprintf | 
|  | template<> | 
|  | inline int safe_vsprintf<wchar_t>(wchar_t* buf, size_t len, const wchar_t* fmt, | 
|  | va_list args) { | 
|  | return ::vswprintf(buf, len, fmt, args); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Safely append to a string buffer, updating the output pointer and always | 
|  | * reserving the last character of the buffer for a null terminator. | 
|  | *   bufStart - pointer to the start of the output buffer | 
|  | *   bufEnd - pointer just past the end of the output buffer | 
|  | *   fmt - format string | 
|  | *   additional arguments as passed to *printf | 
|  | * Returns the number of characters actually written, not including the null | 
|  | * terminator.  Nothing is written, including the null terminator, if the | 
|  | * buffer start points beyond the output buffer. | 
|  | * | 
|  | * Templated to work with any character type that has a safe_vsprintf | 
|  | * implementation. | 
|  | */ | 
|  | template<class charT> | 
|  | static int append_sprintf(charT* bufStart, const charT* bufEnd, | 
|  | const charT* fmt, ...) { | 
|  | va_list args; | 
|  | va_start(args, fmt); // initialize variable arguments list | 
|  | // compute space left in buffer: -1 for null terminator | 
|  | int maxlen = bufEnd - bufStart - 1; | 
|  | if (maxlen <= 0) return 0; | 
|  | int n = safe_vsprintf(bufStart, maxlen, fmt, args); | 
|  | va_end(args); | 
|  | if (n > maxlen) { | 
|  | n = maxlen; | 
|  | } | 
|  | bufStart[n] = 0; | 
|  | return n; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Log a given jsval with a prefix. | 
|  | *  cx - JSContext for the JS execution context to use | 
|  | *  val - jsval to print | 
|  | *  prefix - string to print before the value, defaults to empty string | 
|  | * | 
|  | * TODO(jat): this whole printf-style logging needs to be replaced, but we | 
|  | * run into library version issues if we use C++ iostreams so we would need | 
|  | * to implement our own equivalent.  Given that this code is all likely to | 
|  | * be rewritten for out-of-process hosted mode, it seems unlikely to be worth | 
|  | * the effort until that is completed. | 
|  | */ | 
|  | void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { | 
|  | JSType type = JS_TypeOfValue(cx, val); | 
|  | const char* typeString=JS_GetTypeName(cx, type); | 
|  | static const int BUF_SIZE = 256; | 
|  | char buf[BUF_SIZE]; | 
|  | const char *bufEnd = buf + BUF_SIZE; | 
|  | char* p = buf; | 
|  | p += append_sprintf(p, bufEnd, "%s%s", prefix, typeString); | 
|  | switch(type) { | 
|  | case JSTYPE_VOID: | 
|  | break; | 
|  | case JSTYPE_BOOLEAN: | 
|  | p += append_sprintf(p, bufEnd, ": %s", | 
|  | JSVAL_TO_BOOLEAN(val) ? "true" : "false"); | 
|  | break; | 
|  | case JSTYPE_NUMBER: | 
|  | if (JSVAL_IS_INT(val)) { | 
|  | p += append_sprintf(p, bufEnd, ": %d", JSVAL_TO_INT(val)); | 
|  | } else { | 
|  | p += append_sprintf(p, bufEnd, ": %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 += append_sprintf(p, bufEnd, " @ %08x, class %s", | 
|  | (unsigned)obj, clazz ? clazz->name : "<null>"); | 
|  | break; | 
|  | } | 
|  | case JSTYPE_FUNCTION: | 
|  | case JSTYPE_LIMIT: | 
|  | break; | 
|  | case JSTYPE_STRING: { | 
|  | /* | 
|  | * TODO(jat): support JS strings with international characters | 
|  | */ | 
|  | JsStringWrap str(cx, JSVAL_TO_STRING(val)); | 
|  | p += append_sprintf(p, bufEnd, ": %.*s", str.length(), str.bytes()); | 
|  | break; | 
|  | } | 
|  | } | 
|  | Tracer::log("%s", buf); | 
|  | } | 
|  | #else | 
|  | // Include a null version just to keep from cluttering up call sites. | 
|  | static inline void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { } | 
|  | #endif | 
|  |  | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * 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 scriptObjectInt, 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); | 
|  | JSContext* cx = JsRootedValue::currentContext(); | 
|  | nsCOMPtr<nsIScriptContext> scriptContext(GetScriptContextFromJSContext(cx)); | 
|  |  | 
|  | nsIScriptGlobalObject* scriptObject = | 
|  | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObjectInt); | 
|  | JSObject* scriptWindow = | 
|  | reinterpret_cast<JSObject*>(scriptObject->GetGlobalJSObject()); | 
|  | nsXPIDLString scriptString; | 
|  | scriptString = jcode.jstr(); | 
|  |  | 
|  | nsXPIDLString aRetValue; | 
|  | PRBool aIsUndefined; | 
|  | if (NS_FAILED(scriptContext->EvaluateString(scriptString, scriptWindow, 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); | 
|  | JSContext* cx = JsRootedValue::currentContext(); | 
|  | nsIScriptGlobalObject* scriptObject = | 
|  | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObjInt); | 
|  | 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: ()Z | 
|  | */ | 
|  | extern "C" JNIEXPORT jboolean JNICALL | 
|  | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1raiseJavaScriptException | 
|  | (JNIEnv* env, jclass) | 
|  | { | 
|  | Tracer tracer("LowLevelMoz._raiseJavaScriptException"); | 
|  | JS_SetPendingException(JsRootedValue::currentContext(), 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; | 
|  | } |