blob: 3b2854e259591acb9021bc5d229759888b427f71 [file] [log] [blame]
gwt.team.jatc85cb7f2007-03-02 20:13:50 +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/*
18 * Defines the JavaScript class gwt_external_class, which interface via JNI
19 * to Java objects.
20 */
21
22#include <jni.h>
23#include "JsRootedValue.h"
24#include "gwt-jni.h"
25#include "ExternalWrapper.h"
26#include "Tracer.h"
27#include "JsStringWrap.h"
28
scottb@google.com6435ea22008-06-11 01:12:18 +000029#ifdef ENABLE_TRACING
30extern void PrintJSValue(JSContext* cx, jsval val, char* prefix="");
31#else
32// Include a null version just to keep from cluttering up call sites.
33static inline void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { }
34#endif
35
gwt.team.jatc85cb7f2007-03-02 20:13:50 +000036// note that this does not use the NS_DEFINE_CID macro because it defines
37// the variable as const, which by default has internal linkage. I could
38// work around it by putting extern in front of the macro, but that seems
39// fragile if the macro changes.
40nsCID kGwtExternalCID = GWT_EXTERNAL_FACTORY_CID;
41
42/*
43 * definition of JavaScript gwtOnLoad method which maps to the Java
44 * gwtOnLoad method.
45 */
46static JSBool gwtOnLoad(JSContext *cx, JSObject *obj, uintN argc,
47 jsval *argv, jsval *rval)
48{
49 Tracer tracer("gwtOnLoad");
gwt.team.jatd43ad6c2007-03-19 22:24:12 +000050 tracer.log("context=%08x", unsigned(cx));
gwt.team.scottb63d38d02007-07-25 20:51:13 +000051 JsRootedValue::ContextManager context(cx);
gwt.team.jatd43ad6c2007-03-19 22:24:12 +000052 JsRootedValue::ensureRuntime(cx);
jat@google.come7f61a72008-12-24 00:45:21 +000053 if (argc < 3) {
54 tracer.setFail("less than 3 args");
gwt.team.jatc85cb7f2007-03-02 20:13:50 +000055 return JS_FALSE;
56 }
57
58 JSObject* scriptWindow = 0;
59 if (argv[0] != JSVAL_NULL && argv[0] != JSVAL_VOID) {
60 if (!JS_ValueToObject(cx, argv[0], &scriptWindow)) {
61 tracer.setFail("can't get script window object");
62 return JS_FALSE;
63 }
64 }
65
66 JSString* moduleName = 0;
67 if (argv[1] != JSVAL_NULL && argv[1] != JSVAL_VOID) {
68 moduleName = JS_ValueToString(cx, argv[1]);
69 }
70
scottb@google.combd9fe8c2008-08-28 22:56:54 +000071 JSString* version = 0;
jat@google.come7f61a72008-12-24 00:45:21 +000072 if (argv[2] != JSVAL_NULL && argv[2] != JSVAL_VOID) {
scottb@google.combd9fe8c2008-08-28 22:56:54 +000073 version = JS_ValueToString(cx, argv[2]);
74 }
75
gwt.team.jatc85cb7f2007-03-02 20:13:50 +000076 nsCOMPtr<nsIScriptGlobalObject> scriptGlobal(0);
77 if (scriptWindow) {
78 nsCOMPtr<nsIXPConnect> xpConnect = do_GetService(nsIXPConnect::GetCID());
79 nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative;
80 xpConnect->GetWrappedNativeOfJSObject(cx, scriptWindow,
81 getter_AddRefs(wrappedNative));
82 if (wrappedNative) {
83 nsCOMPtr<nsISupports> native;
84 wrappedNative->GetNative(getter_AddRefs(native));
85 scriptGlobal = do_QueryInterface(native);
86 }
87 }
88 jstring jModuleName(0);
89 if (moduleName) {
90 jModuleName = savedJNIEnv->NewString(JS_GetStringChars(moduleName),
91 JS_GetStringLength(moduleName));
92 if (!jModuleName || savedJNIEnv->ExceptionCheck()) {
93 tracer.setFail("can't get module name in Java string");
94 return JS_FALSE;
95 }
gwt.team.jatd43ad6c2007-03-19 22:24:12 +000096 tracer.log("module name=%s", JS_GetStringBytes(moduleName));
97 } else {
scottb@google.com6435ea22008-06-11 01:12:18 +000098 tracer.log("null module name");
gwt.team.jatc85cb7f2007-03-02 20:13:50 +000099 }
scottb@google.com6435ea22008-06-11 01:12:18 +0000100
scottb@google.combd9fe8c2008-08-28 22:56:54 +0000101 jstring jVersion(0);
102 if (version) {
103 jVersion = savedJNIEnv->NewString(JS_GetStringChars(version),
104 JS_GetStringLength(version));
105 if (!jVersion || savedJNIEnv->ExceptionCheck()) {
106 tracer.setFail("can't get module name in Java string");
107 return JS_FALSE;
108 }
109 tracer.log("version=%s", JS_GetStringBytes(version));
110 } else {
111 tracer.log("null version");
112 }
113
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000114 jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj));
115 jclass objClass = savedJNIEnv->GetObjectClass(externalObject);
116 if (!objClass || savedJNIEnv->ExceptionCheck()) {
117 tracer.setFail("can't get LowLevelMoz.ExternalObject class");
118 return JS_FALSE;
119 }
scottb@google.com6435ea22008-06-11 01:12:18 +0000120
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000121 jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "gwtOnLoad",
scottb@google.combd9fe8c2008-08-28 22:56:54 +0000122 "(ILjava/lang/String;Ljava/lang/String;)Z");
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000123 if (!methodID || savedJNIEnv->ExceptionCheck()) {
124 tracer.setFail("can't get gwtOnLoad method");
125 return JS_FALSE;
126 }
scottb@google.com6435ea22008-06-11 01:12:18 +0000127
gwt.team.jatd43ad6c2007-03-19 22:24:12 +0000128 tracer.log("scriptGlobal=%08x", unsigned(scriptGlobal.get()));
scottb@google.com6435ea22008-06-11 01:12:18 +0000129
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000130 jboolean result = savedJNIEnv->CallBooleanMethod(externalObject, methodID,
scottb@google.combd9fe8c2008-08-28 22:56:54 +0000131 NS_REINTERPRET_CAST(jint, scriptGlobal.get()), jModuleName, jVersion);
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000132 if (savedJNIEnv->ExceptionCheck()) {
133 tracer.setFail("LowLevelMoz.ExternalObject.gwtOnLoad() threw an exception");
134 return JS_FALSE;
135 }
136 *rval = BOOLEAN_TO_JSVAL((result == JNI_FALSE) ? JS_FALSE : JS_TRUE);
137 return JS_TRUE;
138}
139
jat@google.come7f61a72008-12-24 00:45:21 +0000140/*
141 * definition of JavaScript initModule method which maps to the Java
142 * initModule method.
143 */
144static JSBool initModule(JSContext *cx, JSObject *obj, uintN argc,
145 jsval *argv, jsval *rval)
146{
147 Tracer tracer("initModule");
148 tracer.log("context=%08x", unsigned(cx));
149 JsRootedValue::ContextManager context(cx);
150 JsRootedValue::ensureRuntime(cx);
151 if (argc < 1) {
152 tracer.setFail("less than 1 args");
153 return JS_FALSE;
154 }
155
156 JSString* moduleName = 0;
157 if (argv[0] != JSVAL_NULL && argv[0] != JSVAL_VOID) {
158 moduleName = JS_ValueToString(cx, argv[0]);
159 }
160
161 jstring jModuleName(0);
162 if (moduleName) {
163 jModuleName = savedJNIEnv->NewString(JS_GetStringChars(moduleName),
164 JS_GetStringLength(moduleName));
165 if (!jModuleName || savedJNIEnv->ExceptionCheck()) {
166 tracer.setFail("can't get module name in Java string");
167 return JS_FALSE;
168 }
169 tracer.log("module name=%s", JS_GetStringBytes(moduleName));
170 } else {
171 tracer.log("null module name");
172 }
173
174 jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj));
175 jclass objClass = savedJNIEnv->GetObjectClass(externalObject);
176 if (!objClass || savedJNIEnv->ExceptionCheck()) {
177 tracer.setFail("can't get LowLevelMoz.ExternalObject class");
178 return JS_FALSE;
179 }
180
181 jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "initModule",
182 "(Ljava/lang/String;)Z");
183 if (!methodID || savedJNIEnv->ExceptionCheck()) {
184 tracer.setFail("can't get initModule method");
185 return JS_FALSE;
186 }
187
188 jboolean result = savedJNIEnv->CallBooleanMethod(externalObject, methodID,
189 jModuleName);
190 if (savedJNIEnv->ExceptionCheck()) {
191 tracer.setFail("LowLevelMoz.ExternalObject.initModule() threw an exception");
192 return JS_FALSE;
193 }
194 *rval = BOOLEAN_TO_JSVAL((result == JNI_FALSE) ? JS_FALSE : JS_TRUE);
195 return JS_TRUE;
196}
197
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000198/*=======================================================================*/
199/* Implementation of the getProperty, setProperty, and finalize methods */
200/* for the JavaScript class gwt_external_class. */
201/*=======================================================================*/
202
203static JSBool JS_DLL_CALLBACK gwt_external_getProperty(JSContext *cx,
204 JSObject *obj, jsval id, jsval *vp)
205{
206 Tracer tracer("gwt_external_getProperty");
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000207 JsRootedValue::ContextManager context(cx);
scottb@google.com6435ea22008-06-11 01:12:18 +0000208 if (*vp != JSVAL_VOID) {
209 // we setup the gwtOnLoad function as a property in GetScriptObject and
210 // do not maintain a copy of it anywhere. So, if there is a cached
211 // value for a property we must return it. As we never redefine any
212 // property values, this is safe. If we were going to keep this code
213 // around and add Toby's profiling code we would need to revisit this.
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000214 return JS_TRUE;
scottb@google.com6435ea22008-06-11 01:12:18 +0000215 }
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000216
217 if (!JSVAL_IS_STRING(id)) {
218 tracer.setFail("not a string");
219 return JS_FALSE;
220 }
221 JsStringWrap jsStr(cx, id);
222 tracer.log("obj=%08x, property=%.*s", obj, jsStr.length(), jsStr.bytes());
223
224// All this is for calling resolveReference which only returns void. So,
225// just replace this code to return void for now. TODO(jat): revisit when
scottb@google.com6435ea22008-06-11 01:12:18 +0000226// merging in Toby's code.
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000227#if 0
228 jstring jident = savedJNIEnv->NewString(jsStr.chars(), jsStr.length());
229 if (!jident || savedJNIEnv->ExceptionCheck()) {
230 tracer.setFail("can't create Java string ");
231 return JS_FALSE;
232 }
233 jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj));
234 jclass objClass = savedJNIEnv->GetObjectClass(externalObject);
235 if (!objClass || savedJNIEnv->ExceptionCheck()) {
236 tracer.setFail("can't find Java class for JS object");
237 return JS_FALSE;
238 }
239
240 jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "resolveReference",
241 "(Ljava/lang/String;)I");
242 if (!methodID || savedJNIEnv->ExceptionCheck()) {
243 tracer.setFail("exception getting method for int resolveReference(String)");
244 return JS_FALSE;
245 }
246 int retval = savedJNIEnv->CallIntMethod(externalObject, methodID, jident);
247 if (savedJNIEnv->ExceptionCheck()) {
248 tracer.setFail("exception calling int resolveReference(String)");
249 return JS_FALSE;
250 }
251 *vp = retval;
252#else
253 *vp = JSVAL_VOID;
254#endif
255 return JS_TRUE;
256}
257
258static void JS_DLL_CALLBACK gwt_external_finalize(JSContext *cx, JSObject *obj)
259{
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000260 // We don't need to push a context if all we do is DeleteGlobalRef
261 Tracer tracer("gwt_external_finalize", obj);
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000262 jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj));
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000263 if (externalObject) {
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000264 savedJNIEnv->DeleteGlobalRef(externalObject);
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000265 }
266 JS_FinalizeStub(cx, obj);
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000267}
268
269static JSBool JS_DLL_CALLBACK gwt_external_setProperty(JSContext *cx,
270 JSObject *obj, jsval id, jsval *vp)
271{
272 return JS_FALSE;
273}
274
275static JSClass gwt_external_class = {
276 "gwt_external_class", JSCLASS_HAS_PRIVATE,
277 JS_PropertyStub, JS_PropertyStub, gwt_external_getProperty,
278 gwt_external_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub,
279 gwt_external_finalize,
280 JSCLASS_NO_OPTIONAL_MEMBERS
281};
282
283NS_IMPL_ISUPPORTS1(ExternalWrapper, nsIScriptObjectOwner)
284
285/*
286 * Create a new LowLevelMoz.ExternalObject instance and a JavaScript object
287 * that refers to it, mapping the gwtOnLoad member function mapping to
288 * a Javascript method.
289 */
290NS_IMETHODIMP ExternalWrapper::GetScriptObject(nsIScriptContext *aContext,
291 void** aScriptObject)
292{
293 Tracer tracer("ExternalWrapper::GetScriptObject");
294 if (!aScriptObject) {
295 tracer.setFail("null script object pointer");
296 return NS_ERROR_INVALID_POINTER;
297 }
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000298 if (!jsWindowExternalObject) {
299 JSContext* cx = NS_REINTERPRET_CAST(JSContext*,
300 aContext->GetNativeContext());
301 if (!cx) {
302 tracer.setFail("can't get JSContext");
303 return NS_ERROR_UNEXPECTED;
304 }
305 JsRootedValue::ContextManager context(cx);
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000306 *aScriptObject = 0;
307
308 nsIScriptGlobalObject* globalObject = aContext->GetGlobalObject();
309 nsCOMPtr<nsIDOMWindow> domWindow(do_QueryInterface(globalObject));
310 if (!domWindow) {
311 tracer.setFail("can't get DOM window");
312 return NS_ERROR_UNEXPECTED;
313 }
314 nsCOMPtr<nsIDOMWindow> topWindow;
315 domWindow->GetTop(getter_AddRefs(topWindow));
316 if (!topWindow) {
317 tracer.setFail("Can't get top window");
318 return NS_ERROR_UNEXPECTED;
319 }
320
321 tracer.log("savedJNIEnv=%08x, llClass=%08x", unsigned(savedJNIEnv),
322 lowLevelMozClass);
323
324 jmethodID methodID = savedJNIEnv->GetStaticMethodID(lowLevelMozClass,
325 "createExternalObjectForDOMWindow",
326 "(I)Lcom/google/gwt/dev/shell/moz/LowLevelMoz$ExternalObject;");
327 if (!methodID || savedJNIEnv->ExceptionCheck()) {
328 tracer.setFail("Can't get createExternalObjectForDOMWindow method");
329 return NS_ERROR_UNEXPECTED;
330 }
331
332 jobject externalObject = savedJNIEnv->CallStaticObjectMethod(
333 lowLevelMozClass, methodID, NS_REINTERPRET_CAST(jint, topWindow.get()));
334 if (!externalObject || savedJNIEnv->ExceptionCheck()) {
335 tracer.setFail("createExternalObjectForDOMWindow failed");
336 return NS_ERROR_UNEXPECTED;
337 }
338 externalObject = savedJNIEnv->NewGlobalRef(externalObject);
339 if (!externalObject || savedJNIEnv->ExceptionCheck()) {
340 tracer.setFail("can't get GlobalRef for external object");
341 return NS_ERROR_UNEXPECTED;
342 }
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000343 JSObject* newObj = JS_NewObject(cx, &gwt_external_class, 0,
344 globalObject->GetGlobalJSObject());
345 if (!newObj) {
346 tracer.setFail("can't create new gwt_external_class object");
347 return NS_ERROR_OUT_OF_MEMORY;
348 }
349 if (!JS_SetPrivate(cx, newObj, externalObject)) {
350 savedJNIEnv->DeleteGlobalRef(externalObject);
351 tracer.setFail("can't store external object private reference");
352 return NS_ERROR_UNEXPECTED;
353 }
354
355 if (!JS_DefineFunction(cx, newObj, "gwtOnLoad", gwtOnLoad, 3,
356 JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
357 tracer.setFail("can't define gwtOnLoad function on JavaScript object");
358 return NS_ERROR_UNEXPECTED;
359 }
jat@google.come7f61a72008-12-24 00:45:21 +0000360 if (!JS_DefineFunction(cx, newObj, "initModule", initModule, 1,
361 JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) {
362 tracer.setFail("can't define initModule function on JavaScript object");
363 return NS_ERROR_UNEXPECTED;
364 }
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000365 jsWindowExternalObject = newObj;
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000366 }
367
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000368 *aScriptObject = jsWindowExternalObject;
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000369 return NS_OK;
370}
371
372NS_IMETHODIMP ExternalWrapper::SetScriptObject(void* aScriptObject)
373{
gwt.team.scottb63d38d02007-07-25 20:51:13 +0000374 jsWindowExternalObject = aScriptObject;
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000375 return NS_OK;
376}
377
378NS_IMPL_ISUPPORTS1(nsRpExternalFactory, nsIFactory)
379
380NS_IMETHODIMP nsRpExternalFactory::CreateInstance(nsISupports *aOuter,
381 const nsIID& aIID, void** aResult)
382{
383 Tracer tracer("nsRpExternalFactory::CreateInstance");
384 if (!aResult) {
385 tracer.setFail("null pointer for return value");
386 return NS_ERROR_INVALID_POINTER;
387 }
388 *aResult = NULL;
389
390 if (aOuter) {
391 tracer.setFail("aOuter is not null");
392 return NS_ERROR_NO_AGGREGATION;
393 }
394
395 nsISupports* object = new ExternalWrapper();
396 if (!object) {
397 tracer.setFail("can't create a new ExternalWrapper");
398 return NS_ERROR_OUT_OF_MEMORY;
399 }
scottb@google.com6435ea22008-06-11 01:12:18 +0000400
gwt.team.jatc85cb7f2007-03-02 20:13:50 +0000401 nsresult result = object->QueryInterface(aIID, aResult);
402 if (!*aResult || NS_FAILED(result)) {
403 tracer.setFail("ExternalWrapper::QueryInterface failed");
404 delete object;
405 }
406 return result;
407}
408
409NS_IMETHODIMP nsRpExternalFactory::LockFactory(PRBool lock)
410{
411 return NS_ERROR_NOT_IMPLEMENTED;
412}