blob: 9c3aba21cfbd7977554939c688b274716cd3705d [file] [log] [blame]
/*
* 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;
}