d49e360900ab154f994281ad2d4e552c998e1dca
[WebKit-https.git] / JavaScriptCore / bindings / 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 "list.h"
31 #include "jni_jsobject.h"
32 #include "jni_runtime.h"
33 #include "jni_utility.h"
34 #include "runtime_object.h"
35 #include "runtime_root.h"
36
37 #include <CoreFoundation/CoreFoundation.h>
38 #include <assert.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 (!rootForImp(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                     JSObject *imp = jlong_to_impptr(nativeHandle);
130                     if (findReferenceDictionary(imp) == 0) {
131                         // We may have received a finalize method call from the VM 
132                         // AFTER removing our last reference to the Java instance.
133                         JS_LOG ("finalize called on instance we have already removed.\n");
134                     }
135                     else {
136                         JavaJSObject(nativeHandle).finalize();
137                     }
138                     break;
139                 }
140                 
141                 default: {
142                     fprintf (stderr, "%s:  invalid JavaScript call\n", __PRETTY_FUNCTION__);
143                 }
144             }
145         }
146         context->result = result;
147     }
148
149     return result;
150 }
151
152
153 JavaJSObject::JavaJSObject(jlong nativeJSObject)
154 {
155     _imp = jlong_to_impptr(nativeJSObject);
156     
157     // If we are unable to cast the nativeJSObject to an JSObject something is
158     // terribly wrong.
159     assert (_imp != 0);
160     
161     _root = rootForImp(_imp);
162     
163     // If we can't find the root for the object something is terribly wrong.
164     assert (_root != 0);
165 }
166
167
168 jobject JavaJSObject::call(jstring methodName, jobjectArray args) const
169 {
170     JS_LOG ("methodName = %s\n", JavaString(methodName).UTF8String());
171
172     // Lookup the function object.
173     ExecState *exec = _root->interpreter()->globalExec();
174     JSLock lock;
175     
176     Identifier identifier(JavaString(methodName).ustring());
177     JSValue *func = _imp->get (exec, identifier);
178     if (func->isUndefinedOrNull()) {
179         // Maybe throw an exception here?
180         return 0;
181     }
182
183     // Call the function object.
184     JSObject *funcImp = static_cast<JSObject*>(func);
185     JSObject *thisObj = const_cast<JSObject*>(_imp);
186     List argList = listFromJArray(args);
187     JSValue *result = funcImp->call(exec, thisObj, argList);
188
189     return convertValueToJObject(result);
190 }
191
192 jobject JavaJSObject::eval(jstring script) const
193 {
194     JS_LOG ("script = %s\n", JavaString(script).UTF8String());
195     
196     JSObject *thisObj = const_cast<JSObject*>(_imp);
197     JSValue *result;
198     
199     JSLock lock;
200     
201     Completion completion = _root->interpreter()->evaluate(UString(), 0, JavaString(script).ustring(),thisObj);
202     ComplType type = completion.complType();
203     
204     if (type == Normal) {
205         result = completion.value();
206         if (!result)
207             result = jsUndefined();
208     } else
209         result = jsUndefined();
210     
211     return convertValueToJObject (result);
212 }
213
214 jobject JavaJSObject::getMember(jstring memberName) const
215 {
216     JS_LOG ("(%p) memberName = %s\n", _imp, JavaString(memberName).UTF8String());
217
218     ExecState *exec = _root->interpreter()->globalExec();
219     
220     JSLock lock;
221     JSValue *result = _imp->get (exec, Identifier (JavaString(memberName).ustring()));
222
223     return convertValueToJObject(result);
224 }
225
226 void JavaJSObject::setMember(jstring memberName, jobject value) const
227 {
228     JS_LOG ("memberName = %s, value = %p\n", JavaString(memberName).UTF8String(), value);
229     ExecState *exec = _root->interpreter()->globalExec();
230     JSLock lock;
231     _imp->put(exec, Identifier (JavaString(memberName).ustring()), convertJObjectToValue(value));
232 }
233
234
235 void JavaJSObject::removeMember(jstring memberName) const
236 {
237     JS_LOG ("memberName = %s\n", JavaString(memberName).UTF8String());
238
239     ExecState *exec = _root->interpreter()->globalExec();
240     JSLock lock;
241     _imp->deleteProperty(exec, Identifier (JavaString(memberName).ustring()));
242 }
243
244
245 jobject JavaJSObject::getSlot(jint index) const
246 {
247     JS_LOG ("index = %ld\n", index);
248
249     ExecState *exec = _root->interpreter()->globalExec();
250
251     JSLock lock;
252     JSValue *result = _imp->get (exec, (unsigned)index);
253
254     return convertValueToJObject(result);
255 }
256
257
258 void JavaJSObject::setSlot(jint index, jobject value) const
259 {
260     JS_LOG ("index = %ld, value = %p\n", index, value);
261
262     ExecState *exec = _root->interpreter()->globalExec();
263     JSLock lock;
264     _imp->put(exec, (unsigned)index, convertJObjectToValue(value));
265 }
266
267
268 jstring JavaJSObject::toString() const
269 {
270     JS_LOG ("\n");
271     
272     JSLock lock;
273     JSObject *thisObj = const_cast<JSObject*>(_imp);
274     ExecState *exec = _root->interpreter()->globalExec();
275     
276     return (jstring)convertValueToJValue (exec, thisObj, object_type, "java.lang.String").l;
277 }
278
279 void JavaJSObject::finalize() const
280 {
281     JS_LOG ("\n");
282
283     removeNativeReference (_imp);
284 }
285
286 // We're either creating a 'Root' object (via a call to JavaJSObject.getWindow()), or
287 // another JavaJSObject.
288 jlong JavaJSObject::createNative(jlong nativeHandle)
289 {
290     JS_LOG ("nativeHandle = %d\n", (int)nativeHandle);
291
292     if (nativeHandle == UndefinedHandle)
293         return nativeHandle;
294     else if (rootForImp(jlong_to_impptr(nativeHandle))){
295         return nativeHandle;
296     }
297
298     FindRootObjectForNativeHandleFunctionPtr aFunc = RootObject::findRootObjectForNativeHandleFunction();
299     if (aFunc) {
300         Bindings::RootObject *root = aFunc(jlong_to_ptr(nativeHandle));
301         // If root is !NULL We must have been called via netscape.javascript.JavaJSObject.getWindow(),
302         // otherwise we are being called after creating a JavaJSObject in
303         // JavaJSObject::convertValueToJObject().
304         if (root) {
305             addNativeReference (root, root->rootObjectImp());        
306             return ptr_to_jlong(root->rootObjectImp());
307         }
308         else {
309             return nativeHandle;
310         }
311     }
312     
313     return ptr_to_jlong(0);
314 }
315
316 jobject JavaJSObject::convertValueToJObject (JSValue *value) const
317 {
318     ExecState *exec = _root->interpreter()->globalExec();
319     JNIEnv *env = getJNIEnv();
320     jobject result = 0;
321     
322     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
323     // figure 22-5.
324     // number -> java.lang.Double
325     // string -> java.lang.String
326     // boolean -> java.lang.Boolean
327     // Java instance -> Java instance
328     // Everything else -> JavaJSObject
329     
330     JSType type = value->type();
331     if (type == NumberType) {
332         jclass JSObjectClass = env->FindClass ("java/lang/Double");
333         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(D)V");
334         if (constructorID != NULL) {
335             result = env->NewObject (JSObjectClass, constructorID, (jdouble)value->toNumber(exec));
336         }
337     }
338     else if (type == StringType) {
339         UString stringValue = value->toString(exec);
340         JNIEnv *env = getJNIEnv();
341         result = env->NewString ((const jchar *)stringValue.data(), stringValue.size());
342     }
343     else if (type == BooleanType) {
344         jclass JSObjectClass = env->FindClass ("java/lang/Boolean");
345         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(Z)V");
346         if (constructorID != NULL) {
347             result = env->NewObject (JSObjectClass, constructorID, (jboolean)value->toBoolean(exec));
348         }
349     }
350     else {
351         // Create a JavaJSObject.
352         jlong nativeHandle;
353         
354         if (type == ObjectType){
355             JSObject *imp = static_cast<JSObject*>(value);
356             
357             // We either have a wrapper around a Java instance or a JavaScript
358             // object.  If we have a wrapper around a Java instance, return that
359             // instance, otherwise create a new Java JavaJSObject with the JSObject*
360             // as it's nativeHandle.
361             if (imp->classInfo() && strcmp(imp->classInfo()->className, "RuntimeObject") == 0) {
362                 RuntimeObjectImp *runtimeImp = static_cast<RuntimeObjectImp*>(value);
363                 Bindings::JavaInstance *runtimeInstance = static_cast<Bindings::JavaInstance *>(runtimeImp->getInternalInstance());
364                 return runtimeInstance->javaInstance();
365             }
366             else {
367                 nativeHandle = ptr_to_jlong(imp);
368                 
369                 // Bump our 'meta' reference count for the imp.  We maintain the reference
370                 // until either finalize is called or the applet shuts down.
371                 addNativeReference (_root, imp);
372             }
373         }
374         // All other types will result in an undefined object.
375         else {
376             nativeHandle = UndefinedHandle;
377         }
378         
379         // Now create the Java JavaJSObject.  Look for the JavaJSObject in it's new (Tiger)
380         // location and in the original Java 1.4.2 location.
381         jclass JSObjectClass;
382         
383         JSObjectClass = env->FindClass ("sun/plugin/javascript/webkit/JSObject");
384         if (!JSObjectClass) {
385             env->ExceptionDescribe();
386             env->ExceptionClear();
387             JSObjectClass = env->FindClass ("apple/applet/JSObject");
388         }
389             
390         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(J)V");
391         if (constructorID != NULL) {
392             result = env->NewObject (JSObjectClass, constructorID, nativeHandle);
393         }
394     }
395     
396     return result;
397 }
398
399 JSValue *JavaJSObject::convertJObjectToValue (jobject theObject) const
400 {
401     // Instances of netscape.javascript.JSObject get converted back to
402     // JavaScript objects.  All other objects are wrapped.  It's not
403     // possible to pass primitive types from the Java to JavaScript.
404     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
405     // figure 22-4.
406     jobject classOfInstance = callJNIObjectMethod(theObject, "getClass", "()Ljava/lang/Class;");
407     jstring className = (jstring)callJNIObjectMethod(classOfInstance, "getName", "()Ljava/lang/String;");
408     
409     // Only the sun.plugin.javascript.webkit.JSObject has a member called nativeJSObject. This class is
410     // created above to wrap internal browser objects. The constructor of this class takes the native
411     // pointer and stores it in this object, so that it can be retrieved below.
412     if (strcmp(Bindings::JavaString(className).UTF8String(), "sun.plugin.javascript.webkit.JSObject") == 0) {
413         // Pull the nativeJSObject value from the Java instance.  This is a
414         // pointer to the JSObject.
415         JNIEnv *env = getJNIEnv();
416         jfieldID fieldID = env->GetFieldID((jclass)classOfInstance, "nativeJSObject", "J");
417         if (fieldID == NULL) {
418             return jsUndefined();
419         }
420         jlong nativeHandle = env->GetLongField(theObject, fieldID);
421         if (nativeHandle == UndefinedHandle) {
422             return jsUndefined();
423         }
424         JSObject *imp = static_cast<JSObject*>(jlong_to_impptr(nativeHandle));
425         return imp;
426     }
427
428     JSLock lock;
429     RuntimeObjectImp *newImp = new RuntimeObjectImp(new Bindings::JavaInstance (theObject, _root));
430
431     return newImp;
432 }
433
434 List JavaJSObject::listFromJArray(jobjectArray jArray) const
435 {
436     JNIEnv *env = getJNIEnv();
437     int i, numObjects = jArray ? env->GetArrayLength (jArray) : 0;
438     List aList;
439     
440     for (i = 0; i < numObjects; i++) {
441         jobject anObject = env->GetObjectArrayElement ((jobjectArray)jArray, i);
442         if (anObject) {
443             aList.append (convertJObjectToValue(anObject));
444             env->DeleteLocalRef (anObject);
445         }
446         else {
447             env->ExceptionDescribe();
448             env->ExceptionClear();
449         }
450     }
451     return aList;
452 }
453
454 extern "C" {
455
456 jlong KJS_JSCreateNativeJSObject (JNIEnv*, jclass, jstring, jlong nativeHandle, jboolean)
457 {
458     JSObjectCallContext context;
459     context.type = CreateNative;
460     context.nativeHandle = nativeHandle;
461     return JavaJSObject::invoke (&context).j;
462 }
463
464 void KJS_JSObject_JSFinalize (JNIEnv*, jclass, jlong nativeHandle)
465 {
466     JSObjectCallContext context;
467     context.type = Finalize;
468     context.nativeHandle = nativeHandle;
469     JavaJSObject::invoke (&context);
470 }
471
472 jobject KJS_JSObject_JSObjectCall (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring methodName, jobjectArray args, jboolean)
473 {
474     JSObjectCallContext context;
475     context.type = Call;
476     context.nativeHandle = nativeHandle;
477     context.string = methodName;
478     context.args = args;
479     return JavaJSObject::invoke (&context).l;
480 }
481
482 jobject KJS_JSObject_JSObjectEval (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jscript, jboolean)
483 {
484     JSObjectCallContext context;
485     context.type = Eval;
486     context.nativeHandle = nativeHandle;
487     context.string = jscript;
488     return JavaJSObject::invoke (&context).l;
489 }
490
491 jobject KJS_JSObject_JSObjectGetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
492 {
493     JSObjectCallContext context;
494     context.type = GetMember;
495     context.nativeHandle = nativeHandle;
496     context.string = jname;
497     return JavaJSObject::invoke (&context).l;
498 }
499
500 void KJS_JSObject_JSObjectSetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jobject value, jboolean)
501 {
502     JSObjectCallContext context;
503     context.type = SetMember;
504     context.nativeHandle = nativeHandle;
505     context.string = jname;
506     context.value = value;
507     JavaJSObject::invoke (&context);
508 }
509
510 void KJS_JSObject_JSObjectRemoveMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
511 {
512     JSObjectCallContext context;
513     context.type = RemoveMember;
514     context.nativeHandle = nativeHandle;
515     context.string = jname;
516     JavaJSObject::invoke (&context);
517 }
518
519 jobject KJS_JSObject_JSObjectGetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jboolean)
520 {
521     JSObjectCallContext context;
522     context.type = GetSlot;
523     context.nativeHandle = nativeHandle;
524     context.index = jindex;
525     return JavaJSObject::invoke (&context).l;
526 }
527
528 void KJS_JSObject_JSObjectSetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jobject value, jboolean)
529 {
530     JSObjectCallContext context;
531     context.type = SetSlot;
532     context.nativeHandle = nativeHandle;
533     context.index = jindex;
534     context.value = value;
535     JavaJSObject::invoke (&context);
536 }
537
538 jstring KJS_JSObject_JSObjectToString (JNIEnv*, jclass, jlong nativeHandle)
539 {
540     JSObjectCallContext context;
541     context.type = ToString;
542     context.nativeHandle = nativeHandle;
543     return (jstring)JavaJSObject::invoke (&context).l;
544 }
545
546 }