Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / js / src / liveconnect / jsj_JavaObject.c
blob51e8903157fdaa53789fdf18c5d640e7144a7f37
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * This Original Code has been modified by IBM Corporation. Modifications made
39 * by IBM described herein are Copyright (c) International Business Machines
40 * Corporation, 2000.
41 * Modifications to Mozilla code or documentation identified per MPL Section 3.3
43 * Date Modified by Description of modification
44 * 04/20/2000 IBM Corp. OS/2 VisualAge build.
46 * ***** END LICENSE BLOCK ***** */
49 * This file is part of the Java-vendor-neutral implementation of LiveConnect
51 * It contains the native code implementation of JS's JavaObject class.
53 * An instance of JavaObject is the JavaScript reflection of a Java object.
57 #include <stdlib.h>
58 #include <string.h>
60 #include "jsobj.h"
61 #include "jsj_private.h" /* LiveConnect internals */
62 #include "jsj_hash.h" /* Hash table with Java object as key */
64 #ifdef JSJ_THREADSAFE
65 #include "prmon.h"
66 #endif
69 * This is a hash table that maps from Java objects to JS objects.
70 * It is used to ensure that the same JS object is obtained when a Java
71 * object is reflected more than once, so that JS object equality tests
72 * work in the expected manner, i.e. the "==" and "===" operators.
74 * The table entry keys are Java objects (of type jobject) and the entry values
75 * are JSObject pointers. Because the jobject type is an opaque handle and
76 * not necessarily a pointer, the hashing and key comparison functions must
77 * invoke the appropriate JVM functions.
79 * When the corresponding JS object instance is finalized, the entry is
80 * removed from the table, and a Java GC root for the Java object is removed.
82 static JSJHashTable *java_obj_reflections = NULL;
84 #ifdef JSJ_THREADSAFE
85 static PRMonitor *java_obj_reflections_monitor = NULL;
86 static int java_obj_reflections_mutation_count = 0;
87 #endif
89 static JSBool installed_GC_callback = JS_FALSE;
90 static JSGCCallback old_GC_callback = NULL;
91 static JavaObjectWrapper* deferred_wrappers = NULL;
93 static JSBool jsj_GC_callback(JSContext *cx, JSGCStatus status)
95 if (status == JSGC_END && deferred_wrappers) {
96 JNIEnv *jEnv;
97 JSJavaThreadState *jsj_env = jsj_EnterJava(cx, &jEnv);
98 if (jEnv) {
99 JavaObjectWrapper* java_wrapper = deferred_wrappers;
100 while (java_wrapper) {
101 deferred_wrappers = java_wrapper->u.next;
102 if (java_wrapper->java_obj)
103 (*jEnv)->DeleteGlobalRef(jEnv, java_wrapper->java_obj);
104 jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor);
105 JS_free(cx, java_wrapper);
106 java_wrapper = deferred_wrappers;
108 jsj_ExitJava(jsj_env);
111 /* always chain to old GC callback if non-null. */
112 return old_GC_callback ? old_GC_callback(cx, status) : JS_TRUE;
115 JSBool
116 jsj_InitJavaObjReflectionsTable(void)
118 JS_ASSERT(!java_obj_reflections);
120 java_obj_reflections =
121 JSJ_NewHashTable(512, jsj_HashJavaObject, jsj_JavaObjectComparator,
122 NULL, NULL, NULL);
123 if (!java_obj_reflections)
124 return JS_FALSE;
126 #ifdef JSJ_THREADSAFE
127 java_obj_reflections_monitor = (struct PRMonitor *) PR_NewMonitor();
128 if (!java_obj_reflections_monitor) {
129 JSJ_HashTableDestroy(java_obj_reflections);
130 return JS_FALSE;
132 #endif
134 return JS_TRUE;
137 JSObject *
138 jsj_WrapJavaObject(JSContext *cx,
139 JNIEnv *jEnv,
140 jobject java_obj,
141 jclass java_class)
143 JSJHashNumber hash_code;
144 JSClass *js_class;
145 JSObject *js_wrapper_obj;
146 JavaObjectWrapper *java_wrapper;
147 JavaClassDescriptor *class_descriptor;
148 JSJHashEntry *he, **hep;
150 #ifdef JSJ_THREADSAFE
151 int mutation_count;
152 #endif
154 js_wrapper_obj = NULL;
156 hash_code = jsj_HashJavaObject((void*)java_obj, (void*)jEnv);
158 #ifdef JSJ_THREADSAFE
159 PR_EnterMonitor(java_obj_reflections_monitor);
160 #endif
162 if (!installed_GC_callback) {
164 * Hook into GC callback mechanism, so we can defer deleting global
165 * references until it's safe.
167 old_GC_callback = JS_SetGCCallback(cx, jsj_GC_callback);
168 installed_GC_callback = JS_TRUE;
171 hep = JSJ_HashTableRawLookup(java_obj_reflections,
172 hash_code, java_obj, (void*)jEnv);
173 he = *hep;
175 #ifdef JSJ_THREADSAFE
176 /* Track mutations to hash table */
177 mutation_count = java_obj_reflections_mutation_count;
179 /* We must temporarily release this monitor so as to avoid
180 deadlocks with the JS GC. See Bugsplat #354852 */
181 PR_ExitMonitor(java_obj_reflections_monitor);
182 #endif
184 if (he) {
185 js_wrapper_obj = (JSObject *)he->value;
186 if (js_wrapper_obj)
187 return js_wrapper_obj;
190 /* No existing reflection found. Construct a new one */
191 class_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, java_class);
192 if (!class_descriptor)
193 return NULL;
194 if (class_descriptor->type == JAVA_SIGNATURE_ARRAY) {
195 js_class = &JavaArray_class;
196 } else {
197 JS_ASSERT(IS_OBJECT_TYPE(class_descriptor->type));
198 js_class = &JavaObject_class;
201 /* Create new JS object to reflect Java object */
202 js_wrapper_obj = JS_NewObject(cx, js_class, NULL, NULL);
203 if (!js_wrapper_obj)
204 return NULL;
206 /* Create private, native portion of JavaObject */
207 java_wrapper =
208 (JavaObjectWrapper *)JS_malloc(cx, sizeof(JavaObjectWrapper));
209 if (!java_wrapper) {
210 jsj_ReleaseJavaClassDescriptor(cx, jEnv, class_descriptor);
211 return NULL;
213 JS_SetPrivate(cx, js_wrapper_obj, java_wrapper);
214 java_wrapper->class_descriptor = class_descriptor;
215 java_wrapper->java_obj = NULL;
217 #ifdef JSJ_THREADSAFE
218 PR_EnterMonitor(java_obj_reflections_monitor);
220 /* We may need to do the hash table lookup again, since some other
221 thread may have updated it while the lock wasn't being held. */
222 if (mutation_count != java_obj_reflections_mutation_count) {
223 hep = JSJ_HashTableRawLookup(java_obj_reflections,
224 hash_code, java_obj, (void*)jEnv);
225 he = *hep;
226 if (he) {
227 js_wrapper_obj = (JSObject *)he->value;
228 if (js_wrapper_obj) {
229 PR_ExitMonitor(java_obj_reflections_monitor);
230 return js_wrapper_obj;
235 java_obj_reflections_mutation_count++;
237 #endif
239 java_obj = (*jEnv)->NewGlobalRef(jEnv, java_obj);
240 java_wrapper->java_obj = java_obj;
241 if (!java_obj)
242 goto out_of_memory;
244 /* cache the hash code for all time. */
245 java_wrapper->u.hash_code = hash_code;
247 /* Add the JavaObject to the hash table */
248 he = JSJ_HashTableRawAdd(java_obj_reflections, hep, hash_code,
249 java_obj, js_wrapper_obj, (void*)jEnv);
250 #ifdef JSJ_THREADSAFE
251 PR_ExitMonitor(java_obj_reflections_monitor);
252 #endif
254 if (!he) {
255 (*jEnv)->DeleteGlobalRef(jEnv, java_obj);
256 goto out_of_memory;
259 return js_wrapper_obj;
261 out_of_memory:
262 /* No need to free js_wrapper_obj, as it will be finalized by GC. */
263 JS_ReportOutOfMemory(cx);
264 return NULL;
267 static void
268 remove_java_obj_reflection_from_hashtable(jobject java_obj, JSJHashNumber hash_code)
270 JSJHashEntry *he, **hep;
272 #ifdef JSJ_THREADSAFE
273 PR_EnterMonitor(java_obj_reflections_monitor);
274 #endif
276 hep = JSJ_HashTableRawLookup(java_obj_reflections, hash_code,
277 java_obj, NULL);
278 he = *hep;
280 JS_ASSERT(he);
281 if (he)
282 JSJ_HashTableRawRemove(java_obj_reflections, hep, he, NULL);
284 #ifdef JSJ_THREADSAFE
285 java_obj_reflections_mutation_count++;
287 PR_ExitMonitor(java_obj_reflections_monitor);
288 #endif
291 JS_EXPORT_API(void)
292 JavaObject_finalize(JSContext *cx, JSObject *obj)
294 JavaObjectWrapper *java_wrapper;
295 jobject java_obj;
296 JNIEnv *jEnv;
297 JSJavaThreadState *jsj_env;
299 java_wrapper = JS_GetPrivate(cx, obj);
300 if (!java_wrapper)
301 return;
302 java_obj = java_wrapper->java_obj;
304 if (java_obj) {
305 remove_java_obj_reflection_from_hashtable(java_obj, java_wrapper->u.hash_code);
307 /* defer releasing global refs until it is safe to do so. */
308 java_wrapper->u.next = deferred_wrappers;
309 deferred_wrappers = java_wrapper;
310 } else {
311 jsj_env = jsj_EnterJava(cx, &jEnv);
312 if (jEnv) {
313 jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor);
314 JS_free(cx, java_wrapper);
315 jsj_ExitJava(jsj_env);
316 } else {
317 java_wrapper->u.next = deferred_wrappers;
318 deferred_wrappers = java_wrapper;
323 /* Trivial helper for jsj_DiscardJavaObjReflections(), below */
324 static JSIntn
325 enumerate_remove_java_obj(JSJHashEntry *he, JSIntn i, void *arg)
327 JSJavaThreadState *jsj_env = (JSJavaThreadState *)arg;
328 JNIEnv *jEnv = jsj_env->jEnv;
329 jobject java_obj;
330 JavaObjectWrapper *java_wrapper;
331 JSObject *java_wrapper_obj;
333 java_wrapper_obj = (JSObject *)he->value;
335 /* Warning: NULL argument may cause assertion in JS engine, but it's actually OK */
336 java_wrapper = JS_GetPrivate(jsj_env->cx, java_wrapper_obj);
337 java_obj = java_wrapper->java_obj;
338 (*jEnv)->DeleteGlobalRef(jEnv, java_obj);
339 java_wrapper->java_obj = NULL;
340 return HT_ENUMERATE_REMOVE;
343 /* This shutdown routine discards all JNI references to Java objects
344 that have been reflected into JS, even if there are still references
345 to them from JS. */
346 void
347 jsj_DiscardJavaObjReflections(JNIEnv *jEnv)
349 JSJavaThreadState *jsj_env;
350 char *err_msg;
352 /* Get the per-thread state corresponding to the current Java thread */
353 jsj_env = jsj_MapJavaThreadToJSJavaThreadState(jEnv, &err_msg);
354 JS_ASSERT(jsj_env);
355 if (!jsj_env) {
356 if (err_msg) {
357 jsj_LogError(err_msg);
358 JS_smprintf_free(err_msg);
361 return;
364 JS_ASSERT(!err_msg);
366 if (java_obj_reflections) {
367 JSJ_HashTableEnumerateEntries(java_obj_reflections,
368 enumerate_remove_java_obj,
369 (void*)jsj_env);
370 JSJ_HashTableDestroy(java_obj_reflections);
371 java_obj_reflections = NULL;
375 JSBool
376 JavaObject_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
378 JavaObjectWrapper *java_wrapper;
379 JavaClassDescriptor *class_descriptor;
380 jobject java_obj;
381 JNIEnv *jEnv;
382 JSJavaThreadState *jsj_env;
383 JSBool result;
385 java_wrapper = JS_GetPrivate(cx, obj);
386 if (!java_wrapper) {
387 if (type == JSTYPE_OBJECT) {
388 *vp = OBJECT_TO_JSVAL(obj);
389 return JS_TRUE;
392 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
393 JSJMSG_BAD_OP_JOBJECT);
394 return JS_FALSE;
397 java_obj = java_wrapper->java_obj;
398 class_descriptor = java_wrapper->class_descriptor;
400 switch (type) {
401 case JSTYPE_OBJECT:
402 *vp = OBJECT_TO_JSVAL(obj);
403 return JS_TRUE;
405 case JSTYPE_FUNCTION:
406 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
407 JSJMSG_CONVERT_TO_FUNC);
408 return JS_FALSE;
410 case JSTYPE_VOID:
411 case JSTYPE_STRING:
412 /* Get the Java per-thread environment pointer for this JSContext */
413 jsj_env = jsj_EnterJava(cx, &jEnv);
414 if (!jEnv)
415 return JS_FALSE;
417 /* Either extract a C-string from the java.lang.String object
418 or call the Java toString() method */
419 result = jsj_ConvertJavaObjectToJSString(cx, jEnv, class_descriptor, java_obj, vp);
420 jsj_ExitJava(jsj_env);
421 return result;
423 case JSTYPE_NUMBER:
424 /* Get the Java per-thread environment pointer for this JSContext */
425 jsj_env = jsj_EnterJava(cx, &jEnv);
426 if (!jEnv)
427 return JS_FALSE;
429 /* Call Java doubleValue() method, if applicable */
430 result = jsj_ConvertJavaObjectToJSNumber(cx, jEnv, class_descriptor, java_obj, vp);
431 jsj_ExitJava(jsj_env);
432 return result;
434 case JSTYPE_BOOLEAN:
435 /* Get the Java per-thread environment pointer for this JSContext */
436 jsj_env = jsj_EnterJava(cx, &jEnv);
437 if (!jEnv)
438 return JS_FALSE;
440 /* Call booleanValue() method, if applicable */
441 result = jsj_ConvertJavaObjectToJSBoolean(cx, jEnv, class_descriptor, java_obj, vp);
442 jsj_ExitJava(jsj_env);
443 return result;
445 default:
446 JS_ASSERT(0);
447 return JS_FALSE;
452 * Get a property from the prototype object of a native ECMA object, i.e.
453 * return <js_constructor_name>.prototype.<member_name>
454 * This is used to allow Java objects to inherit methods from Array.prototype
455 * and String.prototype.
457 static JSBool
458 inherit_props_from_JS_natives(JSContext *cx, const char *js_constructor_name,
459 const char *member_name, jsval *vp)
461 JSObject *global_obj, *constructor_obj, *prototype_obj;
462 jsval constructor_val, prototype_val;
464 global_obj = JS_GetGlobalObject(cx);
465 JS_ASSERT(global_obj);
466 if (!global_obj)
467 return JS_FALSE;
469 JS_GetProperty(cx, global_obj, js_constructor_name, &constructor_val);
470 JS_ASSERT(JSVAL_IS_OBJECT(constructor_val));
471 constructor_obj = JSVAL_TO_OBJECT(constructor_val);
473 JS_GetProperty(cx, constructor_obj, "prototype", &prototype_val);
474 JS_ASSERT(JSVAL_IS_OBJECT(prototype_val));
475 prototype_obj = JSVAL_TO_OBJECT(prototype_val);
477 return JS_GetProperty(cx, prototype_obj, member_name, vp) && *vp != JSVAL_VOID;
480 struct JSJPropertyInfo {
481 JSBool wantProp; /* input param tells whether prop is returned */
482 const char* name; /* output param, name of property (XXX ASCII) */
483 uintN attributes; /* output param, attributes of property */
484 JSProperty *prop; /* output param, if wantProp, held pointer that
485 must be released via OBJ_DROP_PROPERTY */
487 typedef struct JSJPropertyInfo JSJPropertyInfo;
489 static JSBool
490 lookup_member_by_id(JSContext *cx, JNIEnv *jEnv, JSObject *obj,
491 JavaObjectWrapper **java_wrapperp, jsid id,
492 JavaMemberDescriptor **member_descriptorp,
493 jsval *vp, JSObject **proto_chainp,
494 JSJPropertyInfo *prop_infop)
496 jsval idval;
497 JavaObjectWrapper *java_wrapper;
498 JavaMemberDescriptor *member_descriptor;
499 const char *member_name;
500 JavaClassDescriptor *class_descriptor;
501 JSObject *proto_chain;
502 JSBool found_in_proto;
504 found_in_proto = JS_FALSE;
505 member_descriptor = NULL;
506 java_wrapper = JS_GetPrivate(cx, obj);
508 /* Handle accesses to prototype object */
509 if (!java_wrapper) {
510 if (JS_IdToValue(cx, id, &idval) && JSVAL_IS_STRING(idval) &&
511 (member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval))) != NULL) {
512 if (!strcmp(member_name, "constructor"))
513 goto done;
515 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_JOBJECT);
516 return JS_FALSE;
519 class_descriptor = java_wrapper->class_descriptor;
520 JS_ASSERT(IS_REFERENCE_TYPE(class_descriptor->type));
522 member_descriptor = jsj_LookupJavaMemberDescriptorById(cx, jEnv, class_descriptor, id);
523 if (member_descriptor)
524 goto done;
526 /* Instances can reference static methods and fields */
527 member_descriptor = jsj_LookupJavaStaticMemberDescriptorById(cx, jEnv, class_descriptor, id);
528 if (member_descriptor)
529 goto done;
531 /* Ensure that the property we're searching for is string-valued. */
532 JS_IdToValue(cx, id, &idval);
533 if (!JSVAL_IS_STRING(idval)) {
534 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_JOBJECT_EXPR);
535 return JS_FALSE;
537 member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
540 * A little LC3 feature magic:
541 * + Instances of java.lang.String "inherit" the standard ECMA string methods
542 * of String.prototype. All of the ECMA string methods convert the Java
543 * string to a JS string before performing the string operation. For example,
544 * s = new java.lang.String("foobar");
545 * return s.slice(2);
546 * + Similarly, instances of Java arrays "inherit" the standard ECMA array
547 * methods of Array.prototype. (Not all of these methods work properly
548 * on JavaArray objects, however, since the 'length' property is read-only.)
550 if (vp) {
551 if ((class_descriptor->type == JAVA_SIGNATURE_JAVA_LANG_STRING) &&
552 inherit_props_from_JS_natives(cx, "String", member_name, vp))
553 goto done;
554 if ((class_descriptor->type == JAVA_SIGNATURE_ARRAY) &&
555 inherit_props_from_JS_natives(cx, "Array", member_name, vp))
556 goto done;
559 /* Check for access to magic prototype chain property */
560 if (!strcmp(member_name, "__proto__")) {
561 proto_chain = JS_GetPrototype(cx, obj);
562 if (vp)
563 *vp = OBJECT_TO_JSVAL(proto_chain);
564 goto done;
568 * See if the property looks like the explicit resolution of an
569 * overloaded method, e.g. "max(double,double)", first as an instance method,
570 * then as a static method. If we find such a method, it will be cached
571 * so future accesses won't run this code.
573 member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_FALSE);
574 if (member_descriptor)
575 goto done;
576 member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_TRUE);
577 if (member_descriptor)
578 goto done;
580 /* Is the property defined in the prototype chain? */
581 if (proto_chainp && prop_infop) {
582 /* If so, follow __proto__ link to search prototype chain */
583 proto_chain = JS_GetPrototype(cx, obj);
585 /* Use OBJ_LOOKUP_PROPERTY to determine if and where the property
586 actually exists in the prototype chain. */
587 if (proto_chain) {
588 if (!OBJ_LOOKUP_PROPERTY(cx, proto_chain, id, proto_chainp,
589 &prop_infop->prop)) {
590 return JS_FALSE;
592 if (prop_infop->prop) {
593 if (!OBJ_GET_ATTRIBUTES(cx, *proto_chainp, id, prop_infop->prop,
594 &prop_infop->attributes)) {
595 OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop);
596 return JS_FALSE;
598 if (!prop_infop->wantProp) {
599 OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop);
600 prop_infop->prop = NULL;
602 prop_infop->name = member_name;
603 found_in_proto = JS_TRUE;
604 goto done;
609 /* Report lack of Java member with the given property name */
610 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NO_INSTANCE_NAME,
611 class_descriptor->name, member_name);
612 return JS_FALSE;
614 done:
615 /* Success. Handle the multiple return values */
616 if (java_wrapperp)
617 *java_wrapperp = java_wrapper;
618 if (member_descriptorp)
619 *member_descriptorp = member_descriptor;
620 if (proto_chainp && !found_in_proto)
621 *proto_chainp = NULL;
622 return JS_TRUE;
625 JS_EXPORT_API(JSBool)
626 JavaObject_getPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
628 jobject java_obj;
629 JavaMemberDescriptor *member_descriptor;
630 JavaObjectWrapper *java_wrapper;
631 JNIEnv *jEnv;
632 JSObject *funobj;
633 jsval field_val, method_val;
634 JSBool success;
635 JSJavaThreadState *jsj_env;
636 JSObject *proto_chain;
637 JSJPropertyInfo prop_info;
639 /* printf("In JavaObject_getProperty\n"); */
641 /* Get the Java per-thread environment pointer for this JSContext */
642 jsj_env = jsj_EnterJava(cx, &jEnv);
643 if (!jEnv)
644 return JS_FALSE;
646 if (vp)
647 *vp = JSVAL_VOID;
648 prop_info.wantProp = JS_FALSE;
649 if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, vp,
650 &proto_chain, &prop_info)) {
651 jsj_ExitJava(jsj_env);
652 return JS_FALSE;
655 /* Handle access to special, non-Java properties of JavaObjects, e.g. the
656 "constructor" property of the prototype object */
657 if (!member_descriptor) {
658 jsj_ExitJava(jsj_env);
659 if (proto_chain)
660 return JS_GetProperty(cx, proto_chain, prop_info.name, vp);
661 return JS_TRUE;
664 java_obj = java_wrapper->java_obj;
665 field_val = method_val = JSVAL_VOID;
667 if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) {
668 jsj_JSIsCallingApplet = JS_TRUE;
671 /* If a field member, get the value of the field */
672 if (member_descriptor->field) {
673 success = jsj_GetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, &field_val);
674 if (!success) {
675 jsj_ExitJava(jsj_env);
676 return JS_FALSE;
680 /* If a method member, build a wrapper around the Java method */
681 if (member_descriptor->methods) {
682 /* Create a function object with this JavaObject as its parent, so that
683 JSFUN_BOUND_METHOD binds it as the default 'this' for the function. */
684 funobj = JS_CloneFunctionObject(cx, member_descriptor->invoke_func_obj, obj);
685 if (!funobj) {
686 jsj_ExitJava(jsj_env);
687 return JS_FALSE;
689 method_val = OBJECT_TO_JSVAL(funobj);
692 #if TEST_JAVAMEMBER
693 /* Always create a JavaMember object, even though it's inefficient */
694 obj = jsj_CreateJavaMember(cx, method_val, field_val);
695 if (!obj) {
696 jsj_ExitJava(jsj_env);
697 return JS_FALSE;
699 *vp = OBJECT_TO_JSVAL(obj);
700 #else /* !TEST_JAVAMEMBER */
702 if (member_descriptor->field) {
703 if (!member_descriptor->methods) {
704 /* Return value of Java field */
705 *vp = field_val;
706 } else {
707 /* Handle special case of access to a property that could refer
708 to either a Java field or a method that share the same name.
709 In Java, such ambiguity is not possible because the compiler
710 can statically determine which is being accessed. */
711 obj = jsj_CreateJavaMember(cx, method_val, field_val);
712 if (!obj) {
713 jsj_ExitJava(jsj_env);
714 return JS_FALSE;
716 *vp = OBJECT_TO_JSVAL(obj);
719 } else {
720 /* Return wrapper around Java method */
721 *vp = method_val;
724 #endif /* !TEST_JAVAMEMBER */
726 jsj_ExitJava(jsj_env);
727 return JS_TRUE;
730 static JSBool
731 JavaObject_setPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
733 jobject java_obj;
734 const char *member_name;
735 JavaObjectWrapper *java_wrapper;
736 JavaClassDescriptor *class_descriptor;
737 JavaMemberDescriptor *member_descriptor;
738 jsval idval;
739 JNIEnv *jEnv;
740 JSJavaThreadState *jsj_env;
741 JSObject *proto_chain;
742 JSJPropertyInfo prop_info;
743 JSBool result;
745 /* printf("In JavaObject_setProperty\n"); */
747 /* Get the Java per-thread environment pointer for this JSContext */
748 jsj_env = jsj_EnterJava(cx, &jEnv);
749 if (!jEnv)
750 return JS_FALSE;
752 prop_info.wantProp = JS_FALSE;
753 if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, NULL,
754 &proto_chain, &prop_info)) {
755 jsj_ExitJava(jsj_env);
756 return JS_FALSE;
759 /* Could be assignment to magic JS __proto__ property rather than a Java field */
760 if (!member_descriptor) {
761 if (proto_chain && (prop_info.attributes & JSPROP_SHARED)) {
762 JS_SetProperty(cx, proto_chain, prop_info.name, vp);
763 } else {
764 JS_IdToValue(cx, id, &idval);
765 if (!JSVAL_IS_STRING(idval))
766 goto no_such_field;
767 member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
768 if (strcmp(member_name, "__proto__"))
769 goto no_such_field;
770 if (!JSVAL_IS_OBJECT(*vp)) {
771 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
772 JSJMSG_BAD_PROTO_ASSIGNMENT);
773 jsj_ExitJava(jsj_env);
774 return JS_FALSE;
776 JS_SetPrototype(cx, obj, JSVAL_TO_OBJECT(*vp));
778 jsj_ExitJava(jsj_env);
779 return JS_TRUE;
782 /* Check for the case where there is a method with the given name, but no field
783 with that name */
784 if (!member_descriptor->field)
785 goto no_such_field;
787 /* Silently fail if field value is final (immutable), as required by ECMA spec */
788 if (member_descriptor->field->modifiers & ACC_FINAL) {
789 jsj_ExitJava(jsj_env);
790 return JS_TRUE;
793 java_obj = java_wrapper->java_obj;
795 if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) {
796 jsj_JSIsCallingApplet = JS_TRUE;
799 result = jsj_SetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, *vp);
800 jsj_ExitJava(jsj_env);
801 return result;
803 no_such_field:
804 JS_IdToValue(cx, id, &idval);
805 member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval));
806 class_descriptor = java_wrapper->class_descriptor;
807 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
808 JSJMSG_NO_NAME_IN_CLASS,
809 member_name, class_descriptor->name);
810 jsj_ExitJava(jsj_env);
811 return JS_FALSE;
814 static JSBool
815 JavaObject_lookupProperty(JSContext *cx, JSObject *obj, jsid id,
816 JSObject **objp, JSProperty **propp)
818 JNIEnv *jEnv;
819 JSErrorReporter old_reporter;
820 jsval dummy_val;
821 JSObject *proto_chain;
822 JSJPropertyInfo prop_info;
823 JSJavaThreadState *jsj_env;
825 /* printf("In JavaObject_lookupProperty()\n"); */
827 /* Get the Java per-thread environment pointer for this JSContext */
828 jsj_env = jsj_EnterJava(cx, &jEnv);
829 if (!jEnv)
830 return JS_FALSE;
832 old_reporter = JS_SetErrorReporter(cx, NULL);
833 prop_info.wantProp = JS_TRUE;
834 if (lookup_member_by_id(cx, jEnv, obj, NULL, id, NULL, &dummy_val,
835 &proto_chain, &prop_info)) {
836 /* signify that the property is in the prototype chain or the object itself. */
837 if (proto_chain) {
838 *objp = proto_chain;
839 *propp = prop_info.prop;
840 } else {
841 *objp = obj;
842 *propp = (JSProperty*)1;
844 } else {
845 *objp = NULL;
846 *propp = NULL;
849 JS_SetErrorReporter(cx, old_reporter);
850 jsj_ExitJava(jsj_env);
851 return JS_TRUE;
854 static JSBool
855 JavaObject_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value,
856 JSPropertyOp getter, JSPropertyOp setter,
857 uintN attrs, JSProperty **propp)
859 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
860 JSJMSG_JOBJECT_PROP_DEFINE);
861 return JS_FALSE;
864 static JSBool
865 JavaObject_getAttributes(JSContext *cx, JSObject *obj, jsid id,
866 JSProperty *prop, uintN *attrsp)
868 /* We don't maintain JS property attributes for Java class members */
869 *attrsp = JSPROP_PERMANENT|JSPROP_ENUMERATE;
870 return JS_TRUE;
873 static JSBool
874 JavaObject_setAttributes(JSContext *cx, JSObject *obj, jsid id,
875 JSProperty *prop, uintN *attrsp)
877 /* We don't maintain JS property attributes for Java class members */
878 if (*attrsp != (JSPROP_PERMANENT|JSPROP_ENUMERATE)) {
879 JS_ASSERT(0);
880 return JS_FALSE;
883 /* Silently ignore all setAttribute attempts */
884 return JS_TRUE;
887 static JSBool
888 JavaObject_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
890 JSVersion version = JS_GetVersion(cx);
892 *vp = JSVAL_FALSE;
894 if (!JSVERSION_IS_ECMA(version)) {
895 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
896 JSJMSG_JOBJECT_PROP_DELETE);
897 return JS_FALSE;
898 } else {
899 /* Attempts to delete permanent properties are silently ignored
900 by ECMAScript. */
901 return JS_TRUE;
905 static JSBool
906 JavaObject_defaultValue(JSContext *cx, JSObject *obj, JSType type, jsval *vp)
908 /* printf("In JavaObject_defaultValue()\n"); */
909 return JavaObject_convert(cx, obj, type, vp);
912 static JSBool
913 JavaObject_newEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op,
914 jsval *statep, jsid *idp)
916 JavaObjectWrapper *java_wrapper;
917 JavaMemberDescriptor *member_descriptor;
918 JavaClassDescriptor *class_descriptor;
919 JNIEnv *jEnv;
920 JSJavaThreadState *jsj_env;
922 java_wrapper = JS_GetPrivate(cx, obj);
923 /* Check for prototype object */
924 if (!java_wrapper) {
925 *statep = JSVAL_NULL;
926 if (idp)
927 *idp = INT_TO_JSVAL(0);
928 return JS_TRUE;
931 class_descriptor = java_wrapper->class_descriptor;
933 switch(enum_op) {
934 case JSENUMERATE_INIT:
936 /* Get the Java per-thread environment pointer for this JSContext */
937 jsj_env = jsj_EnterJava(cx, &jEnv);
938 if (!jEnv)
939 return JS_FALSE;
941 member_descriptor = jsj_GetClassInstanceMembers(cx, jEnv, class_descriptor);
942 *statep = PRIVATE_TO_JSVAL(member_descriptor);
943 if (idp)
944 *idp = INT_TO_JSVAL(class_descriptor->num_instance_members);
945 jsj_ExitJava(jsj_env);
946 return JS_TRUE;
948 case JSENUMERATE_NEXT:
949 member_descriptor = JSVAL_TO_PRIVATE(*statep);
950 if (member_descriptor) {
952 /* Don't enumerate explicit-signature methods, i.e. enumerate toValue,
953 but not toValue(int), toValue(double), etc. */
954 while (member_descriptor->methods && member_descriptor->methods->is_alias) {
955 member_descriptor = member_descriptor->next;
956 if (!member_descriptor) {
957 *statep = JSVAL_NULL;
958 return JS_TRUE;
962 *idp = member_descriptor->id;
963 *statep = PRIVATE_TO_JSVAL(member_descriptor->next);
964 return JS_TRUE;
967 /* Fall through ... */
969 case JSENUMERATE_DESTROY:
970 *statep = JSVAL_NULL;
971 return JS_TRUE;
973 default:
974 JS_ASSERT(0);
975 return JS_FALSE;
979 static JSBool
980 JavaObject_checkAccess(JSContext *cx, JSObject *obj, jsid id,
981 JSAccessMode mode, jsval *vp, uintN *attrsp)
983 switch (mode) {
984 case JSACC_WATCH:
985 JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL,
986 JSJMSG_JOBJECT_PROP_WATCH);
987 return JS_FALSE;
989 default:
990 return JS_TRUE;
994 #define JSJ_SLOT_COUNT (JSSLOT_PRIVATE+1)
996 JSObjectMap *
997 jsj_wrapper_newObjectMap(JSContext *cx, jsrefcount nrefs, JSObjectOps *ops,
998 JSClass *clasp, JSObject *obj)
1000 JSObjectMap * map;
1002 map = (JSObjectMap *) JS_malloc(cx, sizeof(JSObjectMap));
1003 if (map) {
1004 map->nrefs = nrefs;
1005 map->ops = ops;
1006 map->freeslot = JSJ_SLOT_COUNT;
1008 return map;
1011 void
1012 jsj_wrapper_destroyObjectMap(JSContext *cx, JSObjectMap *map)
1014 JS_free(cx, map);
1017 jsval
1018 jsj_wrapper_getRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot)
1020 JS_ASSERT(slot < JSJ_SLOT_COUNT);
1021 JS_ASSERT(obj->map->freeslot == JSJ_SLOT_COUNT);
1022 return STOBJ_GET_SLOT(obj, slot);
1025 JSBool
1026 jsj_wrapper_setRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v)
1028 JS_ASSERT(slot < JSJ_SLOT_COUNT);
1029 JS_ASSERT(obj->map->freeslot == JSJ_SLOT_COUNT);
1030 STOBJ_SET_SLOT(obj, slot, v);
1031 return JS_TRUE;
1034 JSObjectOps JavaObject_ops = {
1035 /* Mandatory non-null function pointer members. */
1036 jsj_wrapper_newObjectMap, /* newObjectMap */
1037 jsj_wrapper_destroyObjectMap, /* destroyObjectMap */
1038 JavaObject_lookupProperty,
1039 JavaObject_defineProperty,
1040 JavaObject_getPropertyById, /* getProperty */
1041 JavaObject_setPropertyById, /* setProperty */
1042 JavaObject_getAttributes,
1043 JavaObject_setAttributes,
1044 JavaObject_deleteProperty,
1045 JavaObject_defaultValue,
1046 JavaObject_newEnumerate,
1047 JavaObject_checkAccess,
1049 /* Optionally non-null members start here. */
1050 NULL, /* thisObject */
1051 NULL, /* dropProperty */
1052 NULL, /* call */
1053 NULL, /* construct */
1054 NULL, /* xdrObject */
1055 NULL, /* hasInstance */
1056 NULL, /* setProto */
1057 NULL, /* setParent */
1058 NULL, /* mark */
1059 NULL, /* clear */
1060 jsj_wrapper_getRequiredSlot, /* getRequiredSlot */
1061 jsj_wrapper_setRequiredSlot /* setRequiredSlot */
1064 static JSObjectOps *
1065 JavaObject_getObjectOps(JSContext *cx, JSClass *clazz)
1067 return &JavaObject_ops;
1070 JSClass JavaObject_class = {
1071 "JavaObject", JSCLASS_HAS_PRIVATE,
1072 NULL, NULL, NULL, NULL,
1073 NULL, NULL, JavaObject_convert, JavaObject_finalize,
1075 /* Optionally non-null members start here. */
1076 JavaObject_getObjectOps,
1077 NULL, /* checkAccess */
1078 NULL, /* call */
1079 NULL, /* construct */
1080 NULL, /* xdrObject */
1081 NULL, /* hasInstance */
1082 NULL, /* mark */
1083 0, /* spare */
1086 extern JS_IMPORT_DATA(JSObjectOps) js_ObjectOps;
1088 JSBool
1089 jsj_init_JavaObject(JSContext *cx, JSObject *global_obj)
1091 return JS_InitClass(cx, global_obj,
1092 0, &JavaObject_class, 0, 0,
1093 0, 0,
1094 0, 0) != 0;