Reviewed by Sam.
[WebKit-https.git] / WebCore / bridge / jni / jni_jsobject.cpp
1 /*
2  * Copyright (C) 2003 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25 #include "config.h"
26 #include "jni_jsobject.h"
27
28 #include "jni_runtime.h"
29 #include "jni_utility.h"
30 #include "runtime_object.h"
31 #include "runtime_root.h"
32 #include <CoreFoundation/CoreFoundation.h>
33 #include <kjs/ExecState.h>
34 #include <kjs/JSGlobalObject.h>
35 #include <kjs/interpreter.h>
36
37 #include <wtf/Assertions.h>
38
39 using namespace KJS::Bindings;
40 using namespace KJS;
41
42 #ifdef NDEBUG
43 #define JS_LOG(formatAndArgs...) ((void)0)
44 #else
45 #define JS_LOG(formatAndArgs...) { \
46     fprintf (stderr, "%s(%p,%p):  ", __PRETTY_FUNCTION__, RootObject::runLoop(), CFRunLoopGetCurrent()); \
47     fprintf(stderr, formatAndArgs); \
48 }
49 #endif
50
51 #define UndefinedHandle 1
52
53 static bool isJavaScriptThread()
54 {
55     return (RootObject::runLoop() == CFRunLoopGetCurrent());
56 }
57
58 jvalue JavaJSObject::invoke (JSObjectCallContext *context)
59 {
60     jvalue result;
61
62     bzero ((void *)&result, sizeof(jvalue));
63     
64     if (!isJavaScriptThread()) {        
65         // Send the call context to the thread that is allowed to
66         // call JavaScript.
67         RootObject::dispatchToJavaScriptThread(context);
68         result = context->result;
69     }
70     else {
71         jlong nativeHandle = context->nativeHandle;
72         if (nativeHandle == UndefinedHandle || nativeHandle == 0) {
73             return result;
74         }
75
76         if (context->type == CreateNative) {
77             result.j = JavaJSObject::createNative(nativeHandle);
78         }
79         else {
80             JSObject *imp = jlong_to_impptr(nativeHandle);
81             if (!findProtectingRootObject(imp)) {
82                 fprintf (stderr, "%s:%d:  Attempt to access JavaScript from destroyed applet, type %d.\n", __FILE__, __LINE__, context->type);
83                 return result;
84             }
85
86             switch (context->type){            
87                 case Call: {
88                     result.l = JavaJSObject(nativeHandle).call(context->string, context->args);
89                     break;
90                 }
91                 
92                 case Eval: {
93                     result.l = JavaJSObject(nativeHandle).eval(context->string);
94                     break;
95                 }
96             
97                 case GetMember: {
98                     result.l = JavaJSObject(nativeHandle).getMember(context->string);
99                     break;
100                 }
101                 
102                 case SetMember: {
103                     JavaJSObject(nativeHandle).setMember(context->string, context->value);
104                     break;
105                 }
106                 
107                 case RemoveMember: {
108                     JavaJSObject(nativeHandle).removeMember(context->string);
109                     break;
110                 }
111             
112                 case GetSlot: {
113                     result.l = JavaJSObject(nativeHandle).getSlot(context->index);
114                     break;
115                 }
116                 
117                 case SetSlot: {
118                     JavaJSObject(nativeHandle).setSlot(context->index, context->value);
119                     break;
120                 }
121             
122                 case ToString: {
123                     result.l = (jobject) JavaJSObject(nativeHandle).toString();
124                     break;
125                 }
126     
127                 case Finalize: {
128                     JavaJSObject(nativeHandle).finalize();
129                     break;
130                 }
131                 
132                 default: {
133                     fprintf (stderr, "%s:  invalid JavaScript call\n", __PRETTY_FUNCTION__);
134                 }
135             }
136         }
137         context->result = result;
138     }
139
140     return result;
141 }
142
143
144 JavaJSObject::JavaJSObject(jlong nativeJSObject)
145 {
146     _imp = jlong_to_impptr(nativeJSObject);
147     
148     ASSERT(_imp);
149     _rootObject = findProtectingRootObject(_imp);
150     ASSERT(_rootObject);
151 }
152
153 RootObject* JavaJSObject::rootObject() const
154
155     return _rootObject && _rootObject->isValid() ? _rootObject.get() : 0; 
156 }
157
158 jobject JavaJSObject::call(jstring methodName, jobjectArray args) const
159 {
160     JS_LOG ("methodName = %s\n", JavaString(methodName).UTF8String());
161
162     RootObject* rootObject = this->rootObject();
163     if (!rootObject)
164         return 0;
165     
166     // Lookup the function object.
167     ExecState* exec = rootObject->globalObject()->globalExec();
168     JSLock lock;
169     
170     Identifier identifier(JavaString(methodName).ustring());
171     JSValue *func = _imp->get (exec, identifier);
172     if (func->isUndefinedOrNull())
173         return 0;
174
175     // Call the function object.
176     JSObject *funcImp = static_cast<JSObject*>(func);
177     JSObject *thisObj = const_cast<JSObject*>(_imp);
178     List argList;
179     getListFromJArray(args, argList);
180     rootObject->globalObject()->startTimeoutCheck();
181     JSValue *result = funcImp->call(exec, thisObj, argList);
182     rootObject->globalObject()->stopTimeoutCheck();
183
184     return convertValueToJObject(result);
185 }
186
187 jobject JavaJSObject::eval(jstring script) const
188 {
189     JS_LOG ("script = %s\n", JavaString(script).UTF8String());
190     
191     JSObject *thisObj = const_cast<JSObject*>(_imp);
192     JSValue *result;
193     
194     JSLock lock;
195     
196     RootObject* rootObject = this->rootObject();
197     if (!rootObject)
198         return 0;
199
200     rootObject->globalObject()->startTimeoutCheck();
201     Completion completion = Interpreter::evaluate(rootObject->globalObject()->globalExec(), UString(), 0, JavaString(script).ustring(),thisObj);
202     rootObject->globalObject()->stopTimeoutCheck();
203     ComplType type = completion.complType();
204     
205     if (type == Normal) {
206         result = completion.value();
207         if (!result)
208             result = jsUndefined();
209     } else
210         result = jsUndefined();
211     
212     return convertValueToJObject (result);
213 }
214
215 jobject JavaJSObject::getMember(jstring memberName) const
216 {
217     JS_LOG ("(%p) memberName = %s\n", _imp, JavaString(memberName).UTF8String());
218
219     RootObject* rootObject = this->rootObject();
220     if (!rootObject)
221         return 0;
222
223     ExecState* exec = rootObject->globalObject()->globalExec();
224     
225     JSLock lock;
226     JSValue *result = _imp->get (exec, Identifier (JavaString(memberName).ustring()));
227
228     return convertValueToJObject(result);
229 }
230
231 void JavaJSObject::setMember(jstring memberName, jobject value) const
232 {
233     JS_LOG ("memberName = %s, value = %p\n", JavaString(memberName).UTF8String(), value);
234
235     RootObject* rootObject = this->rootObject();
236     if (!rootObject)
237         return;
238
239     ExecState* exec = rootObject->globalObject()->globalExec();
240     JSLock lock;
241     _imp->put(exec, Identifier (JavaString(memberName).ustring()), convertJObjectToValue(value));
242 }
243
244
245 void JavaJSObject::removeMember(jstring memberName) const
246 {
247     JS_LOG ("memberName = %s\n", JavaString(memberName).UTF8String());
248
249     RootObject* rootObject = this->rootObject();
250     if (!rootObject)
251         return;
252
253     ExecState* exec = rootObject->globalObject()->globalExec();
254     JSLock lock;
255     _imp->deleteProperty(exec, Identifier (JavaString(memberName).ustring()));
256 }
257
258
259 jobject JavaJSObject::getSlot(jint index) const
260 {
261 #ifdef __LP64__
262     JS_LOG ("index = %d\n", index);
263 #else
264     JS_LOG ("index = %ld\n", index);
265 #endif
266
267     RootObject* rootObject = this->rootObject();
268     if (!rootObject)
269         return 0;
270
271     ExecState* exec = rootObject->globalObject()->globalExec();
272
273     JSLock lock;
274     JSValue *result = _imp->get (exec, (unsigned)index);
275
276     return convertValueToJObject(result);
277 }
278
279
280 void JavaJSObject::setSlot(jint index, jobject value) const
281 {
282 #ifdef __LP64__
283     JS_LOG ("index = %d, value = %p\n", index, value);
284 #else
285     JS_LOG ("index = %ld, value = %p\n", index, value);
286 #endif
287
288     RootObject* rootObject = this->rootObject();
289     if (!rootObject)
290         return;
291
292     ExecState* exec = rootObject->globalObject()->globalExec();
293     JSLock lock;
294     _imp->put(exec, (unsigned)index, convertJObjectToValue(value));
295 }
296
297
298 jstring JavaJSObject::toString() const
299 {
300     JS_LOG ("\n");
301     
302     RootObject* rootObject = this->rootObject();
303     if (!rootObject)
304         return 0;
305
306     JSLock lock;
307     JSObject *thisObj = const_cast<JSObject*>(_imp);
308     ExecState* exec = rootObject->globalObject()->globalExec();
309     
310     return (jstring)convertValueToJValue (exec, thisObj, object_type, "java.lang.String").l;
311 }
312
313 void JavaJSObject::finalize() const
314 {
315     if (RootObject* rootObject = this->rootObject())
316         rootObject->gcUnprotect(_imp);
317 }
318
319 // We're either creating a 'Root' object (via a call to JavaJSObject.getWindow()), or
320 // another JavaJSObject.
321 jlong JavaJSObject::createNative(jlong nativeHandle)
322 {
323     JS_LOG ("nativeHandle = %d\n", (int)nativeHandle);
324
325     if (nativeHandle == UndefinedHandle)
326         return nativeHandle;
327
328     if (findProtectingRootObject(jlong_to_impptr(nativeHandle)))
329         return nativeHandle;
330
331     CreateRootObjectFunction createRootObject = RootObject::createRootObject();
332     if (!createRootObject)
333         return ptr_to_jlong(0);
334
335     RefPtr<RootObject> rootObject = createRootObject(jlong_to_ptr(nativeHandle));
336
337     // If rootObject is !NULL We must have been called via netscape.javascript.JavaJSObject.getWindow(),
338     // otherwise we are being called after creating a JavaJSObject in
339     // JavaJSObject::convertValueToJObject().
340     if (rootObject) {
341         JSObject* globalObject = rootObject->globalObject();
342         // We call gcProtect here to get the object into the root object's "protect set" which
343         // is used to test if a native handle is valid as well as getting the root object given the handle.
344         rootObject->gcProtect(globalObject);
345         return ptr_to_jlong(globalObject);
346     }
347     
348     return nativeHandle;
349 }
350
351 jobject JavaJSObject::convertValueToJObject (JSValue *value) const
352 {
353     JSLock lock;
354     
355     RootObject* rootObject = this->rootObject();
356     if (!rootObject)
357         return 0;
358
359     ExecState* exec = rootObject->globalObject()->globalExec();
360     JNIEnv *env = getJNIEnv();
361     jobject result = 0;
362     
363     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
364     // figure 22-5.
365     // number -> java.lang.Double
366     // string -> java.lang.String
367     // boolean -> java.lang.Boolean
368     // Java instance -> Java instance
369     // Everything else -> JavaJSObject
370     
371     JSType type = value->type();
372     if (type == NumberType) {
373         jclass JSObjectClass = env->FindClass ("java/lang/Double");
374         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(D)V");
375         if (constructorID != NULL) {
376             result = env->NewObject (JSObjectClass, constructorID, (jdouble)value->toNumber(exec));
377         }
378     }
379     else if (type == StringType) {
380         UString stringValue = value->toString(exec);
381         JNIEnv *env = getJNIEnv();
382         result = env->NewString ((const jchar *)stringValue.data(), stringValue.size());
383     }
384     else if (type == BooleanType) {
385         jclass JSObjectClass = env->FindClass ("java/lang/Boolean");
386         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(Z)V");
387         if (constructorID != NULL) {
388             result = env->NewObject (JSObjectClass, constructorID, (jboolean)value->toBoolean(exec));
389         }
390     }
391     else {
392         // Create a JavaJSObject.
393         jlong nativeHandle;
394         
395         if (type == ObjectType){
396             JSObject *imp = static_cast<JSObject*>(value);
397             
398             // We either have a wrapper around a Java instance or a JavaScript
399             // object.  If we have a wrapper around a Java instance, return that
400             // instance, otherwise create a new Java JavaJSObject with the JSObject*
401             // as it's nativeHandle.
402             if (imp->classInfo() && strcmp(imp->classInfo()->className, "RuntimeObject") == 0) {
403                 RuntimeObjectImp *runtimeImp = static_cast<RuntimeObjectImp*>(value);
404                 JavaInstance *runtimeInstance = static_cast<JavaInstance *>(runtimeImp->getInternalInstance());
405                 if (!runtimeInstance)
406                     return 0;
407                 
408                 return runtimeInstance->javaInstance();
409             }
410             else {
411                 nativeHandle = ptr_to_jlong(imp);
412                 rootObject->gcProtect(imp);
413             }
414         }
415         // All other types will result in an undefined object.
416         else {
417             nativeHandle = UndefinedHandle;
418         }
419         
420         // Now create the Java JavaJSObject.  Look for the JavaJSObject in it's new (Tiger)
421         // location and in the original Java 1.4.2 location.
422         jclass JSObjectClass;
423         
424         JSObjectClass = env->FindClass ("sun/plugin/javascript/webkit/JSObject");
425         if (!JSObjectClass) {
426             env->ExceptionDescribe();
427             env->ExceptionClear();
428             JSObjectClass = env->FindClass ("apple/applet/JSObject");
429         }
430             
431         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(J)V");
432         if (constructorID != NULL) {
433             result = env->NewObject (JSObjectClass, constructorID, nativeHandle);
434         }
435     }
436     
437     return result;
438 }
439
440 JSValue *JavaJSObject::convertJObjectToValue (jobject theObject) const
441 {
442     // Instances of netscape.javascript.JSObject get converted back to
443     // JavaScript objects.  All other objects are wrapped.  It's not
444     // possible to pass primitive types from the Java to JavaScript.
445     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
446     // figure 22-4.
447     jobject classOfInstance = callJNIObjectMethod(theObject, "getClass", "()Ljava/lang/Class;");
448     jstring className = (jstring)callJNIObjectMethod(classOfInstance, "getName", "()Ljava/lang/String;");
449     
450     // Only the sun.plugin.javascript.webkit.JSObject has a member called nativeJSObject. This class is
451     // created above to wrap internal browser objects. The constructor of this class takes the native
452     // pointer and stores it in this object, so that it can be retrieved below.
453     if (strcmp(JavaString(className).UTF8String(), "sun.plugin.javascript.webkit.JSObject") == 0) {
454         // Pull the nativeJSObject value from the Java instance.  This is a
455         // pointer to the JSObject.
456         JNIEnv *env = getJNIEnv();
457         jfieldID fieldID = env->GetFieldID((jclass)classOfInstance, "nativeJSObject", "J");
458         if (fieldID == NULL) {
459             return jsUndefined();
460         }
461         jlong nativeHandle = env->GetLongField(theObject, fieldID);
462         if (nativeHandle == UndefinedHandle) {
463             return jsUndefined();
464         }
465         JSObject *imp = static_cast<JSObject*>(jlong_to_impptr(nativeHandle));
466         return imp;
467     }
468
469     JSLock lock;
470     JavaInstance* javaInstance = new JavaInstance(theObject, _rootObject);
471     return KJS::Bindings::Instance::createRuntimeObject(javaInstance);
472 }
473
474 void JavaJSObject::getListFromJArray(jobjectArray jArray, List& list) const
475 {
476     JNIEnv *env = getJNIEnv();
477     int i, numObjects = jArray ? env->GetArrayLength (jArray) : 0;
478     
479     for (i = 0; i < numObjects; i++) {
480         jobject anObject = env->GetObjectArrayElement ((jobjectArray)jArray, i);
481         if (anObject) {
482             list.append(convertJObjectToValue(anObject));
483             env->DeleteLocalRef (anObject);
484         }
485         else {
486             env->ExceptionDescribe();
487             env->ExceptionClear();
488         }
489     }
490 }
491
492 extern "C" {
493
494 jlong KJS_JSCreateNativeJSObject (JNIEnv*, jclass, jstring, jlong nativeHandle, jboolean)
495 {
496     JSObjectCallContext context;
497     context.type = CreateNative;
498     context.nativeHandle = nativeHandle;
499     return JavaJSObject::invoke (&context).j;
500 }
501
502 void KJS_JSObject_JSFinalize (JNIEnv*, jclass, jlong nativeHandle)
503 {
504     JSObjectCallContext context;
505     context.type = Finalize;
506     context.nativeHandle = nativeHandle;
507     JavaJSObject::invoke (&context);
508 }
509
510 jobject KJS_JSObject_JSObjectCall (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring methodName, jobjectArray args, jboolean)
511 {
512     JSObjectCallContext context;
513     context.type = Call;
514     context.nativeHandle = nativeHandle;
515     context.string = methodName;
516     context.args = args;
517     return JavaJSObject::invoke (&context).l;
518 }
519
520 jobject KJS_JSObject_JSObjectEval (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jscript, jboolean)
521 {
522     JSObjectCallContext context;
523     context.type = Eval;
524     context.nativeHandle = nativeHandle;
525     context.string = jscript;
526     return JavaJSObject::invoke (&context).l;
527 }
528
529 jobject KJS_JSObject_JSObjectGetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
530 {
531     JSObjectCallContext context;
532     context.type = GetMember;
533     context.nativeHandle = nativeHandle;
534     context.string = jname;
535     return JavaJSObject::invoke (&context).l;
536 }
537
538 void KJS_JSObject_JSObjectSetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jobject value, jboolean)
539 {
540     JSObjectCallContext context;
541     context.type = SetMember;
542     context.nativeHandle = nativeHandle;
543     context.string = jname;
544     context.value = value;
545     JavaJSObject::invoke (&context);
546 }
547
548 void KJS_JSObject_JSObjectRemoveMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
549 {
550     JSObjectCallContext context;
551     context.type = RemoveMember;
552     context.nativeHandle = nativeHandle;
553     context.string = jname;
554     JavaJSObject::invoke (&context);
555 }
556
557 jobject KJS_JSObject_JSObjectGetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jboolean)
558 {
559     JSObjectCallContext context;
560     context.type = GetSlot;
561     context.nativeHandle = nativeHandle;
562     context.index = jindex;
563     return JavaJSObject::invoke (&context).l;
564 }
565
566 void KJS_JSObject_JSObjectSetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jobject value, jboolean)
567 {
568     JSObjectCallContext context;
569     context.type = SetSlot;
570     context.nativeHandle = nativeHandle;
571     context.index = jindex;
572     context.value = value;
573     JavaJSObject::invoke (&context);
574 }
575
576 jstring KJS_JSObject_JSObjectToString (JNIEnv*, jclass, jlong nativeHandle)
577 {
578     JSObjectCallContext context;
579     context.type = ToString;
580     context.nativeHandle = nativeHandle;
581     return (jstring)JavaJSObject::invoke (&context).l;
582 }
583
584 }