| /* | |
| * Copyright 2008 Google Inc. | |
| * | |
| * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| * License for the specific language governing permissions and limitations under | |
| * the License. | |
| */ | |
| #include <string> | |
| #include <sstream> | |
| #include "jni.h" | |
| #include "java-dispatch.h" | |
| #include "trace.h" | |
| #include "JStringWrap.h" | |
| namespace gwt { | |
| /* | |
| * Declarations for private functions. | |
| */ | |
| JSClassRef DispatchObjectClassCreate(); | |
| JSClassRef DispatchMethodClassCreate(); | |
| JSValueRef DispatchObjectGetProperty(JSContextRef, JSObjectRef, JSStringRef, | |
| JSValueRef*); | |
| JSValueRef DispatchObjectToString(JSContextRef, JSObjectRef, JSObjectRef, | |
| size_t, const JSValueRef*, JSValueRef*); | |
| bool DispatchObjectSetProperty(JSContextRef, JSObjectRef, JSStringRef, | |
| JSValueRef, JSValueRef*); | |
| void DispatchObjectFinalize(JSObjectRef); | |
| JSValueRef DispatchMethodCallAsFunction(JSContextRef, JSObjectRef, | |
| JSObjectRef, size_t, | |
| const JSValueRef*, JSValueRef*); | |
| JSValueRef DispatchMethodGetToString(JSContextRef, JSObjectRef, JSStringRef, | |
| JSValueRef*); | |
| JSValueRef DispatchMethodToString(JSContextRef, JSObjectRef, JSObjectRef, | |
| size_t, const JSValueRef*, JSValueRef*); | |
| void DispatchMethodFinalize(JSObjectRef); | |
| /* | |
| * Call this when an underlying Java Object should be freed. | |
| */ | |
| void ReleaseJavaObject(jobject jObject); | |
| /* | |
| * The class definition stuct for DispatchObjects. | |
| */ | |
| static JSClassDefinition _dispatchObjectClassDef = { 0, | |
| kJSClassAttributeNone, "DispatchObject", 0, 0, 0, 0, | |
| DispatchObjectFinalize, 0, DispatchObjectGetProperty, | |
| DispatchObjectSetProperty, 0, 0, 0, 0, 0, 0 }; | |
| /* | |
| * The class definition structs for DispatchMethods. | |
| */ | |
| static JSStaticValue _dispatchMethodStaticValues[] = { | |
| { "toString", DispatchMethodGetToString, 0, kJSPropertyAttributeNone }, | |
| { 0, 0, 0, 0 } | |
| }; | |
| static JSClassDefinition _dispatchMethodClassDef = { 0, | |
| kJSClassAttributeNoAutomaticPrototype, "DispatchMethod", 0, | |
| _dispatchMethodStaticValues, 0, 0, DispatchMethodFinalize, 0, 0, 0, 0, | |
| 0, DispatchMethodCallAsFunction, 0, 0, 0 }; | |
| /* | |
| * The classes used to create DispatchObjects and DispatchMethods. | |
| */ | |
| static JSClassRef _dispatchObjectClass = DispatchObjectClassCreate(); | |
| static JSClassRef _dispatchMethodClass = DispatchMethodClassCreate(); | |
| /* | |
| * Java class and method references needed to do delegation. | |
| */ | |
| /* | |
| * The main JVM, used by foreign threads to attach. | |
| */ | |
| static JavaVM* _javaVM; | |
| /* | |
| * Only valid for the main thread! WebKit can finalized on a foreign thread. | |
| */ | |
| static JNIEnv* _javaEnv; | |
| static jclass _javaDispatchObjectClass; | |
| static jclass _javaDispatchMethodClass; | |
| static jclass _lowLevelSafClass; | |
| static jmethodID _javaDispatchObjectSetFieldMethod; | |
| static jmethodID _javaDispatchObjectGetFieldMethod; | |
| static jmethodID _javaDispatchMethodInvokeMethod; | |
| static jmethodID _javaDispatchObjectToStringMethod; | |
| static jmethodID _lowLevelSafReleaseObject; | |
| /* | |
| * Structure to hold DispatchMethod private data. | |
| * | |
| * NOTE: utf8Name is defensively copied. | |
| */ | |
| class DispatchMethodData { | |
| public: | |
| DispatchMethodData(jobject jObject, std::string& utf8Name) | |
| : _jObject(jObject), _utf8Name(utf8Name) { } | |
| ~DispatchMethodData() { | |
| ReleaseJavaObject(_jObject); | |
| } | |
| jobject _jObject; | |
| std::string _utf8Name; | |
| }; | |
| /* | |
| * The following takes the prototype from the Function constructor, this allows | |
| * us to easily support call and apply on our objects that support CallAsFunction. | |
| * | |
| * NOTE: The return value is not protected. | |
| */ | |
| JSValueRef GetFunctionPrototype(JSContextRef jsContext, JSValueRef* exception) { | |
| TR_ENTER(); | |
| JSObjectRef globalObject = JSContextGetGlobalObject(jsContext); | |
| JSStringRef fnPropName= JSStringCreateWithUTF8CString("Function"); | |
| JSValueRef fnCtorValue = JSObjectGetProperty(jsContext, globalObject, | |
| fnPropName, exception); | |
| JSStringRelease(fnPropName); | |
| if (!fnCtorValue) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| JSObjectRef fnCtorObject = JSValueToObject(jsContext, fnCtorValue, exception); | |
| if (!fnCtorObject) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| JSStringRef protoPropName = JSStringCreateWithUTF8CString("prototype"); | |
| JSValueRef fnPrototype = JSObjectGetProperty(jsContext, fnCtorObject, | |
| protoPropName, exception); | |
| JSStringRelease(protoPropName); | |
| if (!fnPrototype) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| TR_LEAVE(); | |
| return fnPrototype; | |
| } | |
| /* | |
| * | |
| */ | |
| JSClassRef GetDispatchObjectClass() { | |
| TR_ENTER(); | |
| TR_LEAVE(); | |
| return _dispatchObjectClass; | |
| } | |
| /* | |
| * | |
| */ | |
| JSClassRef GetDispatchMethodClass() { | |
| TR_ENTER(); | |
| TR_LEAVE(); | |
| return _dispatchMethodClass; | |
| } | |
| /* | |
| * | |
| */ | |
| JSClassRef DispatchObjectClassCreate() { | |
| TR_ENTER(); | |
| JSClassRef dispClass = JSClassCreate(&_dispatchObjectClassDef); | |
| JSClassRetain(dispClass); | |
| TR_LEAVE(); | |
| return dispClass; | |
| } | |
| /* | |
| * | |
| */ | |
| JSClassRef DispatchMethodClassCreate() { | |
| TR_ENTER(); | |
| JSClassRef dispClass = JSClassCreate(&_dispatchMethodClassDef); | |
| JSClassRetain(dispClass); | |
| TR_LEAVE(); | |
| return dispClass; | |
| } | |
| /* | |
| * NOTE: The object returned from this function is not protected. | |
| */ | |
| JSObjectRef DispatchObjectCreate(JSContextRef jsContext, jobject jObject) { | |
| TR_ENTER(); | |
| JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchObjectClass, | |
| _javaEnv->NewGlobalRef(jObject)); | |
| TR_LEAVE(); | |
| return dispInst; | |
| } | |
| /* | |
| * NOTE: The object returned from this function is not protected. | |
| */ | |
| JSObjectRef DispatchMethodCreate(JSContextRef jsContext, std::string& name, | |
| jobject jObject) { | |
| TR_ENTER(); | |
| JSObjectRef dispInst = JSObjectMake(jsContext, _dispatchMethodClass, | |
| new DispatchMethodData(_javaEnv->NewGlobalRef(jObject), name)); | |
| // This could only be cached relative to jsContext. | |
| JSValueRef fnProtoValue = GetFunctionPrototype(jsContext, NULL); | |
| JSObjectSetPrototype(jsContext, dispInst, fnProtoValue); | |
| TR_LEAVE(); | |
| return dispInst; | |
| } | |
| /* | |
| * NOTE: The value returned from this function is not protected, but all | |
| * JSValues that are passed into Java are protected before the invocation. | |
| */ | |
| JSValueRef DispatchObjectGetProperty(JSContextRef jsContext, | |
| JSObjectRef jsObject, JSStringRef jsPropertyName, | |
| JSValueRef* jsException) { | |
| TR_ENTER(); | |
| // If you call toString on a DispatchObject, you should get the results | |
| // of the java object's toString invcation. | |
| if (JSStringIsEqualToUTF8CString(jsPropertyName, "toString")) { | |
| JSObjectRef jsFunction = JSObjectMakeFunctionWithCallback(jsContext, | |
| jsPropertyName, DispatchObjectToString); | |
| return jsFunction; | |
| } | |
| // The class check is omitted because it should not be possible to tear off | |
| // a getter. | |
| jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | |
| jstring jPropertyName = _javaEnv->NewString( | |
| static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)), | |
| static_cast<jsize>(JSStringGetLength(jsPropertyName))); | |
| if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) { | |
| TR_FAIL(); | |
| _javaEnv->ExceptionClear(); | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| JSValueRef jsResult = reinterpret_cast<JSValueRef>( | |
| _javaEnv->CallIntMethod(jObject, _javaDispatchObjectGetFieldMethod, | |
| reinterpret_cast<jint>(jsContext), | |
| jPropertyName)); | |
| if (!jsResult || _javaEnv->ExceptionCheck()) { | |
| TR_FAIL(); | |
| _javaEnv->ExceptionClear(); | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| // Java left us an extra reference to eat. | |
| JSValueUnprotectChecked(jsContext, jsResult); | |
| TR_LEAVE(); | |
| return jsResult; | |
| } | |
| /* | |
| * | |
| */ | |
| bool DispatchObjectSetProperty(JSContextRef jsContext, JSObjectRef jsObject, | |
| JSStringRef jsPropertyName, JSValueRef jsValue, JSValueRef* jsException) { | |
| TR_ENTER(); | |
| // The class check is omitted because it should not be possible to tear off | |
| // a getter. | |
| jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | |
| jstring jPropertyName = _javaEnv->NewString( | |
| static_cast<const jchar*>(JSStringGetCharactersPtr(jsPropertyName)), | |
| static_cast<jsize>(JSStringGetLength(jsPropertyName))); | |
| if (!jObject || !jPropertyName || _javaEnv->ExceptionCheck()) { | |
| _javaEnv->ExceptionClear(); | |
| return false; | |
| } | |
| JSValueProtectChecked(jsContext, jsValue); | |
| _javaEnv->CallIntMethod(jObject, _javaDispatchObjectSetFieldMethod, | |
| reinterpret_cast<jint>(jsContext), jPropertyName, | |
| reinterpret_cast<jint>(jsValue)); | |
| if (_javaEnv->ExceptionCheck()) { | |
| _javaEnv->ExceptionClear(); | |
| return false; | |
| } | |
| TR_LEAVE(); | |
| return true; | |
| } | |
| /* | |
| * | |
| */ | |
| void DispatchObjectFinalize(JSObjectRef jsObject) { | |
| TR_ENTER(); | |
| jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsObject)); | |
| ReleaseJavaObject(jObject); | |
| TR_LEAVE(); | |
| } | |
| /* | |
| * | |
| */ | |
| void DispatchMethodFinalize(JSObjectRef jsObject) { | |
| TR_ENTER(); | |
| DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | |
| JSObjectGetPrivate(jsObject)); | |
| delete data; | |
| TR_LEAVE(); | |
| } | |
| /* | |
| * NOTE: The value returned from this function is not protected. | |
| */ | |
| JSValueRef DispatchObjectToString(JSContextRef jsContext, JSObjectRef, | |
| JSObjectRef jsThis, size_t, const JSValueRef*, JSValueRef*) { | |
| TR_ENTER(); | |
| // This function cannot be torn off and applied to any JSValue. If this does | |
| // not reference a DispatchObject, return undefined. | |
| if (!JSValueIsObjectOfClass(jsContext, jsThis, GetDispatchObjectClass())) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| jobject jObject = reinterpret_cast<jobject>(JSObjectGetPrivate(jsThis)); | |
| jstring jResult = reinterpret_cast<jstring>( | |
| _javaEnv->CallObjectMethod(jObject, _javaDispatchObjectToStringMethod)); | |
| if (_javaEnv->ExceptionCheck()) { | |
| return JSValueMakeUndefined(jsContext); | |
| } else if (!jResult) { | |
| return JSValueMakeNull(jsContext); | |
| } else { | |
| JStringWrap result(_javaEnv, jResult); | |
| JSStringRef resultString = JSStringCreateWithCharacters( | |
| static_cast<const JSChar*>(result.jstr()), | |
| static_cast<size_t>(result.length())); | |
| JSValueRef jsResultString = JSValueMakeString(jsContext, resultString); | |
| JSStringRelease(resultString); | |
| return jsResultString; | |
| } | |
| TR_LEAVE(); | |
| } | |
| /* | |
| * | |
| */ | |
| JSValueRef DispatchMethodCallAsFunction(JSContextRef jsContext, | |
| JSObjectRef jsFunction, JSObjectRef jsThis, size_t argumentCount, | |
| const JSValueRef arguments[], JSValueRef* exception) { | |
| TR_ENTER(); | |
| // We don't need to check the class here because we take the private | |
| // data from jsFunction and not jsThis. | |
| DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | |
| JSObjectGetPrivate(jsFunction)); | |
| jobject jObject = data->_jObject; | |
| jintArray jArguments = _javaEnv->NewIntArray(argumentCount); | |
| if (!jArguments || _javaEnv->ExceptionCheck()) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| // This single element int array will be passed into the java call to allow the | |
| // called java method to raise an exception. We will check for a non-null value | |
| // after the call is dispatched. | |
| jintArray jException = _javaEnv->NewIntArray(1); | |
| if (!jException || _javaEnv->ExceptionCheck()) { | |
| return JNI_FALSE; | |
| } | |
| for (size_t i = 0; i < argumentCount; ++i) { | |
| JSValueRef arg = arguments[i]; | |
| // Java will take ownership of the arguments. | |
| JSValueProtectChecked(jsContext, arg); | |
| _javaEnv->SetIntArrayRegion(jArguments, i, 1, reinterpret_cast<jint*>(&arg)); | |
| if (_javaEnv->ExceptionCheck()) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| } | |
| // Java will take ownership of this. | |
| JSValueProtectChecked(jsContext, jsThis); | |
| JSValueRef jsResult = reinterpret_cast<JSValueRef>(_javaEnv->CallIntMethod(jObject, | |
| _javaDispatchMethodInvokeMethod, reinterpret_cast<jint>(jsContext), | |
| reinterpret_cast<jint>(jsThis), jArguments, jException)); | |
| if (_javaEnv->ExceptionCheck()) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| JSValueRef jsException = NULL; | |
| _javaEnv->GetIntArrayRegion(jException, 0, 1, reinterpret_cast<jint*>(&jsException)); | |
| if (!_javaEnv->ExceptionCheck() && jsException) { | |
| // If the java dispatch set an exception, then we pass it back to our caller. | |
| if (exception) { | |
| *exception = jsException; | |
| } | |
| // Java left us an extra reference to eat. | |
| JSValueUnprotectChecked(jsContext, jsException); | |
| } | |
| // Java left us an extra reference to eat. | |
| JSValueUnprotectChecked(jsContext, jsResult); | |
| TR_LEAVE(); | |
| return jsResult; | |
| } | |
| /* | |
| * NOTE: The object returned from this function is not protected. | |
| */ | |
| JSValueRef DispatchMethodToString(JSContextRef jsContext, JSObjectRef, | |
| JSObjectRef thisObject, size_t, const JSValueRef*, JSValueRef*) { | |
| TR_ENTER(); | |
| // This function cannot be torn off and applied to any JSValue. If this does | |
| // not reference a DispatchMethod, return undefined. | |
| if (!JSValueIsObjectOfClass(jsContext, thisObject, GetDispatchMethodClass())) { | |
| return JSValueMakeUndefined(jsContext); | |
| } | |
| std::ostringstream ss; | |
| DispatchMethodData* data = reinterpret_cast<DispatchMethodData*>( | |
| JSObjectGetPrivate(thisObject)); | |
| ss << "function " << data->_utf8Name << "() {\n [native code]\n}\n"; | |
| JSStringRef stringRep = JSStringCreateWithUTF8CString(ss.str().c_str()); | |
| JSValueRef jsStringRep = JSValueMakeString(jsContext, stringRep); | |
| JSStringRelease(stringRep); | |
| TR_LEAVE(); | |
| return jsStringRep; | |
| } | |
| /* | |
| * NOTE: The object returned from this function is not protected. | |
| */ | |
| JSValueRef DispatchMethodGetToString(JSContextRef jsContext, | |
| JSObjectRef jsObject, JSStringRef jsPropertyName, JSValueRef* jsException) { | |
| TR_ENTER(); | |
| JSObjectRef toStringFn = JSObjectMakeFunctionWithCallback(jsContext, | |
| jsPropertyName, DispatchMethodToString); | |
| TR_LEAVE(); | |
| return toStringFn; | |
| } | |
| /* | |
| * | |
| */ | |
| bool Initialize(JNIEnv* javaEnv, jclass javaDispatchObjectClass, | |
| jclass javaDispatchMethodClass, jclass lowLevelSafClass) { | |
| TR_ENTER(); | |
| if (!javaEnv || !javaDispatchObjectClass || !javaDispatchMethodClass | |
| || !lowLevelSafClass) { | |
| return false; | |
| } | |
| _javaVM = 0; | |
| javaEnv->GetJavaVM(&_javaVM); | |
| _javaEnv = javaEnv; | |
| _javaDispatchObjectClass = static_cast<jclass>( | |
| javaEnv->NewGlobalRef(javaDispatchObjectClass)); | |
| _javaDispatchMethodClass = static_cast<jclass>( | |
| javaEnv->NewGlobalRef(javaDispatchMethodClass)); | |
| _lowLevelSafClass = static_cast<jclass>( | |
| javaEnv->NewGlobalRef(lowLevelSafClass)); | |
| _javaDispatchObjectSetFieldMethod = javaEnv->GetMethodID( | |
| javaDispatchObjectClass, "setField", "(ILjava/lang/String;I)V"); | |
| _javaDispatchObjectGetFieldMethod = javaEnv->GetMethodID( | |
| javaDispatchObjectClass, "getField", "(ILjava/lang/String;)I"); | |
| _javaDispatchMethodInvokeMethod = javaEnv->GetMethodID( | |
| javaDispatchMethodClass, "invoke", "(II[I[I)I"); | |
| _javaDispatchObjectToStringMethod = javaEnv->GetMethodID( | |
| javaDispatchObjectClass, "toString", "()Ljava/lang/String;"); | |
| _lowLevelSafReleaseObject = javaEnv->GetStaticMethodID( | |
| lowLevelSafClass, "releaseObject", "(Ljava/lang/Object;)V"); | |
| if (!_javaVM | |
| || !_javaDispatchObjectSetFieldMethod || !_javaDispatchObjectGetFieldMethod | |
| || !_javaDispatchMethodInvokeMethod || !_javaDispatchObjectToStringMethod | |
| || !_lowLevelSafReleaseObject || javaEnv->ExceptionCheck()) { | |
| return false; | |
| } | |
| TR_LEAVE(); | |
| return true; | |
| } | |
| void ReleaseJavaObject(jobject jObject) { | |
| // Tricky: this call may be on a foreign thread. | |
| JNIEnv* javaEnv = 0; | |
| if ((_javaVM->AttachCurrentThreadAsDaemon(reinterpret_cast<void**>(&javaEnv), | |
| NULL) < 0) || !javaEnv) { | |
| TR_FAIL(); | |
| return; | |
| } | |
| // Tell the Java code we're done with this object. | |
| javaEnv->CallStaticVoidMethod(_lowLevelSafClass, _lowLevelSafReleaseObject, | |
| jObject); | |
| if (javaEnv->ExceptionCheck()) { | |
| TR_FAIL(); | |
| return; | |
| } | |
| javaEnv->DeleteGlobalRef(jObject); | |
| } | |
| } // namespace gwt |