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
JavaInstance(jobject instance,PassRefPtr<RootObject> rootObject)52 JavaInstance::JavaInstance(jobject instance, PassRefPtr<RootObject> rootObject)
53 : Instance(rootObject)
54 {
55 m_instance = new JobjectWrapper(instance);
56 m_class = 0;
57 }
58
~JavaInstance()59 JavaInstance::~JavaInstance()
60 {
61 delete m_class;
62 }
63
newRuntimeObject(ExecState * exec)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
virtualBegin()71 void JavaInstance::virtualBegin()
72 {
73 getJNIEnv()->PushLocalFrame(NUM_LOCAL_REFS);
74 }
75
virtualEnd()76 void JavaInstance::virtualEnd()
77 {
78 getJNIEnv()->PopLocalFrame(0);
79 }
80
getClass() const81 Class* JavaInstance::getClass() const
82 {
83 if (!m_class)
84 m_class = new JavaClass (m_instance->m_instance);
85 return m_class;
86 }
87
stringValue(ExecState * exec) const88 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
numberValue(ExecState *) const105 JSValue JavaInstance::numberValue(ExecState*) const
106 {
107 jdouble doubleValue = callJNIMethod<jdouble>(m_instance->m_instance, "doubleValue", "()D");
108 return jsNumber(doubleValue);
109 }
110
booleanValue() const111 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:
JavaRuntimeMethod(ExecState * exec,JSGlobalObject * globalObject,const Identifier & name,Bindings::MethodList & list)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
createStructure(JSGlobalData & globalData,JSValue prototype)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
getMethod(ExecState * exec,const Identifier & propertyName)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
invokeMethod(ExecState * exec,RuntimeMethod * runtimeMethod)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
defaultValue(ExecState * exec,PreferredPrimitiveType hint) const314 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
valueOf(ExecState * exec) const330 JSValue JavaInstance::valueOf(ExecState* exec) const
331 {
332 return stringValue(exec);
333 }
334
335 #endif // ENABLE(JAVA_BRIDGE)
336