| /* |
| * 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; |
| } |