jat@google.com | 64a55cb | 2009-10-16 14:16:57 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2007 Google Inc. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| 5 | * use this file except in compliance with the License. You may obtain a copy of |
| 6 | * the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 13 | * License for the specific language governing permissions and limitations under |
| 14 | * the License. |
| 15 | */ |
| 16 | |
| 17 | // Mozilla-specific hosted-mode methods |
| 18 | |
| 19 | // Define to log debug-level output rather than just warnings. |
| 20 | #define DEBUG |
| 21 | |
| 22 | #include <cstdio> |
| 23 | #include <cstdarg> |
| 24 | #include <cwchar> |
| 25 | |
| 26 | // Mozilla header files |
| 27 | #include "mozilla-headers.h" |
| 28 | |
| 29 | #include <jni.h> |
| 30 | #include "gwt-jni.h" |
| 31 | #include "JsRootedValue.h" |
| 32 | #include "ExternalWrapper.h" |
| 33 | #include "Tracer.h" |
| 34 | #include "JsStringWrap.h" |
| 35 | |
| 36 | /* |
| 37 | * Debug definitions -- define FILETRACE to have debug output written to |
| 38 | * a file named gwt-ll.log, or JAVATRACE to have debug output passed to the |
| 39 | * Java LowLevelMoz.trace method. |
| 40 | */ |
| 41 | #ifdef ENABLE_TRACING |
| 42 | #define FILETRACE |
| 43 | //#define JAVATRACE |
| 44 | #endif |
| 45 | |
| 46 | // include javah-generated header to make sure we match |
| 47 | #include "LowLevelMoz.h" |
| 48 | |
| 49 | JNIEnv* savedJNIEnv = 0; |
| 50 | jclass lowLevelMozClass; |
| 51 | |
| 52 | // Only include debugging code if we are tracing somewhere. |
| 53 | #ifdef ENABLE_TRACING |
| 54 | |
| 55 | /* |
| 56 | * Template so vsnprintf/vswprintf can be used interchangeably in the |
| 57 | * append_sprintf template below. |
| 58 | * buf - pointer to the start of the output buffer |
| 59 | * len - maximum number of characters to write into the buffer |
| 60 | * (including the null terminator) |
| 61 | * fmt - printf-style format string |
| 62 | * args - stdarg-style variable arguments list |
| 63 | * Returns the number of characters written (excluding the null terminator) |
| 64 | * or -1 if an error occurred. |
| 65 | * |
| 66 | * Note that %lc and %ls are only legal in the wchar_t implementation. |
| 67 | */ |
| 68 | template<class charT> |
| 69 | int safe_vsprintf(charT* buf, size_t len, const charT* fmt, va_list args); |
| 70 | |
| 71 | // specialization for char that maps to vsnprintf |
| 72 | template<> |
| 73 | inline int safe_vsprintf<char>(char* buf, size_t len, const char* fmt, |
| 74 | va_list args) { |
| 75 | return ::vsnprintf(buf, len, fmt, args); |
| 76 | } |
| 77 | |
| 78 | // specialization for wchar_t that maps to vswprintf |
| 79 | template<> |
| 80 | inline int safe_vsprintf<wchar_t>(wchar_t* buf, size_t len, const wchar_t* fmt, |
| 81 | va_list args) { |
| 82 | return ::vswprintf(buf, len, fmt, args); |
| 83 | } |
| 84 | |
| 85 | /* |
| 86 | * Safely append to a string buffer, updating the output pointer and always |
| 87 | * reserving the last character of the buffer for a null terminator. |
| 88 | * bufStart - pointer to the start of the output buffer |
| 89 | * bufEnd - pointer just past the end of the output buffer |
| 90 | * fmt - format string |
| 91 | * additional arguments as passed to *printf |
| 92 | * Returns the number of characters actually written, not including the null |
| 93 | * terminator. Nothing is written, including the null terminator, if the |
| 94 | * buffer start points beyond the output buffer. |
| 95 | * |
| 96 | * Templated to work with any character type that has a safe_vsprintf |
| 97 | * implementation. |
| 98 | */ |
| 99 | template<class charT> |
| 100 | static int append_sprintf(charT* bufStart, const charT* bufEnd, |
| 101 | const charT* fmt, ...) { |
| 102 | va_list args; |
| 103 | va_start(args, fmt); // initialize variable arguments list |
| 104 | // compute space left in buffer: -1 for null terminator |
| 105 | int maxlen = bufEnd - bufStart - 1; |
| 106 | if (maxlen <= 0) return 0; |
| 107 | int n = safe_vsprintf(bufStart, maxlen, fmt, args); |
| 108 | va_end(args); |
| 109 | if (n > maxlen) { |
| 110 | n = maxlen; |
| 111 | } |
| 112 | bufStart[n] = 0; |
| 113 | return n; |
| 114 | } |
| 115 | |
| 116 | /* |
| 117 | * Log a given jsval with a prefix. |
| 118 | * cx - JSContext for the JS execution context to use |
| 119 | * val - jsval to print |
| 120 | * prefix - string to print before the value, defaults to empty string |
| 121 | * |
| 122 | * TODO(jat): this whole printf-style logging needs to be replaced, but we |
| 123 | * run into library version issues if we use C++ iostreams so we would need |
| 124 | * to implement our own equivalent. Given that this code is all likely to |
| 125 | * be rewritten for out-of-process hosted mode, it seems unlikely to be worth |
| 126 | * the effort until that is completed. |
| 127 | */ |
| 128 | void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { |
| 129 | JSType type = JS_TypeOfValue(cx, val); |
| 130 | const char* typeString=JS_GetTypeName(cx, type); |
| 131 | static const int BUF_SIZE = 256; |
| 132 | char buf[BUF_SIZE]; |
| 133 | const char *bufEnd = buf + BUF_SIZE; |
| 134 | char* p = buf; |
| 135 | p += append_sprintf(p, bufEnd, "%s%s", prefix, typeString); |
| 136 | switch(type) { |
| 137 | case JSTYPE_VOID: |
| 138 | break; |
| 139 | case JSTYPE_BOOLEAN: |
| 140 | p += append_sprintf(p, bufEnd, ": %s", |
| 141 | JSVAL_TO_BOOLEAN(val) ? "true" : "false"); |
| 142 | break; |
| 143 | case JSTYPE_NUMBER: |
| 144 | if (JSVAL_IS_INT(val)) { |
| 145 | p += append_sprintf(p, bufEnd, ": %d", JSVAL_TO_INT(val)); |
| 146 | } else { |
| 147 | p += append_sprintf(p, bufEnd, ": %lf", (double)*JSVAL_TO_DOUBLE(val)); |
| 148 | } |
| 149 | break; |
| 150 | case JSTYPE_OBJECT: { |
| 151 | JSObject* obj = JSVAL_TO_OBJECT(val); |
| 152 | if (!JSVAL_IS_OBJECT(val)) break; |
| 153 | JSClass* clazz = obj ? JS_GET_CLASS(cx, obj) : 0; |
| 154 | p += append_sprintf(p, bufEnd, " @ %08x, class %s", |
| 155 | (unsigned)obj, clazz ? clazz->name : "<null>"); |
| 156 | break; |
| 157 | } |
| 158 | case JSTYPE_FUNCTION: |
| 159 | case JSTYPE_LIMIT: |
| 160 | break; |
| 161 | case JSTYPE_STRING: { |
| 162 | /* |
| 163 | * TODO(jat): support JS strings with international characters |
| 164 | */ |
| 165 | JsStringWrap str(cx, JSVAL_TO_STRING(val)); |
| 166 | p += append_sprintf(p, bufEnd, ": %.*s", str.length(), str.bytes()); |
| 167 | break; |
| 168 | } |
| 169 | } |
| 170 | Tracer::log("%s", buf); |
| 171 | } |
| 172 | #else |
| 173 | // Include a null version just to keep from cluttering up call sites. |
| 174 | static inline void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { } |
| 175 | #endif |
| 176 | |
| 177 | |
| 178 | static bool InitGlobals(JNIEnv* env, jclass llClass) { |
| 179 | if (savedJNIEnv) |
| 180 | return false; |
| 181 | |
| 182 | #ifdef FILETRACE |
| 183 | Tracer::setFile("gwt-ll.log"); |
| 184 | #endif // FILETRACE |
| 185 | |
| 186 | #ifdef JAVATRACE |
| 187 | Tracer::setJava(env, llClass); |
| 188 | #endif // JAVATRACE |
| 189 | |
| 190 | #ifdef DEBUG |
| 191 | Tracer::setLevel(Tracer::LEVEL_DEBUG); |
| 192 | #endif |
| 193 | |
| 194 | savedJNIEnv = env; |
| 195 | lowLevelMozClass = static_cast<jclass>(env->NewGlobalRef(llClass)); |
| 196 | return true; |
| 197 | } |
| 198 | |
| 199 | /* |
| 200 | * Class: com_google_gwt_dev_shell_moz_LowLevelMoz |
| 201 | * Method: _executeScriptWithInfo |
| 202 | * Signature: (ILjava/lang/String;Ljava/lang/String;I)Z |
| 203 | */ |
| 204 | extern "C" JNIEXPORT jboolean JNICALL |
| 205 | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1executeScriptWithInfo |
| 206 | (JNIEnv* env, jclass llClass, jint scriptObjectInt, jstring code, |
| 207 | jstring file, jint line) |
| 208 | { |
| 209 | Tracer tracer("LowLevelMoz._executeScriptWithInfo"); |
| 210 | JStringWrap jcode(env, code); |
| 211 | if (!jcode.jstr()) { |
| 212 | tracer.setFail("null code string"); |
| 213 | return JNI_FALSE; |
| 214 | } |
| 215 | JStringWrap jfile(env, file); |
| 216 | if (!jfile.str()) { |
| 217 | tracer.setFail("null file name"); |
| 218 | return JNI_FALSE; |
| 219 | } |
| 220 | tracer.log("code=%s, file=%s, line=%d", jcode.str(), jfile.str(), line); |
| 221 | JSContext* cx = JsRootedValue::currentContext(); |
| 222 | nsCOMPtr<nsIScriptContext> scriptContext(GetScriptContextFromJSContext(cx)); |
| 223 | |
| 224 | nsIScriptGlobalObject* scriptObject = |
| 225 | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObjectInt); |
| 226 | JSObject* scriptWindow = |
| 227 | reinterpret_cast<JSObject*>(scriptObject->GetGlobalJSObject()); |
| 228 | nsXPIDLString scriptString; |
| 229 | scriptString = jcode.jstr(); |
| 230 | |
| 231 | nsXPIDLString aRetValue; |
| 232 | PRBool aIsUndefined; |
| 233 | if (NS_FAILED(scriptContext->EvaluateString(scriptString, scriptWindow, 0, |
| 234 | jfile.str(), line, 0, aRetValue, &aIsUndefined))) { |
| 235 | tracer.setFail("EvaluateString failed"); |
| 236 | return JNI_FALSE; |
| 237 | } |
| 238 | return JNI_TRUE; |
| 239 | } |
| 240 | |
| 241 | /* |
| 242 | * Class: com_google_gwt_dev_shell_moz_LowLevelMoz |
| 243 | * Method: _invoke |
| 244 | * Signature: (ILjava/lang/String;I[I)I |
| 245 | */ |
| 246 | extern "C" JNIEXPORT jboolean JNICALL |
| 247 | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1invoke |
| 248 | (JNIEnv* env, jclass, jint scriptObjInt, jstring methodName, jint jsThisInt, |
| 249 | jintArray jsArgsInt, jint jsRetValInt) |
| 250 | { |
| 251 | Tracer tracer("LowLevelMoz._invoke"); |
| 252 | |
| 253 | JStringWrap methodStr(env, methodName); |
| 254 | if (!methodStr.str()) { |
| 255 | tracer.setFail("null method name"); |
| 256 | return JNI_FALSE; |
| 257 | } |
| 258 | JsRootedValue* jsThisRV = reinterpret_cast<JsRootedValue*>(jsThisInt); |
| 259 | jint jsArgc = env->GetArrayLength(jsArgsInt); |
| 260 | tracer.log("method=%s, jsthis=%08x, #args=%d", methodStr.str(), jsThisInt, |
| 261 | jsArgc); |
| 262 | JSContext* cx = JsRootedValue::currentContext(); |
| 263 | nsIScriptGlobalObject* scriptObject = |
| 264 | NS_REINTERPRET_CAST(nsIScriptGlobalObject*, scriptObjInt); |
| 265 | JSObject* scriptWindow |
| 266 | = reinterpret_cast<JSObject*>(scriptObject->GetGlobalJSObject()); |
| 267 | |
| 268 | jsval fval; |
| 269 | if (!JS_GetProperty(cx, scriptWindow, methodStr.str(), &fval)) { |
| 270 | tracer.setFail("JS_GetProperty(method) failed"); |
| 271 | return JNI_FALSE; |
| 272 | } |
| 273 | JSFunction* jsFunction = JS_ValueToFunction(cx, fval); |
| 274 | if (!jsFunction) { |
| 275 | tracer.setFail("JS_ValueToFunction failed"); |
| 276 | return JNI_FALSE; |
| 277 | } |
| 278 | |
| 279 | // extract arguments in jsval form |
| 280 | nsAutoArrayPtr<jint> jsargvals(new jint[jsArgc]); |
| 281 | if (!jsargvals) { |
| 282 | tracer.setFail("failed to allocate arg array"); |
| 283 | return JNI_FALSE; |
| 284 | } |
| 285 | env->GetIntArrayRegion(jsArgsInt, 0, jsArgc, jsargvals); |
| 286 | if (env->ExceptionCheck()) { |
| 287 | tracer.setFail("copy from Java array failed"); |
| 288 | return JNI_FALSE; |
| 289 | } |
| 290 | nsAutoArrayPtr<jsval> jsargs(new jsval[jsArgc]); |
| 291 | for (int i = 0; i < jsArgc; ++i) { |
| 292 | JsRootedValue* arg = reinterpret_cast<JsRootedValue*>(jsargvals[i]); |
| 293 | jsargs[i] = arg->getValue(); |
| 294 | } |
| 295 | |
| 296 | jsval jsrval; |
| 297 | JSObject* jsThis; |
| 298 | if (jsThisRV->isNull()) { |
| 299 | jsThis = scriptWindow; |
| 300 | } else { |
| 301 | jsThis = jsThisRV->getObject(); |
| 302 | } |
| 303 | |
| 304 | PrintJSValue(cx, OBJECT_TO_JSVAL(jsThis), "jsThis="); |
| 305 | for (int i = 0; i < jsArgc; ++i) { |
| 306 | char buf[256]; |
| 307 | snprintf(buf, sizeof(buf), "arg[%d]=", i); |
| 308 | PrintJSValue(cx, jsargs[i], buf); |
| 309 | } |
| 310 | //tracer.log("fval = %08x, args=%08x", fval, jsargs.get()); |
| 311 | if (!JS_CallFunctionValue(cx, jsThis, fval, jsArgc, jsargs.get(), &jsrval)) { |
| 312 | tracer.setFail("JS_CallFunctionValue failed"); |
| 313 | return JNI_FALSE; |
| 314 | } |
| 315 | |
| 316 | PrintJSValue(cx, jsrval, "return value="); |
| 317 | JsRootedValue* returnVal = reinterpret_cast<JsRootedValue*>(jsRetValInt); |
| 318 | returnVal->setValue(jsrval); |
| 319 | return JNI_TRUE; |
| 320 | } |
| 321 | |
| 322 | |
| 323 | /* |
| 324 | * Class: com_google_gwt_dev_shell_moz_LowLevelMoz |
| 325 | * Method: _raiseJavaScriptException |
| 326 | * Signature: ()Z |
| 327 | */ |
| 328 | extern "C" JNIEXPORT jboolean JNICALL |
| 329 | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1raiseJavaScriptException |
| 330 | (JNIEnv* env, jclass) |
| 331 | { |
| 332 | Tracer tracer("LowLevelMoz._raiseJavaScriptException"); |
| 333 | JS_SetPendingException(JsRootedValue::currentContext(), JSVAL_NULL); |
| 334 | return JNI_TRUE; |
| 335 | } |
| 336 | |
| 337 | /* |
| 338 | * Class: com_google_gwt_dev_shell_moz_LowLevelMoz |
| 339 | * Method: _registerExternalFactoryHandler |
| 340 | * Signature: ()Z |
| 341 | */ |
| 342 | extern "C" JNIEXPORT jboolean JNICALL |
| 343 | Java_com_google_gwt_dev_shell_moz_LowLevelMoz__1registerExternalFactoryHandler |
| 344 | (JNIEnv* env, jclass llClass) |
| 345 | { |
| 346 | if (!InitGlobals(env, llClass)) |
| 347 | return JNI_FALSE; |
| 348 | |
| 349 | // tracing isn't setup until after InitGlobals is called |
| 350 | Tracer tracer("LowLevelMoz._registerExternalFactoryHandler"); |
| 351 | |
| 352 | char buf[256]; |
| 353 | sprintf(buf, " jniEnv=%08x, llClass=%08x", (unsigned)env, (unsigned)llClass); |
| 354 | tracer.log(buf); |
| 355 | |
| 356 | // Register "window.external" as our own class |
| 357 | if (NS_FAILED(nsComponentManager::RegisterFactory( |
| 358 | kGwtExternalCID, "externalFactory", GWT_EXTERNAL_CONTRACTID, |
| 359 | new nsRpExternalFactory(), PR_TRUE))) { |
| 360 | tracer.setFail("RegisterFactory failed"); |
| 361 | return JNI_FALSE; |
| 362 | } |
| 363 | |
| 364 | nsCOMPtr<nsICategoryManager> categoryManager = |
| 365 | do_GetService(NS_CATEGORYMANAGER_CONTRACTID); |
| 366 | if (!categoryManager) { |
| 367 | tracer.setFail("unable to get category manager"); |
| 368 | return JNI_FALSE; |
| 369 | } |
| 370 | |
| 371 | nsXPIDLCString previous; |
| 372 | if (NS_FAILED(categoryManager->AddCategoryEntry( |
| 373 | JAVASCRIPT_GLOBAL_PROPERTY_CATEGORY, "external", GWT_EXTERNAL_CONTRACTID, |
| 374 | PR_TRUE, PR_TRUE, getter_Copies(previous)))) { |
| 375 | tracer.setFail("AddCategoryEntry failed"); |
| 376 | return JNI_FALSE; |
| 377 | } |
| 378 | |
| 379 | return JNI_TRUE; |
| 380 | } |