2006-07-23 Geoffrey Garen <ggaren@apple.com>
[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 #if __LP64__
248     JS_LOG ("index = %d\n", index);
249 #else
250     JS_LOG ("index = %ld\n", index);
251 #endif
252
253     ExecState *exec = _root->interpreter()->globalExec();
254
255     JSLock lock;
256     JSValue *result = _imp->get (exec, (unsigned)index);
257
258     return convertValueToJObject(result);
259 }
260
261
262 void JavaJSObject::setSlot(jint index, jobject value) const
263 {
264 #if __LP64__
265     JS_LOG ("index = %d, value = %p\n", index, value);
266 #else
267     JS_LOG ("index = %ld, value = %p\n", index, value);
268 #endif
269
270     ExecState *exec = _root->interpreter()->globalExec();
271     JSLock lock;
272     _imp->put(exec, (unsigned)index, convertJObjectToValue(value));
273 }
274
275
276 jstring JavaJSObject::toString() const
277 {
278     JS_LOG ("\n");
279     
280     JSLock lock;
281     JSObject *thisObj = const_cast<JSObject*>(_imp);
282     ExecState *exec = _root->interpreter()->globalExec();
283     
284     return (jstring)convertValueToJValue (exec, thisObj, object_type, "java.lang.String").l;
285 }
286
287 void JavaJSObject::finalize() const
288 {
289     JS_LOG ("\n");
290
291     removeNativeReference (_imp);
292 }
293
294 // We're either creating a 'Root' object (via a call to JavaJSObject.getWindow()), or
295 // another JavaJSObject.
296 jlong JavaJSObject::createNative(jlong nativeHandle)
297 {
298     JS_LOG ("nativeHandle = %d\n", (int)nativeHandle);
299
300     if (nativeHandle == UndefinedHandle)
301         return nativeHandle;
302     else if (rootForImp(jlong_to_impptr(nativeHandle))){
303         return nativeHandle;
304     }
305
306     FindRootObjectForNativeHandleFunctionPtr aFunc = RootObject::findRootObjectForNativeHandleFunction();
307     if (aFunc) {
308         Bindings::RootObject *root = aFunc(jlong_to_ptr(nativeHandle));
309         // If root is !NULL We must have been called via netscape.javascript.JavaJSObject.getWindow(),
310         // otherwise we are being called after creating a JavaJSObject in
311         // JavaJSObject::convertValueToJObject().
312         if (root) {
313             addNativeReference (root, root->rootObjectImp());        
314             return ptr_to_jlong(root->rootObjectImp());
315         }
316         else {
317             return nativeHandle;
318         }
319     }
320     
321     return ptr_to_jlong(0);
322 }
323
324 jobject JavaJSObject::convertValueToJObject (JSValue *value) const
325 {
326     ExecState *exec = _root->interpreter()->globalExec();
327     JNIEnv *env = getJNIEnv();
328     jobject result = 0;
329     
330     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
331     // figure 22-5.
332     // number -> java.lang.Double
333     // string -> java.lang.String
334     // boolean -> java.lang.Boolean
335     // Java instance -> Java instance
336     // Everything else -> JavaJSObject
337     
338     JSType type = value->type();
339     if (type == NumberType) {
340         jclass JSObjectClass = env->FindClass ("java/lang/Double");
341         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(D)V");
342         if (constructorID != NULL) {
343             result = env->NewObject (JSObjectClass, constructorID, (jdouble)value->toNumber(exec));
344         }
345     }
346     else if (type == StringType) {
347         UString stringValue = value->toString(exec);
348         JNIEnv *env = getJNIEnv();
349         result = env->NewString ((const jchar *)stringValue.data(), stringValue.size());
350     }
351     else if (type == BooleanType) {
352         jclass JSObjectClass = env->FindClass ("java/lang/Boolean");
353         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(Z)V");
354         if (constructorID != NULL) {
355             result = env->NewObject (JSObjectClass, constructorID, (jboolean)value->toBoolean(exec));
356         }
357     }
358     else {
359         // Create a JavaJSObject.
360         jlong nativeHandle;
361         
362         if (type == ObjectType){
363             JSObject *imp = static_cast<JSObject*>(value);
364             
365             // We either have a wrapper around a Java instance or a JavaScript
366             // object.  If we have a wrapper around a Java instance, return that
367             // instance, otherwise create a new Java JavaJSObject with the JSObject*
368             // as it's nativeHandle.
369             if (imp->classInfo() && strcmp(imp->classInfo()->className, "RuntimeObject") == 0) {
370                 RuntimeObjectImp *runtimeImp = static_cast<RuntimeObjectImp*>(value);
371                 Bindings::JavaInstance *runtimeInstance = static_cast<Bindings::JavaInstance *>(runtimeImp->getInternalInstance());
372                 return runtimeInstance->javaInstance();
373             }
374             else {
375                 nativeHandle = ptr_to_jlong(imp);
376                 
377                 // Bump our 'meta' reference count for the imp.  We maintain the reference
378                 // until either finalize is called or the applet shuts down.
379                 addNativeReference (_root, imp);
380             }
381         }
382         // All other types will result in an undefined object.
383         else {
384             nativeHandle = UndefinedHandle;
385         }
386         
387         // Now create the Java JavaJSObject.  Look for the JavaJSObject in it's new (Tiger)
388         // location and in the original Java 1.4.2 location.
389         jclass JSObjectClass;
390         
391         JSObjectClass = env->FindClass ("sun/plugin/javascript/webkit/JSObject");
392         if (!JSObjectClass) {
393             env->ExceptionDescribe();
394             env->ExceptionClear();
395             JSObjectClass = env->FindClass ("apple/applet/JSObject");
396         }
397             
398         jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(J)V");
399         if (constructorID != NULL) {
400             result = env->NewObject (JSObjectClass, constructorID, nativeHandle);
401         }
402     }
403     
404     return result;
405 }
406
407 JSValue *JavaJSObject::convertJObjectToValue (jobject theObject) const
408 {
409     // Instances of netscape.javascript.JSObject get converted back to
410     // JavaScript objects.  All other objects are wrapped.  It's not
411     // possible to pass primitive types from the Java to JavaScript.
412     // See section 22.7 of 'JavaScript:  The Definitive Guide, 4th Edition',
413     // figure 22-4.
414     jobject classOfInstance = callJNIObjectMethod(theObject, "getClass", "()Ljava/lang/Class;");
415     jstring className = (jstring)callJNIObjectMethod(classOfInstance, "getName", "()Ljava/lang/String;");
416     
417     // Only the sun.plugin.javascript.webkit.JSObject has a member called nativeJSObject. This class is
418     // created above to wrap internal browser objects. The constructor of this class takes the native
419     // pointer and stores it in this object, so that it can be retrieved below.
420     if (strcmp(Bindings::JavaString(className).UTF8String(), "sun.plugin.javascript.webkit.JSObject") == 0) {
421         // Pull the nativeJSObject value from the Java instance.  This is a
422         // pointer to the JSObject.
423         JNIEnv *env = getJNIEnv();
424         jfieldID fieldID = env->GetFieldID((jclass)classOfInstance, "nativeJSObject", "J");
425         if (fieldID == NULL) {
426             return jsUndefined();
427         }
428         jlong nativeHandle = env->GetLongField(theObject, fieldID);
429         if (nativeHandle == UndefinedHandle) {
430             return jsUndefined();
431         }
432         JSObject *imp = static_cast<JSObject*>(jlong_to_impptr(nativeHandle));
433         return imp;
434     }
435
436     JSLock lock;
437     RuntimeObjectImp *newImp = new RuntimeObjectImp(new Bindings::JavaInstance (theObject, _root));
438
439     return newImp;
440 }
441
442 List JavaJSObject::listFromJArray(jobjectArray jArray) const
443 {
444     JNIEnv *env = getJNIEnv();
445     int i, numObjects = jArray ? env->GetArrayLength (jArray) : 0;
446     List aList;
447     
448     for (i = 0; i < numObjects; i++) {
449         jobject anObject = env->GetObjectArrayElement ((jobjectArray)jArray, i);
450         if (anObject) {
451             aList.append (convertJObjectToValue(anObject));
452             env->DeleteLocalRef (anObject);
453         }
454         else {
455             env->ExceptionDescribe();
456             env->ExceptionClear();
457         }
458     }
459     return aList;
460 }
461
462 extern "C" {
463
464 jlong KJS_JSCreateNativeJSObject (JNIEnv*, jclass, jstring, jlong nativeHandle, jboolean)
465 {
466     JSObjectCallContext context;
467     context.type = CreateNative;
468     context.nativeHandle = nativeHandle;
469     return JavaJSObject::invoke (&context).j;
470 }
471
472 void KJS_JSObject_JSFinalize (JNIEnv*, jclass, jlong nativeHandle)
473 {
474     JSObjectCallContext context;
475     context.type = Finalize;
476     context.nativeHandle = nativeHandle;
477     JavaJSObject::invoke (&context);
478 }
479
480 jobject KJS_JSObject_JSObjectCall (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring methodName, jobjectArray args, jboolean)
481 {
482     JSObjectCallContext context;
483     context.type = Call;
484     context.nativeHandle = nativeHandle;
485     context.string = methodName;
486     context.args = args;
487     return JavaJSObject::invoke (&context).l;
488 }
489
490 jobject KJS_JSObject_JSObjectEval (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jscript, jboolean)
491 {
492     JSObjectCallContext context;
493     context.type = Eval;
494     context.nativeHandle = nativeHandle;
495     context.string = jscript;
496     return JavaJSObject::invoke (&context).l;
497 }
498
499 jobject KJS_JSObject_JSObjectGetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
500 {
501     JSObjectCallContext context;
502     context.type = GetMember;
503     context.nativeHandle = nativeHandle;
504     context.string = jname;
505     return JavaJSObject::invoke (&context).l;
506 }
507
508 void KJS_JSObject_JSObjectSetMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jobject value, jboolean)
509 {
510     JSObjectCallContext context;
511     context.type = SetMember;
512     context.nativeHandle = nativeHandle;
513     context.string = jname;
514     context.value = value;
515     JavaJSObject::invoke (&context);
516 }
517
518 void KJS_JSObject_JSObjectRemoveMember (JNIEnv*, jclass, jlong nativeHandle, jstring, jstring jname, jboolean)
519 {
520     JSObjectCallContext context;
521     context.type = RemoveMember;
522     context.nativeHandle = nativeHandle;
523     context.string = jname;
524     JavaJSObject::invoke (&context);
525 }
526
527 jobject KJS_JSObject_JSObjectGetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jboolean)
528 {
529     JSObjectCallContext context;
530     context.type = GetSlot;
531     context.nativeHandle = nativeHandle;
532     context.index = jindex;
533     return JavaJSObject::invoke (&context).l;
534 }
535
536 void KJS_JSObject_JSObjectSetSlot (JNIEnv*, jclass, jlong nativeHandle, jstring, jint jindex, jobject value, jboolean)
537 {
538     JSObjectCallContext context;
539     context.type = SetSlot;
540     context.nativeHandle = nativeHandle;
541     context.index = jindex;
542     context.value = value;
543     JavaJSObject::invoke (&context);
544 }
545
546 jstring KJS_JSObject_JSObjectToString (JNIEnv*, jclass, jlong nativeHandle)
547 {
548     JSObjectCallContext context;
549     context.type = ToString;
550     context.nativeHandle = nativeHandle;
551     return (jstring)JavaJSObject::invoke (&context).l;
552 }
553
554 }