blob: 9c3aba21cfbd7977554939c688b274716cd3705d [file] [log] [blame]
jat@google.com64a55cb2009-10-16 14:16:57 +00001/*
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
49JNIEnv* savedJNIEnv = 0;
50jclass 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 */
68template<class charT>
69int safe_vsprintf(charT* buf, size_t len, const charT* fmt, va_list args);
70
71// specialization for char that maps to vsnprintf
72template<>
73inline 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
79template<>
80inline 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 */
99template<class charT>
100static 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 */
128void 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.
174static inline void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { }
175#endif
176
177
178static 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 */
204extern "C" JNIEXPORT jboolean JNICALL
205Java_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 */
246extern "C" JNIEXPORT jboolean JNICALL
247Java_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 */
328extern "C" JNIEXPORT jboolean JNICALL
329Java_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 */
342extern "C" JNIEXPORT jboolean JNICALL
343Java_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}