Home | History | Annotate | Download | only in jsc
      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