1 /* 2 * Copyright (C) 2003, 2008, 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "JavaInstanceJSC.h" 28 29 #if ENABLE(JAVA_BRIDGE) 30 31 #include "JavaRuntimeObject.h" 32 #include "JNIUtilityPrivate.h" 33 #include "JSDOMBinding.h" 34 #include "JavaArrayJSC.h" 35 #include "JavaClassJSC.h" 36 #include "JavaMethod.h" 37 #include "JavaString.h" 38 #include "Logging.h" 39 #include "jni_jsobject.h" 40 #include "runtime_method.h" 41 #include "runtime_object.h" 42 #include "runtime_root.h" 43 #include <runtime/ArgList.h> 44 #include <runtime/Error.h> 45 #include <runtime/FunctionPrototype.h> 46 #include <runtime/JSLock.h> 47 48 using namespace JSC::Bindings; 49 using namespace JSC; 50 using namespace WebCore; 51 52 JavaInstance::JavaInstance(jobject instance, PassRefPtr<RootObject> rootObject) 53 : Instance(rootObject) 54 { 55 m_instance = new JobjectWrapper(instance); 56 m_class = 0; 57 } 58 59 JavaInstance::~JavaInstance() 60 { 61 delete m_class; 62 } 63 64 RuntimeObject* JavaInstance::newRuntimeObject(ExecState* exec) 65 { 66 return new (exec) JavaRuntimeObject(exec, exec->lexicalGlobalObject(), this); 67 } 68 69 #define NUM_LOCAL_REFS 64 70 71 void JavaInstance::virtualBegin() 72 { 73 getJNIEnv()->PushLocalFrame(NUM_LOCAL_REFS); 74 } 75 76 void JavaInstance::virtualEnd() 77 { 78 getJNIEnv()->PopLocalFrame(0); 79 } 80 81 Class* JavaInstance::getClass() const 82 { 83 if (!m_class) 84 m_class = new JavaClass (m_instance->m_instance); 85 return m_class; 86 } 87 88 JSValue JavaInstance::stringValue(ExecState* exec) const 89 { 90 JSLock lock(SilenceAssertionsOnly); 91 92 jstring stringValue = (jstring)callJNIMethod<jobject>(m_instance->m_instance, "toString", "()Ljava/lang/String;"); 93 94 // Should throw a JS exception, rather than returning ""? - but better than a null dereference. 95 if (!stringValue) 96 return jsString(exec, UString()); 97 98 JNIEnv* env = getJNIEnv(); 99 const jchar* c = getUCharactersFromJStringInEnv(env, stringValue); 100 UString u((const UChar*)c, (int)env->GetStringLength(stringValue)); 101 releaseUCharactersForJStringInEnv(env, stringValue, c); 102 return jsString(exec, u); 103 } 104 105 JSValue JavaInstance::numberValue(ExecState*) const 106 { 107 jdouble doubleValue = callJNIMethod<jdouble>(m_instance->m_instance, "doubleValue", "()D"); 108 return jsNumber(doubleValue); 109 } 110 111 JSValue JavaInstance::booleanValue() const 112 { 113 jboolean booleanValue = callJNIMethod<jboolean>(m_instance->m_instance, "booleanValue", "()Z"); 114 return jsBoolean(booleanValue); 115 } 116 117 class JavaRuntimeMethod : public RuntimeMethod { 118 public: 119 JavaRuntimeMethod(ExecState* exec, JSGlobalObject* globalObject, const Identifier& name, Bindings::MethodList& list) 120 // FIXME: deprecatedGetDOMStructure uses the prototype off of the wrong global object 121 // We need to pass in the right global object for "i". 122 : RuntimeMethod(exec, globalObject, WebCore::deprecatedGetDOMStructure<JavaRuntimeMethod>(exec), name, list) 123 { 124 ASSERT(inherits(&s_info)); 125 } 126 127 static Structure* createStructure(JSGlobalData& globalData, JSValue prototype) 128 { 129 return Structure::create(globalData, prototype, TypeInfo(ObjectType, StructureFlags), AnonymousSlotCount, &s_info); 130 } 131 132 static const ClassInfo s_info; 133 }; 134 135 const ClassInfo JavaRuntimeMethod::s_info = { "JavaRuntimeMethod", &RuntimeMethod::s_info, 0, 0 }; 136 137 JSValue JavaInstance::getMethod(ExecState* exec, const Identifier& propertyName) 138 { 139 MethodList methodList = getClass()->methodsNamed(propertyName, this); 140 return new (exec) JavaRuntimeMethod(exec, exec->lexicalGlobalObject(), propertyName, methodList); 141 } 142 143 JSValue JavaInstance::invokeMethod(ExecState* exec, RuntimeMethod* runtimeMethod) 144 { 145 if (!asObject(runtimeMethod)->inherits(&JavaRuntimeMethod::s_info)) 146 return throwError(exec, createTypeError(exec, "Attempt to invoke non-Java method on Java object.")); 147 148 const MethodList& methodList = *runtimeMethod->methods(); 149 150 int i; 151 int count = exec->argumentCount(); 152 JSValue resultValue; 153 Method* method = 0; 154 size_t numMethods = methodList.size(); 155 156 // Try to find a good match for the overloaded method. The 157 // fundamental problem is that JavaScript doesn't have the 158 // notion of method overloading and Java does. We could 159 // get a bit more sophisticated and attempt to does some 160 // type checking as we as checking the number of parameters. 161 for (size_t methodIndex = 0; methodIndex < numMethods; methodIndex++) { 162 Method* aMethod = methodList[methodIndex]; 163 if (aMethod->numParameters() == count) { 164 method = aMethod; 165 break; 166 } 167 } 168 if (!method) { 169 LOG(LiveConnect, "JavaInstance::invokeMethod unable to find an appropiate method"); 170 return jsUndefined(); 171 } 172 173 const JavaMethod* jMethod = static_cast<const JavaMethod*>(method); 174 LOG(LiveConnect, "JavaInstance::invokeMethod call %s %s on %p", UString(jMethod->name().impl()).utf8().data(), jMethod->signature(), m_instance->m_instance); 175 176 Vector<jvalue> jArgs(count); 177 178 for (i = 0; i < count; i++) { 179 CString javaClassName = jMethod->parameterAt(i).utf8(); 180 jArgs[i] = convertValueToJValue(exec, m_rootObject.get(), exec->argument(i), javaTypeFromClassName(javaClassName.data()), javaClassName.data()); 181 LOG(LiveConnect, "JavaInstance::invokeMethod arg[%d] = %s", i, exec->argument(i).toString(exec).ascii().data()); 182 } 183 184 jvalue result; 185 186 // Try to use the JNI abstraction first, otherwise fall back to 187 // normal JNI. The JNI dispatch abstraction allows the Java plugin 188 // to dispatch the call on the appropriate internal VM thread. 189 RootObject* rootObject = this->rootObject(); 190 if (!rootObject) 191 return jsUndefined(); 192 193 bool handled = false; 194 if (rootObject->nativeHandle()) { 195 jobject obj = m_instance->m_instance; 196 JSValue exceptionDescription; 197 const char *callingURL = 0; // FIXME, need to propagate calling URL to Java 198 jmethodID methodId = getMethodID(obj, jMethod->name().utf8().data(), jMethod->signature()); 199 handled = dispatchJNICall(exec, rootObject->nativeHandle(), obj, jMethod->isStatic(), jMethod->returnType(), methodId, jArgs.data(), result, callingURL, exceptionDescription); 200 if (exceptionDescription) { 201 throwError(exec, createError(exec, exceptionDescription.toString(exec))); 202 return jsUndefined(); 203 } 204 } 205 206 // This is a deprecated code path which should not be required on Android. 207 // Remove this guard once Bug 39476 is fixed. 208 #if PLATFORM(ANDROID) || defined(BUILDING_ON_TIGER) 209 if (!handled) 210 result = callJNIMethod(m_instance->m_instance, jMethod->returnType(), jMethod->name().utf8().data(), jMethod->signature(), jArgs.data()); 211 #endif 212 213 switch (jMethod->returnType()) { 214 case JavaTypeVoid: 215 { 216 resultValue = jsUndefined(); 217 } 218 break; 219 220 case JavaTypeObject: 221 { 222 if (result.l) { 223 // FIXME: JavaTypeArray return type is handled below, can we actually get an array here? 224 const char* arrayType = jMethod->returnTypeClassName(); 225 if (arrayType[0] == '[') 226 resultValue = JavaArray::convertJObjectToArray(exec, result.l, arrayType, rootObject); 227 else { 228 jobject classOfInstance = callJNIMethod<jobject>(result.l, "getClass", "()Ljava/lang/Class;"); 229 jstring className = static_cast<jstring>(callJNIMethod<jobject>(classOfInstance, "getName", "()Ljava/lang/String;")); 230 if (!strcmp(JavaString(className).utf8(), "sun.plugin.javascript.webkit.JSObject")) { 231 // Pull the nativeJSObject value from the Java instance. This is a pointer to the JSObject. 232 JNIEnv* env = getJNIEnv(); 233 jfieldID fieldID = env->GetFieldID(static_cast<jclass>(classOfInstance), "nativeJSObject", "J"); 234 jlong nativeHandle = env->GetLongField(result.l, fieldID); 235 // FIXME: Handling of undefined values differs between functions in JNIUtilityPrivate.cpp and those in those in jni_jsobject.mm, 236 // and so it does between different versions of LiveConnect spec. There should not be multiple code paths to do the same work. 237 if (nativeHandle == 1 /* UndefinedHandle */) 238 return jsUndefined(); 239 return static_cast<JSObject*>(jlong_to_ptr(nativeHandle)); 240 } else 241 return JavaInstance::create(result.l, rootObject)->createRuntimeObject(exec); 242 } 243 } else 244 return jsUndefined(); 245 } 246 break; 247 248 case JavaTypeBoolean: 249 { 250 resultValue = jsBoolean(result.z); 251 } 252 break; 253 254 case JavaTypeByte: 255 { 256 resultValue = jsNumber(result.b); 257 } 258 break; 259 260 case JavaTypeChar: 261 { 262 resultValue = jsNumber(result.c); 263 } 264 break; 265 266 case JavaTypeShort: 267 { 268 resultValue = jsNumber(result.s); 269 } 270 break; 271 272 case JavaTypeInt: 273 { 274 resultValue = jsNumber(result.i); 275 } 276 break; 277 278 case JavaTypeLong: 279 { 280 resultValue = jsNumber(result.j); 281 } 282 break; 283 284 case JavaTypeFloat: 285 { 286 resultValue = jsNumber(result.f); 287 } 288 break; 289 290 case JavaTypeDouble: 291 { 292 resultValue = jsNumber(result.d); 293 } 294 break; 295 296 case JavaTypeArray: 297 { 298 const char* arrayType = jMethod->returnTypeClassName(); 299 ASSERT(arrayType[0] == '['); 300 resultValue = JavaArray::convertJObjectToArray(exec, result.l, arrayType, rootObject); 301 } 302 break; 303 304 case JavaTypeInvalid: 305 { 306 resultValue = jsUndefined(); 307 } 308 break; 309 } 310 311 return resultValue; 312 } 313 314 JSValue JavaInstance::defaultValue(ExecState* exec, PreferredPrimitiveType hint) const 315 { 316 if (hint == PreferString) 317 return stringValue(exec); 318 if (hint == PreferNumber) 319 return numberValue(exec); 320 JavaClass* aClass = static_cast<JavaClass*>(getClass()); 321 if (aClass->isStringClass()) 322 return stringValue(exec); 323 if (aClass->isNumberClass()) 324 return numberValue(exec); 325 if (aClass->isBooleanClass()) 326 return booleanValue(); 327 return valueOf(exec); 328 } 329 330 JSValue JavaInstance::valueOf(ExecState* exec) const 331 { 332 return stringValue(exec); 333 } 334 335 #endif // ENABLE(JAVA_BRIDGE) 336