| /* | |
| * Copyright 2007 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. | |
| */ | |
| /* | |
| * Defines the JavaScript class gwt_external_class, which interface via JNI | |
| * to Java objects. | |
| */ | |
| #include <jni.h> | |
| #include "JsRootedValue.h" | |
| #include "gwt-jni.h" | |
| #include "ExternalWrapper.h" | |
| #include "Tracer.h" | |
| #include "JsStringWrap.h" | |
| #ifdef ENABLE_TRACING | |
| extern void PrintJSValue(JSContext* cx, jsval val, char* prefix=""); | |
| #else | |
| // Include a null version just to keep from cluttering up call sites. | |
| static inline void PrintJSValue(JSContext* cx, jsval val, char* prefix="") { } | |
| #endif | |
| // note that this does not use the NS_DEFINE_CID macro because it defines | |
| // the variable as const, which by default has internal linkage. I could | |
| // work around it by putting extern in front of the macro, but that seems | |
| // fragile if the macro changes. | |
| nsCID kGwtExternalCID = GWT_EXTERNAL_FACTORY_CID; | |
| /* | |
| * definition of JavaScript gwtOnLoad method which maps to the Java | |
| * gwtOnLoad method. | |
| */ | |
| static JSBool gwtOnLoad(JSContext *cx, JSObject *obj, uintN argc, | |
| jsval *argv, jsval *rval) | |
| { | |
| Tracer tracer("gwtOnLoad"); | |
| tracer.log("context=%08x", unsigned(cx)); | |
| JsRootedValue::ContextManager context(cx); | |
| JsRootedValue::ensureRuntime(cx); | |
| if (argc < 3) { | |
| tracer.setFail("less than 3 args"); | |
| return JS_FALSE; | |
| } | |
| JSObject* scriptWindow = 0; | |
| if (argv[0] != JSVAL_NULL && argv[0] != JSVAL_VOID) { | |
| if (!JS_ValueToObject(cx, argv[0], &scriptWindow)) { | |
| tracer.setFail("can't get script window object"); | |
| return JS_FALSE; | |
| } | |
| } | |
| JSString* moduleName = 0; | |
| if (argv[1] != JSVAL_NULL && argv[1] != JSVAL_VOID) { | |
| moduleName = JS_ValueToString(cx, argv[1]); | |
| } | |
| JSString* version = 0; | |
| if (argv[2] != JSVAL_NULL && argv[2] != JSVAL_VOID) { | |
| version = JS_ValueToString(cx, argv[2]); | |
| } | |
| nsCOMPtr<nsIScriptGlobalObject> scriptGlobal(0); | |
| if (scriptWindow) { | |
| nsCOMPtr<nsIXPConnect> xpConnect = do_GetService(nsIXPConnect::GetCID()); | |
| nsCOMPtr<nsIXPConnectWrappedNative> wrappedNative; | |
| xpConnect->GetWrappedNativeOfJSObject(cx, scriptWindow, | |
| getter_AddRefs(wrappedNative)); | |
| if (wrappedNative) { | |
| nsCOMPtr<nsISupports> native; | |
| wrappedNative->GetNative(getter_AddRefs(native)); | |
| scriptGlobal = do_QueryInterface(native); | |
| } | |
| } | |
| jstring jModuleName(0); | |
| if (moduleName) { | |
| jModuleName = savedJNIEnv->NewString(JS_GetStringChars(moduleName), | |
| JS_GetStringLength(moduleName)); | |
| if (!jModuleName || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get module name in Java string"); | |
| return JS_FALSE; | |
| } | |
| tracer.log("module name=%s", JS_GetStringBytes(moduleName)); | |
| } else { | |
| tracer.log("null module name"); | |
| } | |
| jstring jVersion(0); | |
| if (version) { | |
| jVersion = savedJNIEnv->NewString(JS_GetStringChars(version), | |
| JS_GetStringLength(version)); | |
| if (!jVersion || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get module name in Java string"); | |
| return JS_FALSE; | |
| } | |
| tracer.log("version=%s", JS_GetStringBytes(version)); | |
| } else { | |
| tracer.log("null version"); | |
| } | |
| jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj)); | |
| jclass objClass = savedJNIEnv->GetObjectClass(externalObject); | |
| if (!objClass || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get LowLevelMoz.ExternalObject class"); | |
| return JS_FALSE; | |
| } | |
| jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "gwtOnLoad", | |
| "(ILjava/lang/String;Ljava/lang/String;)Z"); | |
| if (!methodID || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get gwtOnLoad method"); | |
| return JS_FALSE; | |
| } | |
| tracer.log("scriptGlobal=%08x", unsigned(scriptGlobal.get())); | |
| jboolean result = savedJNIEnv->CallBooleanMethod(externalObject, methodID, | |
| NS_REINTERPRET_CAST(jint, scriptGlobal.get()), jModuleName, jVersion); | |
| if (savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("LowLevelMoz.ExternalObject.gwtOnLoad() threw an exception"); | |
| return JS_FALSE; | |
| } | |
| *rval = BOOLEAN_TO_JSVAL((result == JNI_FALSE) ? JS_FALSE : JS_TRUE); | |
| return JS_TRUE; | |
| } | |
| /* | |
| * definition of JavaScript initModule method which maps to the Java | |
| * initModule method. | |
| */ | |
| static JSBool initModule(JSContext *cx, JSObject *obj, uintN argc, | |
| jsval *argv, jsval *rval) | |
| { | |
| Tracer tracer("initModule"); | |
| tracer.log("context=%08x", unsigned(cx)); | |
| JsRootedValue::ContextManager context(cx); | |
| JsRootedValue::ensureRuntime(cx); | |
| if (argc < 1) { | |
| tracer.setFail("less than 1 args"); | |
| return JS_FALSE; | |
| } | |
| JSString* moduleName = 0; | |
| if (argv[0] != JSVAL_NULL && argv[0] != JSVAL_VOID) { | |
| moduleName = JS_ValueToString(cx, argv[0]); | |
| } | |
| jstring jModuleName(0); | |
| if (moduleName) { | |
| jModuleName = savedJNIEnv->NewString(JS_GetStringChars(moduleName), | |
| JS_GetStringLength(moduleName)); | |
| if (!jModuleName || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get module name in Java string"); | |
| return JS_FALSE; | |
| } | |
| tracer.log("module name=%s", JS_GetStringBytes(moduleName)); | |
| } else { | |
| tracer.log("null module name"); | |
| } | |
| jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj)); | |
| jclass objClass = savedJNIEnv->GetObjectClass(externalObject); | |
| if (!objClass || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get LowLevelMoz.ExternalObject class"); | |
| return JS_FALSE; | |
| } | |
| jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "initModule", | |
| "(Ljava/lang/String;)Z"); | |
| if (!methodID || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get initModule method"); | |
| return JS_FALSE; | |
| } | |
| jboolean result = savedJNIEnv->CallBooleanMethod(externalObject, methodID, | |
| jModuleName); | |
| if (savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("LowLevelMoz.ExternalObject.initModule() threw an exception"); | |
| return JS_FALSE; | |
| } | |
| *rval = BOOLEAN_TO_JSVAL((result == JNI_FALSE) ? JS_FALSE : JS_TRUE); | |
| return JS_TRUE; | |
| } | |
| /*=======================================================================*/ | |
| /* Implementation of the getProperty, setProperty, and finalize methods */ | |
| /* for the JavaScript class gwt_external_class. */ | |
| /*=======================================================================*/ | |
| static JSBool JS_DLL_CALLBACK gwt_external_getProperty(JSContext *cx, | |
| JSObject *obj, jsval id, jsval *vp) | |
| { | |
| Tracer tracer("gwt_external_getProperty"); | |
| JsRootedValue::ContextManager context(cx); | |
| if (*vp != JSVAL_VOID) { | |
| // we setup the gwtOnLoad function as a property in GetScriptObject and | |
| // do not maintain a copy of it anywhere. So, if there is a cached | |
| // value for a property we must return it. As we never redefine any | |
| // property values, this is safe. If we were going to keep this code | |
| // around and add Toby's profiling code we would need to revisit this. | |
| return JS_TRUE; | |
| } | |
| if (!JSVAL_IS_STRING(id)) { | |
| tracer.setFail("not a string"); | |
| return JS_FALSE; | |
| } | |
| JsStringWrap jsStr(cx, id); | |
| tracer.log("obj=%08x, property=%.*s", obj, jsStr.length(), jsStr.bytes()); | |
| // All this is for calling resolveReference which only returns void. So, | |
| // just replace this code to return void for now. TODO(jat): revisit when | |
| // merging in Toby's code. | |
| #if 0 | |
| jstring jident = savedJNIEnv->NewString(jsStr.chars(), jsStr.length()); | |
| if (!jident || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't create Java string "); | |
| return JS_FALSE; | |
| } | |
| jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj)); | |
| jclass objClass = savedJNIEnv->GetObjectClass(externalObject); | |
| if (!objClass || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't find Java class for JS object"); | |
| return JS_FALSE; | |
| } | |
| jmethodID methodID = savedJNIEnv->GetMethodID(objClass, "resolveReference", | |
| "(Ljava/lang/String;)I"); | |
| if (!methodID || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("exception getting method for int resolveReference(String)"); | |
| return JS_FALSE; | |
| } | |
| int retval = savedJNIEnv->CallIntMethod(externalObject, methodID, jident); | |
| if (savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("exception calling int resolveReference(String)"); | |
| return JS_FALSE; | |
| } | |
| *vp = retval; | |
| #else | |
| *vp = JSVAL_VOID; | |
| #endif | |
| return JS_TRUE; | |
| } | |
| static void JS_DLL_CALLBACK gwt_external_finalize(JSContext *cx, JSObject *obj) | |
| { | |
| // We don't need to push a context if all we do is DeleteGlobalRef | |
| Tracer tracer("gwt_external_finalize", obj); | |
| jobject externalObject = NS_REINTERPRET_CAST(jobject, JS_GetPrivate(cx, obj)); | |
| if (externalObject) { | |
| savedJNIEnv->DeleteGlobalRef(externalObject); | |
| } | |
| JS_FinalizeStub(cx, obj); | |
| } | |
| static JSBool JS_DLL_CALLBACK gwt_external_setProperty(JSContext *cx, | |
| JSObject *obj, jsval id, jsval *vp) | |
| { | |
| return JS_FALSE; | |
| } | |
| static JSClass gwt_external_class = { | |
| "gwt_external_class", JSCLASS_HAS_PRIVATE, | |
| JS_PropertyStub, JS_PropertyStub, gwt_external_getProperty, | |
| gwt_external_setProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, | |
| gwt_external_finalize, | |
| JSCLASS_NO_OPTIONAL_MEMBERS | |
| }; | |
| NS_IMPL_ISUPPORTS1(ExternalWrapper, nsIScriptObjectOwner) | |
| /* | |
| * Create a new LowLevelMoz.ExternalObject instance and a JavaScript object | |
| * that refers to it, mapping the gwtOnLoad member function mapping to | |
| * a Javascript method. | |
| */ | |
| NS_IMETHODIMP ExternalWrapper::GetScriptObject(nsIScriptContext *aContext, | |
| void** aScriptObject) | |
| { | |
| Tracer tracer("ExternalWrapper::GetScriptObject"); | |
| if (!aScriptObject) { | |
| tracer.setFail("null script object pointer"); | |
| return NS_ERROR_INVALID_POINTER; | |
| } | |
| if (!jsWindowExternalObject) { | |
| JSContext* cx = NS_REINTERPRET_CAST(JSContext*, | |
| aContext->GetNativeContext()); | |
| if (!cx) { | |
| tracer.setFail("can't get JSContext"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| JsRootedValue::ContextManager context(cx); | |
| *aScriptObject = 0; | |
| nsIScriptGlobalObject* globalObject = aContext->GetGlobalObject(); | |
| nsCOMPtr<nsIDOMWindow> domWindow(do_QueryInterface(globalObject)); | |
| if (!domWindow) { | |
| tracer.setFail("can't get DOM window"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| nsCOMPtr<nsIDOMWindow> topWindow; | |
| domWindow->GetTop(getter_AddRefs(topWindow)); | |
| if (!topWindow) { | |
| tracer.setFail("Can't get top window"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| tracer.log("savedJNIEnv=%08x, llClass=%08x", unsigned(savedJNIEnv), | |
| lowLevelMozClass); | |
| jmethodID methodID = savedJNIEnv->GetStaticMethodID(lowLevelMozClass, | |
| "createExternalObjectForDOMWindow", | |
| "(I)Lcom/google/gwt/dev/shell/moz/LowLevelMoz$ExternalObject;"); | |
| if (!methodID || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("Can't get createExternalObjectForDOMWindow method"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| jobject externalObject = savedJNIEnv->CallStaticObjectMethod( | |
| lowLevelMozClass, methodID, NS_REINTERPRET_CAST(jint, topWindow.get())); | |
| if (!externalObject || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("createExternalObjectForDOMWindow failed"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| externalObject = savedJNIEnv->NewGlobalRef(externalObject); | |
| if (!externalObject || savedJNIEnv->ExceptionCheck()) { | |
| tracer.setFail("can't get GlobalRef for external object"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| JSObject* newObj = JS_NewObject(cx, &gwt_external_class, 0, | |
| globalObject->GetGlobalJSObject()); | |
| if (!newObj) { | |
| tracer.setFail("can't create new gwt_external_class object"); | |
| return NS_ERROR_OUT_OF_MEMORY; | |
| } | |
| if (!JS_SetPrivate(cx, newObj, externalObject)) { | |
| savedJNIEnv->DeleteGlobalRef(externalObject); | |
| tracer.setFail("can't store external object private reference"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| if (!JS_DefineFunction(cx, newObj, "gwtOnLoad", gwtOnLoad, 3, | |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { | |
| tracer.setFail("can't define gwtOnLoad function on JavaScript object"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| if (!JS_DefineFunction(cx, newObj, "initModule", initModule, 1, | |
| JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT)) { | |
| tracer.setFail("can't define initModule function on JavaScript object"); | |
| return NS_ERROR_UNEXPECTED; | |
| } | |
| jsWindowExternalObject = newObj; | |
| } | |
| *aScriptObject = jsWindowExternalObject; | |
| return NS_OK; | |
| } | |
| NS_IMETHODIMP ExternalWrapper::SetScriptObject(void* aScriptObject) | |
| { | |
| jsWindowExternalObject = aScriptObject; | |
| return NS_OK; | |
| } | |
| NS_IMPL_ISUPPORTS1(nsRpExternalFactory, nsIFactory) | |
| NS_IMETHODIMP nsRpExternalFactory::CreateInstance(nsISupports *aOuter, | |
| const nsIID& aIID, void** aResult) | |
| { | |
| Tracer tracer("nsRpExternalFactory::CreateInstance"); | |
| if (!aResult) { | |
| tracer.setFail("null pointer for return value"); | |
| return NS_ERROR_INVALID_POINTER; | |
| } | |
| *aResult = NULL; | |
| if (aOuter) { | |
| tracer.setFail("aOuter is not null"); | |
| return NS_ERROR_NO_AGGREGATION; | |
| } | |
| nsISupports* object = new ExternalWrapper(); | |
| if (!object) { | |
| tracer.setFail("can't create a new ExternalWrapper"); | |
| return NS_ERROR_OUT_OF_MEMORY; | |
| } | |
| nsresult result = object->QueryInterface(aIID, aResult); | |
| if (!*aResult || NS_FAILED(result)) { | |
| tracer.setFail("ExternalWrapper::QueryInterface failed"); | |
| delete object; | |
| } | |
| return result; | |
| } | |
| NS_IMETHODIMP nsRpExternalFactory::LockFactory(PRBool lock) | |
| { | |
| return NS_ERROR_NOT_IMPLEMENTED; | |
| } |