/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * This Original Code has been modified by IBM Corporation. Modifications made * by IBM described herein are Copyright (c) International Business Machines * Corporation, 2000. * Modifications to Mozilla code or documentation identified per MPL Section 3.3 * * Date Modified by Description of modification * 04/20/2000 IBM Corp. OS/2 VisualAge build. * * ***** END LICENSE BLOCK ***** */ /* * This file is part of the Java-vendor-neutral implementation of LiveConnect * * It contains the native code implementation of JS's JavaObject class. * * An instance of JavaObject is the JavaScript reflection of a Java object. * */ #include <stdlib.h> #include <string.h> #include "jsapi.h" #include "jscntxt.h" #include "jsobj.h" #include "jsj_private.h" /* LiveConnect internals */ #include "jsj_hash.h" /* Hash table with Java object as key */ #ifdef JSJ_THREADSAFE #include "prmon.h" #endif /* * This is a hash table that maps from Java objects to JS objects. * It is used to ensure that the same JS object is obtained when a Java * object is reflected more than once, so that JS object equality tests * work in the expected manner, i.e. the "==" and "===" operators. * * The table entry keys are Java objects (of type jobject) and the entry values * are JSObject pointers. Because the jobject type is an opaque handle and * not necessarily a pointer, the hashing and key comparison functions must * invoke the appropriate JVM functions. * * When the corresponding JS object instance is finalized, the entry is * removed from the table, and a Java GC root for the Java object is removed. */ static JSJHashTable *java_obj_reflections = NULL; #ifdef JSJ_THREADSAFE static PRMonitor *java_obj_reflections_monitor = NULL; static int java_obj_reflections_mutation_count = 0; #endif static JSBool installed_GC_callback = JS_FALSE; static JSGCCallback old_GC_callback = NULL; static JavaObjectWrapper* deferred_wrappers = NULL; static JSBool jsj_GC_callback(JSContext *cx, JSGCStatus status) { if (status == JSGC_END && deferred_wrappers) { JNIEnv *jEnv; JSJavaThreadState *jsj_env = jsj_EnterJava(cx, &jEnv); if (jEnv) { JavaObjectWrapper* java_wrapper = deferred_wrappers; while (java_wrapper) { deferred_wrappers = java_wrapper->u.next; if (java_wrapper->java_obj) (*jEnv)->DeleteGlobalRef(jEnv, java_wrapper->java_obj); jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor); JS_free(cx, java_wrapper); java_wrapper = deferred_wrappers; } jsj_ExitJava(jsj_env); } } /* always chain to old GC callback if non-null. */ return old_GC_callback ? old_GC_callback(cx, status) : JS_TRUE; } JSBool jsj_InitJavaObjReflectionsTable(void) { JS_ASSERT(!java_obj_reflections); java_obj_reflections = JSJ_NewHashTable(512, jsj_HashJavaObject, jsj_JavaObjectComparator, NULL, NULL, NULL); if (!java_obj_reflections) return JS_FALSE; #ifdef JSJ_THREADSAFE java_obj_reflections_monitor = (struct PRMonitor *) PR_NewMonitor(); if (!java_obj_reflections_monitor) { JSJ_HashTableDestroy(java_obj_reflections); return JS_FALSE; } #endif return JS_TRUE; } JSObject * jsj_WrapJavaObject(JSContext *cx, JNIEnv *jEnv, jobject java_obj, jclass java_class) { JSJHashNumber hash_code; JSClass *js_class; JSObject *js_wrapper_obj; JavaObjectWrapper *java_wrapper; JavaClassDescriptor *class_descriptor; JSJHashEntry *he, **hep; #ifdef JSJ_THREADSAFE int mutation_count; #endif js_wrapper_obj = NULL; hash_code = jsj_HashJavaObject((void*)java_obj, (void*)jEnv); #ifdef JSJ_THREADSAFE PR_EnterMonitor(java_obj_reflections_monitor); #endif if (!installed_GC_callback) { /* * Hook into GC callback mechanism, so we can defer deleting global * references until it's safe. */ old_GC_callback = JS_SetGCCallback(cx, jsj_GC_callback); installed_GC_callback = JS_TRUE; } hep = JSJ_HashTableRawLookup(java_obj_reflections, hash_code, java_obj, (void*)jEnv); he = *hep; #ifdef JSJ_THREADSAFE /* Track mutations to hash table */ mutation_count = java_obj_reflections_mutation_count; /* We must temporarily release this monitor so as to avoid deadlocks with the JS GC. See Bugsplat #354852 */ PR_ExitMonitor(java_obj_reflections_monitor); #endif if (he) { js_wrapper_obj = (JSObject *)he->value; if (js_wrapper_obj) return js_wrapper_obj; } /* No existing reflection found. Construct a new one */ class_descriptor = jsj_GetJavaClassDescriptor(cx, jEnv, java_class); if (!class_descriptor) return NULL; if (class_descriptor->type == JAVA_SIGNATURE_ARRAY) { js_class = &JavaArray_class; } else { JS_ASSERT(IS_OBJECT_TYPE(class_descriptor->type)); js_class = &JavaObject_class; } /* Create new JS object to reflect Java object */ js_wrapper_obj = JS_NewObject(cx, js_class, NULL, NULL); if (!js_wrapper_obj) return NULL; /* Create private, native portion of JavaObject */ java_wrapper = (JavaObjectWrapper *)JS_malloc(cx, sizeof(JavaObjectWrapper)); if (!java_wrapper) { jsj_ReleaseJavaClassDescriptor(cx, jEnv, class_descriptor); return NULL; } JS_SetPrivate(cx, js_wrapper_obj, java_wrapper); java_wrapper->class_descriptor = class_descriptor; java_wrapper->java_obj = NULL; #ifdef JSJ_THREADSAFE PR_EnterMonitor(java_obj_reflections_monitor); /* We may need to do the hash table lookup again, since some other thread may have updated it while the lock wasn't being held. */ if (mutation_count != java_obj_reflections_mutation_count) { hep = JSJ_HashTableRawLookup(java_obj_reflections, hash_code, java_obj, (void*)jEnv); he = *hep; if (he) { js_wrapper_obj = (JSObject *)he->value; if (js_wrapper_obj) { PR_ExitMonitor(java_obj_reflections_monitor); return js_wrapper_obj; } } } java_obj_reflections_mutation_count++; #endif java_obj = (*jEnv)->NewGlobalRef(jEnv, java_obj); java_wrapper->java_obj = java_obj; if (!java_obj) goto out_of_memory; /* cache the hash code for all time. */ java_wrapper->u.hash_code = hash_code; /* Add the JavaObject to the hash table */ he = JSJ_HashTableRawAdd(java_obj_reflections, hep, hash_code, java_obj, js_wrapper_obj, (void*)jEnv); #ifdef JSJ_THREADSAFE PR_ExitMonitor(java_obj_reflections_monitor); #endif if (!he) { (*jEnv)->DeleteGlobalRef(jEnv, java_obj); goto out_of_memory; } return js_wrapper_obj; out_of_memory: /* No need to free js_wrapper_obj, as it will be finalized by GC. */ JS_ReportOutOfMemory(cx); return NULL; } static void remove_java_obj_reflection_from_hashtable(jobject java_obj, JSJHashNumber hash_code) { JSJHashEntry *he, **hep; #ifdef JSJ_THREADSAFE PR_EnterMonitor(java_obj_reflections_monitor); #endif hep = JSJ_HashTableRawLookup(java_obj_reflections, hash_code, java_obj, NULL); he = *hep; JS_ASSERT(he); if (he) JSJ_HashTableRawRemove(java_obj_reflections, hep, he, NULL); #ifdef JSJ_THREADSAFE java_obj_reflections_mutation_count++; PR_ExitMonitor(java_obj_reflections_monitor); #endif } JS_EXPORT_API(void) JavaObject_finalize(JSContext *cx, JSObject *obj) { JavaObjectWrapper *java_wrapper; jobject java_obj; JNIEnv *jEnv; JSJavaThreadState *jsj_env; java_wrapper = JS_GetPrivate(cx, obj); if (!java_wrapper) return; java_obj = java_wrapper->java_obj; if (java_obj) { remove_java_obj_reflection_from_hashtable(java_obj, java_wrapper->u.hash_code); /* defer releasing global refs until it is safe to do so. */ java_wrapper->u.next = deferred_wrappers; deferred_wrappers = java_wrapper; } else { jsj_env = jsj_EnterJava(cx, &jEnv); if (jEnv) { jsj_ReleaseJavaClassDescriptor(cx, jEnv, java_wrapper->class_descriptor); JS_free(cx, java_wrapper); jsj_ExitJava(jsj_env); } else { java_wrapper->u.next = deferred_wrappers; deferred_wrappers = java_wrapper; } } } /* Trivial helper for jsj_DiscardJavaObjReflections(), below */ static JSIntn enumerate_remove_java_obj(JSJHashEntry *he, JSIntn i, void *arg) { JSJavaThreadState *jsj_env = (JSJavaThreadState *)arg; JNIEnv *jEnv = jsj_env->jEnv; jobject java_obj; JavaObjectWrapper *java_wrapper; JSObject *java_wrapper_obj; java_wrapper_obj = (JSObject *)he->value; /* Warning: NULL argument may cause assertion in JS engine, but it's actually OK */ java_wrapper = JS_GetPrivate(jsj_env->cx, java_wrapper_obj); java_obj = java_wrapper->java_obj; (*jEnv)->DeleteGlobalRef(jEnv, java_obj); java_wrapper->java_obj = NULL; return HT_ENUMERATE_REMOVE; } /* This shutdown routine discards all JNI references to Java objects that have been reflected into JS, even if there are still references to them from JS. */ void jsj_DiscardJavaObjReflections(JNIEnv *jEnv) { JSJavaThreadState *jsj_env; char *err_msg; /* Get the per-thread state corresponding to the current Java thread */ jsj_env = jsj_MapJavaThreadToJSJavaThreadState(jEnv, &err_msg); JS_ASSERT(jsj_env); if (!jsj_env) { if (err_msg) { jsj_LogError(err_msg); JS_smprintf_free(err_msg); } return; } JS_ASSERT(!err_msg); if (java_obj_reflections) { JSJ_HashTableEnumerateEntries(java_obj_reflections, enumerate_remove_java_obj, (void*)jsj_env); JSJ_HashTableDestroy(java_obj_reflections); java_obj_reflections = NULL; } } JSBool JavaObject_convert(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { JavaObjectWrapper *java_wrapper; JavaClassDescriptor *class_descriptor; jobject java_obj; JNIEnv *jEnv; JSJavaThreadState *jsj_env; JSBool result; java_wrapper = JS_GetPrivate(cx, obj); if (!java_wrapper) { if (type == JSTYPE_OBJECT) { *vp = OBJECT_TO_JSVAL(obj); return JS_TRUE; } JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_JOBJECT); return JS_FALSE; } java_obj = java_wrapper->java_obj; class_descriptor = java_wrapper->class_descriptor; switch (type) { case JSTYPE_OBJECT: *vp = OBJECT_TO_JSVAL(obj); return JS_TRUE; case JSTYPE_FUNCTION: JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_CONVERT_TO_FUNC); return JS_FALSE; case JSTYPE_VOID: case JSTYPE_STRING: /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; /* Either extract a C-string from the java.lang.String object or call the Java toString() method */ result = jsj_ConvertJavaObjectToJSString(cx, jEnv, class_descriptor, java_obj, vp); jsj_ExitJava(jsj_env); return result; case JSTYPE_NUMBER: /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; /* Call Java doubleValue() method, if applicable */ result = jsj_ConvertJavaObjectToJSNumber(cx, jEnv, class_descriptor, java_obj, vp); jsj_ExitJava(jsj_env); return result; case JSTYPE_BOOLEAN: /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; /* Call booleanValue() method, if applicable */ result = jsj_ConvertJavaObjectToJSBoolean(cx, jEnv, class_descriptor, java_obj, vp); jsj_ExitJava(jsj_env); return result; default: JS_ASSERT(0); return JS_FALSE; } } /* * Get a property from the prototype object of a native ECMA object, i.e. * return <js_constructor_name>.prototype.<member_name> * This is used to allow Java objects to inherit methods from Array.prototype * and String.prototype. */ static JSBool inherit_props_from_JS_natives(JSContext *cx, const char *js_constructor_name, const char *member_name, jsval *vp) { JSObject *global_obj, *constructor_obj, *prototype_obj; jsval constructor_val, prototype_val; global_obj = JS_GetGlobalObject(cx); JS_ASSERT(global_obj); if (!global_obj) return JS_FALSE; JS_GetProperty(cx, global_obj, js_constructor_name, &constructor_val); JS_ASSERT(JSVAL_IS_OBJECT(constructor_val)); constructor_obj = JSVAL_TO_OBJECT(constructor_val); JS_GetProperty(cx, constructor_obj, "prototype", &prototype_val); JS_ASSERT(JSVAL_IS_OBJECT(prototype_val)); prototype_obj = JSVAL_TO_OBJECT(prototype_val); return JS_GetProperty(cx, prototype_obj, member_name, vp) && *vp != JSVAL_VOID; } struct JSJPropertyInfo { JSBool wantProp; /* input param tells whether prop is returned */ const char* name; /* output param, name of property (XXX ASCII) */ uintN attributes; /* output param, attributes of property */ JSProperty *prop; /* output param, if wantProp, held pointer that must be released via OBJ_DROP_PROPERTY */ }; typedef struct JSJPropertyInfo JSJPropertyInfo; static JSBool lookup_member_by_id(JSContext *cx, JNIEnv *jEnv, JSObject *obj, JavaObjectWrapper **java_wrapperp, jsid id, JavaMemberDescriptor **member_descriptorp, jsval *vp, JSObject **proto_chainp, JSJPropertyInfo *prop_infop) { jsval idval; JavaObjectWrapper *java_wrapper; JavaMemberDescriptor *member_descriptor; const char *member_name; JavaClassDescriptor *class_descriptor; JSObject *proto_chain; JSBool found_in_proto; // This method accesses slots without using the JSAPI and these slots may // be stale if running on trace. Must run in the interpreter here. js_LeaveTrace(cx); found_in_proto = JS_FALSE; member_descriptor = NULL; java_wrapper = JS_GetPrivate(cx, obj); /* Handle accesses to prototype object */ if (!java_wrapper) { if (JS_IdToValue(cx, id, &idval) && JSVAL_IS_STRING(idval) && (member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval))) != NULL) { if (!strcmp(member_name, "constructor")) goto done; } JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_OP_JOBJECT); return JS_FALSE; } class_descriptor = java_wrapper->class_descriptor; JS_ASSERT(IS_REFERENCE_TYPE(class_descriptor->type)); member_descriptor = jsj_LookupJavaMemberDescriptorById(cx, jEnv, class_descriptor, id); if (member_descriptor) goto done; /* Instances can reference static methods and fields */ member_descriptor = jsj_LookupJavaStaticMemberDescriptorById(cx, jEnv, class_descriptor, id); if (member_descriptor) goto done; /* Ensure that the property we're searching for is string-valued. */ JS_IdToValue(cx, id, &idval); if (!JSVAL_IS_STRING(idval)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_JOBJECT_EXPR); return JS_FALSE; } member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); /* * A little LC3 feature magic: * + Instances of java.lang.String "inherit" the standard ECMA string methods * of String.prototype. All of the ECMA string methods convert the Java * string to a JS string before performing the string operation. For example, * s = new java.lang.String("foobar"); * return s.slice(2); * + Similarly, instances of Java arrays "inherit" the standard ECMA array * methods of Array.prototype. (Not all of these methods work properly * on JavaArray objects, however, since the 'length' property is read-only.) */ if (vp) { if ((class_descriptor->type == JAVA_SIGNATURE_JAVA_LANG_STRING) && inherit_props_from_JS_natives(cx, "String", member_name, vp)) goto done; if ((class_descriptor->type == JAVA_SIGNATURE_ARRAY) && inherit_props_from_JS_natives(cx, "Array", member_name, vp)) goto done; } /* Check for access to magic prototype chain property */ if (!strcmp(member_name, "__proto__")) { proto_chain = JS_GetPrototype(cx, obj); if (vp) *vp = OBJECT_TO_JSVAL(proto_chain); goto done; } /* * See if the property looks like the explicit resolution of an * overloaded method, e.g. "max(double,double)", first as an instance method, * then as a static method. If we find such a method, it will be cached * so future accesses won't run this code. */ member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_FALSE); if (member_descriptor) goto done; member_descriptor = jsj_ResolveExplicitMethod(cx, jEnv, class_descriptor, id, JS_TRUE); if (member_descriptor) goto done; /* Is the property defined in the prototype chain? */ if (proto_chainp && prop_infop) { /* If so, follow __proto__ link to search prototype chain */ proto_chain = JS_GetPrototype(cx, obj); /* Use OBJ_LOOKUP_PROPERTY to determine if and where the property actually exists in the prototype chain. */ if (proto_chain) { if (!OBJ_LOOKUP_PROPERTY(cx, proto_chain, id, proto_chainp, &prop_infop->prop)) { return JS_FALSE; } if (prop_infop->prop) { if (!OBJ_GET_ATTRIBUTES(cx, *proto_chainp, id, prop_infop->prop, &prop_infop->attributes)) { OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop); return JS_FALSE; } if (!prop_infop->wantProp) { OBJ_DROP_PROPERTY(cx, *proto_chainp, prop_infop->prop); prop_infop->prop = NULL; } prop_infop->name = member_name; found_in_proto = JS_TRUE; goto done; } } } /* Report lack of Java member with the given property name */ JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NO_INSTANCE_NAME, class_descriptor->name, member_name); return JS_FALSE; done: /* Success. Handle the multiple return values */ if (java_wrapperp) *java_wrapperp = java_wrapper; if (member_descriptorp) *member_descriptorp = member_descriptor; if (proto_chainp && !found_in_proto) *proto_chainp = NULL; return JS_TRUE; } JS_EXPORT_API(JSBool) JavaObject_getPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { jobject java_obj; JavaMemberDescriptor *member_descriptor; JavaObjectWrapper *java_wrapper; JNIEnv *jEnv; JSObject *funobj; jsval field_val, method_val; JSBool success; JSJavaThreadState *jsj_env; JSObject *proto_chain; JSJPropertyInfo prop_info; /* printf("In JavaObject_getProperty\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; if (vp) *vp = JSVAL_VOID; prop_info.wantProp = JS_FALSE; if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, vp, &proto_chain, &prop_info)) { jsj_ExitJava(jsj_env); return JS_FALSE; } /* Handle access to special, non-Java properties of JavaObjects, e.g. the "constructor" property of the prototype object */ if (!member_descriptor) { jsj_ExitJava(jsj_env); if (proto_chain) return JS_GetProperty(cx, proto_chain, prop_info.name, vp); return JS_TRUE; } java_obj = java_wrapper->java_obj; field_val = method_val = JSVAL_VOID; if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) { jsj_JSIsCallingApplet = JS_TRUE; } /* If a field member, get the value of the field */ if (member_descriptor->field) { success = jsj_GetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, &field_val); if (!success) { jsj_ExitJava(jsj_env); return JS_FALSE; } } /* If a method member, build a wrapper around the Java method */ if (member_descriptor->methods) { /* Create a function object with this JavaObject as its parent, so that JSFUN_BOUND_METHOD binds it as the default 'this' for the function. */ funobj = JS_CloneFunctionObject(cx, member_descriptor->invoke_func_obj, obj); if (!funobj) { jsj_ExitJava(jsj_env); return JS_FALSE; } method_val = OBJECT_TO_JSVAL(funobj); } #if TEST_JAVAMEMBER /* Always create a JavaMember object, even though it's inefficient */ obj = jsj_CreateJavaMember(cx, method_val, field_val); if (!obj) { jsj_ExitJava(jsj_env); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(obj); #else /* !TEST_JAVAMEMBER */ if (member_descriptor->field) { if (!member_descriptor->methods) { /* Return value of Java field */ *vp = field_val; } else { /* Handle special case of access to a property that could refer to either a Java field or a method that share the same name. In Java, such ambiguity is not possible because the compiler can statically determine which is being accessed. */ obj = jsj_CreateJavaMember(cx, method_val, field_val); if (!obj) { jsj_ExitJava(jsj_env); return JS_FALSE; } *vp = OBJECT_TO_JSVAL(obj); } } else { /* Return wrapper around Java method */ *vp = method_val; } #endif /* !TEST_JAVAMEMBER */ jsj_ExitJava(jsj_env); return JS_TRUE; } static JSBool JavaObject_setPropertyById(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { jobject java_obj; const char *member_name; JavaObjectWrapper *java_wrapper; JavaClassDescriptor *class_descriptor; JavaMemberDescriptor *member_descriptor; jsval idval; JNIEnv *jEnv; JSJavaThreadState *jsj_env; JSObject *proto_chain; JSJPropertyInfo prop_info; JSBool result; /* printf("In JavaObject_setProperty\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; prop_info.wantProp = JS_FALSE; if (!lookup_member_by_id(cx, jEnv, obj, &java_wrapper, id, &member_descriptor, NULL, &proto_chain, &prop_info)) { jsj_ExitJava(jsj_env); return JS_FALSE; } /* Could be assignment to magic JS __proto__ property rather than a Java field */ if (!member_descriptor) { if (proto_chain && (prop_info.attributes & JSPROP_SHARED)) { JS_SetProperty(cx, proto_chain, prop_info.name, vp); } else { JS_IdToValue(cx, id, &idval); if (!JSVAL_IS_STRING(idval)) goto no_such_field; member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); if (strcmp(member_name, "__proto__")) goto no_such_field; if (!JSVAL_IS_OBJECT(*vp)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_BAD_PROTO_ASSIGNMENT); jsj_ExitJava(jsj_env); return JS_FALSE; } JS_SetPrototype(cx, obj, JSVAL_TO_OBJECT(*vp)); } jsj_ExitJava(jsj_env); return JS_TRUE; } /* Check for the case where there is a method with the given name, but no field with that name */ if (!member_descriptor->field) goto no_such_field; /* Silently fail if field value is final (immutable), as required by ECMA spec */ if (member_descriptor->field->modifiers & ACC_FINAL) { jsj_ExitJava(jsj_env); return JS_TRUE; } java_obj = java_wrapper->java_obj; if (jaApplet && (*jEnv)->IsInstanceOf(jEnv, java_obj, jaApplet)) { jsj_JSIsCallingApplet = JS_TRUE; } result = jsj_SetJavaFieldValue(cx, jEnv, member_descriptor->field, java_obj, *vp); jsj_ExitJava(jsj_env); return result; no_such_field: JS_IdToValue(cx, id, &idval); member_name = JS_GetStringBytes(JSVAL_TO_STRING(idval)); class_descriptor = java_wrapper->class_descriptor; JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_NO_NAME_IN_CLASS, member_name, class_descriptor->name); jsj_ExitJava(jsj_env); return JS_FALSE; } static JSBool JavaObject_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, JSProperty **propp) { JNIEnv *jEnv; JSErrorReporter old_reporter; jsval dummy_val; JSObject *proto_chain; JSJPropertyInfo prop_info; JSJavaThreadState *jsj_env; /* printf("In JavaObject_lookupProperty()\n"); */ /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; old_reporter = JS_SetErrorReporter(cx, NULL); prop_info.wantProp = JS_TRUE; if (lookup_member_by_id(cx, jEnv, obj, NULL, id, NULL, &dummy_val, &proto_chain, &prop_info)) { /* signify that the property is in the prototype chain or the object itself. */ if (proto_chain) { *objp = proto_chain; *propp = prop_info.prop; } else { *objp = obj; *propp = (JSProperty*)1; } } else { *objp = NULL; *propp = NULL; } JS_SetErrorReporter(cx, old_reporter); jsj_ExitJava(jsj_env); return JS_TRUE; } static JSBool JavaObject_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, JSPropertyOp getter, JSPropertyOp setter, uintN attrs, JSProperty **propp) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JOBJECT_PROP_DEFINE); return JS_FALSE; } static JSBool JavaObject_getAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, uintN *attrsp) { /* We don't maintain JS property attributes for Java class members */ *attrsp = JSPROP_PERMANENT|JSPROP_ENUMERATE; return JS_TRUE; } static JSBool JavaObject_setAttributes(JSContext *cx, JSObject *obj, jsid id, JSProperty *prop, uintN *attrsp) { /* We don't maintain JS property attributes for Java class members */ if (*attrsp != (JSPROP_PERMANENT|JSPROP_ENUMERATE)) { JS_ASSERT(0); return JS_FALSE; } /* Silently ignore all setAttribute attempts */ return JS_TRUE; } static JSBool JavaObject_deleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { JSVersion version = JS_GetVersion(cx); *vp = JSVAL_FALSE; if (!JSVERSION_IS_ECMA(version)) { JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JOBJECT_PROP_DELETE); return JS_FALSE; } else { /* Attempts to delete permanent properties are silently ignored by ECMAScript. */ return JS_TRUE; } } static JSBool JavaObject_defaultValue(JSContext *cx, JSObject *obj, JSType type, jsval *vp) { /* printf("In JavaObject_defaultValue()\n"); */ return JavaObject_convert(cx, obj, type, vp); } static JSBool JavaObject_newEnumerate(JSContext *cx, JSObject *obj, JSIterateOp enum_op, jsval *statep, jsid *idp) { JavaObjectWrapper *java_wrapper; JavaMemberDescriptor *member_descriptor; JavaClassDescriptor *class_descriptor; JNIEnv *jEnv; JSJavaThreadState *jsj_env; java_wrapper = JS_GetPrivate(cx, obj); /* Check for prototype object */ if (!java_wrapper) { *statep = JSVAL_NULL; if (idp) *idp = INT_TO_JSVAL(0); return JS_TRUE; } class_descriptor = java_wrapper->class_descriptor; switch(enum_op) { case JSENUMERATE_INIT: /* Get the Java per-thread environment pointer for this JSContext */ jsj_env = jsj_EnterJava(cx, &jEnv); if (!jEnv) return JS_FALSE; member_descriptor = jsj_GetClassInstanceMembers(cx, jEnv, class_descriptor); *statep = PRIVATE_TO_JSVAL(member_descriptor); if (idp) *idp = INT_TO_JSVAL(class_descriptor->num_instance_members); jsj_ExitJava(jsj_env); return JS_TRUE; case JSENUMERATE_NEXT: member_descriptor = JSVAL_TO_PRIVATE(*statep); if (member_descriptor) { /* Don't enumerate explicit-signature methods, i.e. enumerate toValue, but not toValue(int), toValue(double), etc. */ while (member_descriptor->methods && member_descriptor->methods->is_alias) { member_descriptor = member_descriptor->next; if (!member_descriptor) { *statep = JSVAL_NULL; return JS_TRUE; } } *idp = member_descriptor->id; *statep = PRIVATE_TO_JSVAL(member_descriptor->next); return JS_TRUE; } /* Fall through ... */ case JSENUMERATE_DESTROY: *statep = JSVAL_NULL; return JS_TRUE; default: JS_ASSERT(0); return JS_FALSE; } } static JSBool JavaObject_checkAccess(JSContext *cx, JSObject *obj, jsid id, JSAccessMode mode, jsval *vp, uintN *attrsp) { switch (mode) { case JSACC_WATCH: JS_ReportErrorNumber(cx, jsj_GetErrorMessage, NULL, JSJMSG_JOBJECT_PROP_WATCH); return JS_FALSE; default: return JS_TRUE; } } #define JSJ_SLOT_COUNT (JSSLOT_PRIVATE+1) jsval jsj_wrapper_getRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot) { JS_ASSERT(slot < JSJ_SLOT_COUNT); return STOBJ_GET_SLOT(obj, slot); } JSBool jsj_wrapper_setRequiredSlot(JSContext *cx, JSObject *obj, uint32 slot, jsval v) { JS_ASSERT(slot < JSJ_SLOT_COUNT); STOBJ_SET_SLOT(obj, slot, v); return JS_TRUE; } extern JSObjectOps JavaObject_ops; static const JSObjectMap JavaObjectMap = { &JavaObject_ops }; JSObjectOps JavaObject_ops = { &JavaObjectMap, /* objectMap */ /* Mandatory non-null function pointer members. */ JavaObject_lookupProperty, JavaObject_defineProperty, JavaObject_getPropertyById, /* getProperty */ JavaObject_setPropertyById, /* setProperty */ JavaObject_getAttributes, JavaObject_setAttributes, JavaObject_deleteProperty, JavaObject_defaultValue, JavaObject_newEnumerate, JavaObject_checkAccess, /* Optionally non-null members start here. */ NULL, /* thisObject */ NULL, /* dropProperty */ NULL, /* call */ NULL, /* construct */ NULL, /* hasInstance */ NULL, /* trace */ NULL, /* clear */ jsj_wrapper_getRequiredSlot, /* getRequiredSlot */ jsj_wrapper_setRequiredSlot /* setRequiredSlot */ }; static JSObjectOps * JavaObject_getObjectOps(JSContext *cx, JSClass *clazz) { return &JavaObject_ops; } JSClass JavaObject_class = { "JavaObject", JSCLASS_HAS_PRIVATE, NULL, NULL, NULL, NULL, NULL, NULL, JavaObject_convert, JavaObject_finalize, /* Optionally non-null members start here. */ JavaObject_getObjectOps, NULL, /* checkAccess */ NULL, /* call */ NULL, /* construct */ NULL, /* xdrObject */ NULL, /* hasInstance */ NULL, /* mark */ 0, /* spare */ }; extern JS_IMPORT_DATA(JSObjectOps) js_ObjectOps; JSBool jsj_init_JavaObject(JSContext *cx, JSObject *global_obj) { return JS_InitClass(cx, global_obj, 0, &JavaObject_class, 0, 0, 0, 0, 0, 0) != 0; }