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