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