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