#include <CoreFoundation/CoreFoundation.h>
#include <assert.h>
#include <identifier.h>
#include <internal.h>
#include <interpreter.h>
#include <list.h>
#include <jni_jsobject.h>
#include <jni_runtime.h>
#include <jni_utility.h>
#include <runtime_object.h>
using namespace KJS::Bindings;
using namespace KJS;
#ifdef NDEBUG
#define JS_LOG(formatAndArgs...) ((void)0)
#else
#define JS_LOG(formatAndArgs...) { \
fprintf (stderr, "%s(%p,%p): ", __PRETTY_FUNCTION__, RootObject::runLoop(), CFRunLoopGetCurrent()); \
fprintf(stderr, formatAndArgs); \
}
#endif
#define UndefinedHandle 1
static bool isJavaScriptThread()
{
return (RootObject::runLoop() == CFRunLoopGetCurrent());
}
static CFMutableDictionaryRef referencesByRootDictionary = 0;
static CFMutableDictionaryRef getReferencesByRootDictionary()
{
if (!referencesByRootDictionary)
referencesByRootDictionary = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
return referencesByRootDictionary;
}
static CFMutableDictionaryRef getReferencesDictionary(const Bindings::RootObject *root)
{
CFMutableDictionaryRef refsByRoot = getReferencesByRootDictionary();
CFMutableDictionaryRef referencesDictionary = 0;
referencesDictionary = (CFMutableDictionaryRef)CFDictionaryGetValue (refsByRoot, (const void *)root);
if (!referencesDictionary) {
referencesDictionary = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
CFDictionaryAddValue (refsByRoot, root, referencesDictionary);
CFRelease (referencesDictionary);
}
return referencesDictionary;
}
static CFMutableDictionaryRef findReferenceDictionary(ObjectImp *imp)
{
CFMutableDictionaryRef refsByRoot = getReferencesByRootDictionary ();
CFMutableDictionaryRef foundDictionary = 0;
if (refsByRoot) {
const void **allValues = 0;
CFIndex count, i;
count = CFDictionaryGetCount(refsByRoot);
allValues = (const void **)malloc (sizeof(void *) * count);
CFDictionaryGetKeysAndValues (refsByRoot, NULL, allValues);
for(i = 0; i < count; i++) {
CFMutableDictionaryRef referencesDictionary = (CFMutableDictionaryRef)allValues[i];
if (CFDictionaryGetValue(referencesDictionary, imp) != 0) {
foundDictionary = referencesDictionary;
break;
}
}
free ((void *)allValues);
}
return foundDictionary;
}
static const Bindings::RootObject *rootForImp (ObjectImp *imp)
{
CFMutableDictionaryRef refsByRoot = getReferencesByRootDictionary ();
const Bindings::RootObject *rootObject = 0;
if (refsByRoot) {
const void **allValues = 0;
const void **allKeys = 0;
CFIndex count, i;
count = CFDictionaryGetCount(refsByRoot);
allKeys = (const void **)malloc (sizeof(void *) * count);
allValues = (const void **)malloc (sizeof(void *) * count);
CFDictionaryGetKeysAndValues (refsByRoot, allKeys, allValues);
for(i = 0; i < count; i++) {
CFMutableDictionaryRef referencesDictionary = (CFMutableDictionaryRef)allValues[i];
if (CFDictionaryGetValue(referencesDictionary, imp) != 0) {
rootObject = (const Bindings::RootObject *)allKeys[0];
break;
}
}
free ((void *)allKeys);
free ((void *)allValues);
}
return rootObject;
}
static void addJavaReference (const Bindings::RootObject *root, ObjectImp *imp)
{
JS_LOG ("root = %p, imp %p\n", root, imp);
CFMutableDictionaryRef referencesDictionary = getReferencesDictionary (root);
unsigned int numReferences = (unsigned int)CFDictionaryGetValue (referencesDictionary, imp);
if (numReferences == 0) {
imp->ref();
CFDictionaryAddValue (referencesDictionary, imp, (const void *)1);
}
else {
CFDictionaryReplaceValue (referencesDictionary, imp, (const void *)(numReferences+1));
}
}
static void removeJavaReference (ObjectImp *imp)
{
JS_LOG ("imp %p\n", imp);
CFMutableDictionaryRef referencesDictionary = findReferenceDictionary (imp);
unsigned int numReferences = (unsigned int)CFDictionaryGetValue (referencesDictionary, imp);
if (numReferences == 1) {
imp->deref();
CFDictionaryRemoveValue (referencesDictionary, imp);
}
else {
CFDictionaryReplaceValue (referencesDictionary, imp, (const void *)(numReferences-1));
}
}
static CFRunLoopSourceRef completionSource;
static void performJavaScriptAccess(void *info);
static void performJavaScriptAccess(void *i)
{
assert (CFRunLoopGetCurrent() == RootObject::runLoop());
CFRunLoopSourceContext sourceContext;
CFRunLoopSourceGetContext (completionSource, &sourceContext);
JSObjectCallContext *callContext = (JSObjectCallContext *)sourceContext.info;
CFRunLoopRef originatingLoop = callContext->originatingLoop;
JSObject::invoke (callContext);
CFRunLoopSourceSignal (completionSource);
if (CFRunLoopIsWaiting(originatingLoop)) {
CFRunLoopWakeUp(originatingLoop);
}
}
static void completedJavaScriptAccess (void *i);
static void completedJavaScriptAccess (void *i)
{
assert (CFRunLoopGetCurrent() != RootObject::runLoop());
JSObjectCallContext *callContext = (JSObjectCallContext *)i;
CFRunLoopRef runLoop = (CFRunLoopRef)callContext->originatingLoop;
assert (CFRunLoopGetCurrent() == runLoop);
CFRunLoopStop(runLoop);
}
static pthread_once_t javaScriptAccessLockOnce = PTHREAD_ONCE_INIT;
static pthread_mutex_t javaScriptAccessLock;
static int javaScriptAccessLockCount = 0;
static void initializeJavaScriptAccessLock()
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&javaScriptAccessLock, &attr);
}
static inline void lockJavaScriptAccess()
{
pthread_once(&javaScriptAccessLockOnce, initializeJavaScriptAccessLock);
pthread_mutex_lock(&javaScriptAccessLock);
javaScriptAccessLockCount++;
}
static inline void unlockJavaScriptAccess()
{
javaScriptAccessLockCount--;
pthread_mutex_unlock(&javaScriptAccessLock);
}
static void dispatchToJavaScriptThread(JSObjectCallContext *context)
{
lockJavaScriptAccess();
CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();
assert (currentRunLoop != RootObject::runLoop());
context->originatingLoop = currentRunLoop;
CFRunLoopSourceContext sourceContext = {0, context, NULL, NULL, NULL, NULL, NULL, NULL, NULL, completedJavaScriptAccess};
completionSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
CFRunLoopAddSource(currentRunLoop, completionSource, kCFRunLoopDefaultMode);
CFRunLoopSourceSignal(RootObject::performJavaScriptSource());
if (CFRunLoopIsWaiting(RootObject::runLoop())) {
CFRunLoopWakeUp(RootObject::runLoop());
}
CFRunLoopRun ();
CFRunLoopRemoveSource(currentRunLoop, completionSource, kCFRunLoopDefaultMode);
CFRelease (completionSource);
unlockJavaScriptAccess();
}
FindRootObjectForNativeHandleFunctionPtr RootObject::_findRootObjectForNativeHandleFunctionPtr = 0;
CFRunLoopRef RootObject::_runLoop = 0;
CFRunLoopSourceRef RootObject::_performJavaScriptSource = 0;
void RootObject::setFindRootObjectForNativeHandleFunction(FindRootObjectForNativeHandleFunctionPtr aFunc) {
assert (_findRootObjectForNativeHandleFunctionPtr == 0);
_findRootObjectForNativeHandleFunctionPtr = aFunc;
_runLoop = (CFRunLoopRef)CFRetain (CFRunLoopGetCurrent ());
CFRunLoopSourceContext sourceContext = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, performJavaScriptAccess};
_performJavaScriptSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
CFRunLoopAddSource(_runLoop, _performJavaScriptSource, kCFRunLoopDefaultMode);
}
void RootObject::removeAllJavaReferencesForRoot (Bindings::RootObject *root)
{
JS_LOG ("_root == %p\n", root);
CFMutableDictionaryRef referencesDictionary = getReferencesDictionary (root);
if (referencesDictionary) {
void **allImps = 0;
CFIndex count, i;
count = CFDictionaryGetCount(referencesDictionary);
allImps = (void **)malloc (sizeof(void *) * count);
CFDictionaryGetKeysAndValues (referencesDictionary, (const void **)allImps, NULL);
for(i = 0; i < count; i++) {
ObjectImp *anImp = static_cast<ObjectImp*>(allImps[i]);
anImp->deref();
}
free ((void *)allImps);
CFDictionaryRemoveAllValues (referencesDictionary);
CFMutableDictionaryRef refsByRoot = getReferencesByRootDictionary();
CFDictionaryRemoveValue (refsByRoot, (const void *)root);
delete root;
}
}
jvalue JSObject::invoke (JSObjectCallContext *context)
{
jvalue result;
if (!isJavaScriptThread()) {
dispatchToJavaScriptThread(context);
result = context->result;
}
else {
jlong nativeHandle = context->nativeHandle;
if (nativeHandle == UndefinedHandle || nativeHandle == 0) {
bzero ((void *)&result, sizeof(jvalue));
return result;
}
switch (context->type){
case CreateNative: {
result.j = JSObject::createNative(nativeHandle);
break;
}
case Call: {
result.l = JSObject(nativeHandle).call(context->string, context->args);
break;
}
case Eval: {
result.l = JSObject(nativeHandle).eval(context->string);
break;
}
case GetMember: {
result.l = JSObject(nativeHandle).getMember(context->string);
break;
}
case SetMember: {
JSObject(nativeHandle).setMember(context->string, context->value);
break;
}
case RemoveMember: {
JSObject(nativeHandle).removeMember(context->string);
break;
}
case GetSlot: {
result.l = JSObject(nativeHandle).getSlot(context->index);
break;
}
case SetSlot: {
JSObject(nativeHandle).setSlot(context->index, context->value);
break;
}
case ToString: {
result.l = (jobject) JSObject(nativeHandle).toString();
break;
}
case Finalize: {
ObjectImp *imp = jlong_to_impptr(nativeHandle);
if (findReferenceDictionary(imp) == 0) {
JS_LOG ("finalize called on instance we have already removed.\n");
}
else {
JSObject(nativeHandle).finalize();
}
break;
}
default: {
fprintf (stderr, "%s: invalid JavaScript call\n", __PRETTY_FUNCTION__);
}
}
context->result = result;
}
return result;
}
JSObject::JSObject(jlong nativeJSObject)
{
_imp = jlong_to_impptr(nativeJSObject);
assert (_imp != 0);
_root = rootForImp(_imp);
assert (_root != 0);
}
jobject JSObject::call(jstring methodName, jobjectArray args) const
{
JS_LOG ("methodName = %s\n", JavaString(methodName).UTF8String());
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
Identifier identifier(JavaString(methodName).ustring());
Value func = _imp->get (exec, identifier);
Interpreter::unlock();
if (func.isNull() || func.type() == UndefinedType) {
return 0;
}
ObjectImp *funcImp = static_cast<ObjectImp*>(func.imp());
Object thisObj = Object(const_cast<ObjectImp*>(_imp));
List argList = listFromJArray(args);
Interpreter::lock();
Value result = funcImp->call (exec, thisObj, argList);
Interpreter::unlock();
return convertValueToJObject (result);
}
jobject JSObject::eval(jstring script) const
{
JS_LOG ("script = %s\n", JavaString(script).UTF8String());
Object thisObj = Object(const_cast<ObjectImp*>(_imp));
Interpreter::lock();
KJS::Value result = _root->interpreter()->evaluate(JavaString(script).ustring(),thisObj).value();
Interpreter::unlock();
return convertValueToJObject (result);
}
jobject JSObject::getMember(jstring memberName) const
{
JS_LOG ("(%p) memberName = %s\n", _imp, JavaString(memberName).UTF8String());
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
Value result = _imp->get (exec, Identifier (JavaString(memberName).ustring()));
Interpreter::unlock();
return convertValueToJObject (result);
}
void JSObject::setMember(jstring memberName, jobject value) const
{
JS_LOG ("memberName = %s, value = %p\n", JavaString(memberName).UTF8String(), value);
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
_imp->put (exec, Identifier (JavaString(memberName).ustring()), convertJObjectToValue(value));
Interpreter::unlock();
}
void JSObject::removeMember(jstring memberName) const
{
JS_LOG ("memberName = %s\n", JavaString(memberName).UTF8String());
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
_imp->deleteProperty (exec, Identifier (JavaString(memberName).ustring()));
Interpreter::unlock();
}
jobject JSObject::getSlot(jint index) const
{
JS_LOG ("index = %d\n", index);
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
Value result = _imp->get (exec, (unsigned)index);
Interpreter::unlock();
return convertValueToJObject (result);
}
void JSObject::setSlot(jint index, jobject value) const
{
JS_LOG ("index = %d, value = %p\n", index, value);
ExecState *exec = _root->interpreter()->globalExec();
Interpreter::lock();
_imp->put (exec, (unsigned)index, convertJObjectToValue(value));
Interpreter::unlock();
}
jstring JSObject::toString() const
{
JS_LOG ("\n");
Object thisObj = Object(const_cast<ObjectImp*>(_imp));
ExecState *exec = _root->interpreter()->globalExec();
return (jstring)convertValueToJValue (exec, thisObj, object_type, "java.lang.String").l;
}
void JSObject::finalize() const
{
JS_LOG ("\n");
removeJavaReference (_imp);
}
jlong JSObject::createNative(jlong nativeHandle)
{
JS_LOG ("nativeHandle = %d\n", (int)nativeHandle);
if (nativeHandle == UndefinedHandle)
return nativeHandle;
else if (rootForImp(jlong_to_impptr(nativeHandle))){
return nativeHandle;
}
FindRootObjectForNativeHandleFunctionPtr aFunc = RootObject::findRootObjectForNativeHandleFunction();
if (aFunc) {
Bindings::RootObject *root = aFunc(jlong_to_ptr(nativeHandle));
if (root) {
addJavaReference (root, root->rootObjectImp());
return ptr_to_jlong(root->rootObjectImp());
}
else {
return nativeHandle;
}
}
return ptr_to_jlong(0);
}
jobject JSObject::convertValueToJObject (KJS::Value value) const
{
ExecState *exec = _root->interpreter()->globalExec();
JNIEnv *env = getJNIEnv();
jobject result = 0;
KJS::Type type = value.type();
if (type == KJS::NumberType) {
jclass JSObjectClass = env->FindClass ("java/lang/Double");
jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(D)V");
if (constructorID != NULL) {
result = env->NewObject (JSObjectClass, constructorID, (jdouble)value.toNumber(exec));
}
}
else if (type == KJS::StringType) {
KJS::UString stringValue = value.toString(exec);
JNIEnv *env = getJNIEnv();
result = env->NewString ((const jchar *)stringValue.data(), stringValue.size());
}
else if (type == KJS::BooleanType) {
jclass JSObjectClass = env->FindClass ("java/lang/Boolean");
jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(Z)V");
if (constructorID != NULL) {
result = env->NewObject (JSObjectClass, constructorID, (jboolean)value.toBoolean(exec));
}
}
else {
jlong nativeHandle;
if (type == KJS::ObjectType){
KJS::ObjectImp *imp = static_cast<KJS::ObjectImp*>(value.imp());
if (imp->classInfo() && strcmp(imp->classInfo()->className, "RuntimeObject") == 0) {
KJS::RuntimeObjectImp *runtimeImp = static_cast<KJS::RuntimeObjectImp*>(value.imp());
Bindings::JavaInstance *runtimeInstance = static_cast<Bindings::JavaInstance *>(runtimeImp->getInternalInstance());
return runtimeInstance->javaInstance();
}
else {
nativeHandle = ptr_to_jlong(imp);
addJavaReference (_root, imp);
}
}
else {
nativeHandle = UndefinedHandle;
}
jclass JSObjectClass = env->FindClass ("apple/applet/JSObject");
jmethodID constructorID = env->GetMethodID (JSObjectClass, "<init>", "(J)V");
if (constructorID != NULL) {
result = env->NewObject (JSObjectClass, constructorID, nativeHandle);
}
}
return result;
}
KJS::Value JSObject::convertJObjectToValue (jobject theObject) const
{
jobject classOfInstance = callJNIObjectMethod(theObject, "getClass", "()Ljava/lang/Class;");
jstring className = (jstring)callJNIObjectMethod(classOfInstance, "getName", "()Ljava/lang/String;");
JS_LOG ("converting instance of class %s\n", Bindings::JavaString(className).UTF8String());
if (strcmp(Bindings::JavaString(className).UTF8String(), "netscape.javascript.JSObject") == 0) {
JNIEnv *env = getJNIEnv();
jfieldID fieldID = env->GetFieldID((jclass)classOfInstance, "nativeJSObject", "long");
if (fieldID == NULL) {
return KJS::Undefined();
}
jlong nativeHandle = env->GetLongField(theObject, fieldID);
if (nativeHandle == UndefinedHandle) {
return KJS::Undefined();
}
KJS::ObjectImp *imp = static_cast<KJS::ObjectImp*>(jlong_to_impptr(nativeHandle));
return KJS::Object(const_cast<KJS::ObjectImp*>(imp));
}
Interpreter::lock();
KJS::RuntimeObjectImp *newImp = new KJS::RuntimeObjectImp(new Bindings::JavaInstance (theObject));
Interpreter::unlock();
return KJS::Object(newImp);
}
KJS::List JSObject::listFromJArray(jobjectArray jArray) const
{
JNIEnv *env = getJNIEnv();
long i, numObjects = jArray ? env->GetArrayLength (jArray) : 0;
KJS::List aList;
for (i = 0; i < numObjects; i++) {
jobject anObject = env->GetObjectArrayElement ((jobjectArray)jArray, i);
aList.append (convertJObjectToValue(anObject));
env->DeleteLocalRef (anObject);
}
return aList;
}
extern "C" {
jlong KJS_JSCreateNativeJSObject (JNIEnv *env, jclass clazz, jstring jurl, jlong nativeHandle, jboolean ctx)
{
JSObjectCallContext context;
context.type = CreateNative;
context.nativeHandle = nativeHandle;
return JSObject::invoke (&context).j;
}
void KJS_JSObject_JSFinalize (JNIEnv *env, jclass jsClass, jlong nativeHandle)
{
JSObjectCallContext context;
context.type = Finalize;
context.nativeHandle = nativeHandle;
JSObject::invoke (&context);
}
jobject KJS_JSObject_JSObjectCall (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jstring methodName, jobjectArray args, jboolean ctx)
{
JSObjectCallContext context;
context.type = Call;
context.nativeHandle = nativeHandle;
context.string = methodName;
context.args = args;
return JSObject::invoke (&context).l;
}
jobject KJS_JSObject_JSObjectEval (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jstring jscript, jboolean ctx)
{
JSObjectCallContext context;
context.type = Eval;
context.nativeHandle = nativeHandle;
context.string = jscript;
return JSObject::invoke (&context).l;
}
jobject KJS_JSObject_JSObjectGetMember (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jstring jname, jboolean ctx)
{
JSObjectCallContext context;
context.type = GetMember;
context.nativeHandle = nativeHandle;
context.string = jname;
return JSObject::invoke (&context).l;
}
void KJS_JSObject_JSObjectSetMember (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jstring jname, jobject value, jboolean ctx)
{
JSObjectCallContext context;
context.type = SetMember;
context.nativeHandle = nativeHandle;
context.string = jname;
context.value = value;
JSObject::invoke (&context);
}
void KJS_JSObject_JSObjectRemoveMember (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jstring jname, jboolean ctx)
{
JSObjectCallContext context;
context.type = RemoveMember;
context.nativeHandle = nativeHandle;
context.string = jname;
JSObject::invoke (&context);
}
jobject KJS_JSObject_JSObjectGetSlot (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jint jindex, jboolean ctx)
{
JSObjectCallContext context;
context.type = GetSlot;
context.nativeHandle = nativeHandle;
context.index = jindex;
return JSObject::invoke (&context).l;
}
void KJS_JSObject_JSObjectSetSlot (JNIEnv *env, jclass jsClass, jlong nativeHandle, jstring jurl, jint jindex, jobject value, jboolean ctx)
{
JSObjectCallContext context;
context.type = SetSlot;
context.nativeHandle = nativeHandle;
context.index = jindex;
context.value = value;
JSObject::invoke (&context);
}
jstring KJS_JSObject_JSObjectToString (JNIEnv *env, jclass clazz, jlong nativeHandle)
{
JSObjectCallContext context;
context.type = ToString;
context.nativeHandle = nativeHandle;
return (jstring)JSObject::invoke (&context).l;
}
}