b8145475dde059b004950bbb66446567894e07b3
[platform/framework/web/crosswalk.git] / src / content / browser / android / java / gin_java_method_invocation_helper.cc
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/android/java/gin_java_method_invocation_helper.h"
6
7 #include <unistd.h>
8
9 #include "base/android/event_log.h"
10 #include "base/android/jni_android.h"
11 #include "base/float_util.h"
12 #include "content/browser/android/java/gin_java_script_to_java_types_coercion.h"
13 #include "content/browser/android/java/java_method.h"
14 #include "content/browser/android/java/jni_helper.h"
15 #include "content/common/android/gin_java_bridge_value.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "third_party/WebKit/public/platform/WebString.h"
18
19 using base::android::AttachCurrentThread;
20 using base::android::ScopedJavaLocalRef;
21
22 namespace content {
23
24 namespace {
25
26 // See frameworks/base/core/java/android/webkit/EventLogTags.logtags
27 const int kObjectGetClassInvocationAttemptLogTag = 70151;
28
29 // This is an intermediate solution until we fix http://crbug.com/391492.
30 std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) {
31   const jchar* chars = env->GetStringChars(str, NULL);
32   DCHECK(chars);
33   blink::WebString utf16(chars, env->GetStringLength(str));
34   env->ReleaseStringChars(str, chars);
35   return utf16.utf8();
36 }
37
38 }  // namespace
39
40 GinJavaMethodInvocationHelper::GinJavaMethodInvocationHelper(
41     scoped_ptr<ObjectDelegate> object,
42     const std::string& method_name,
43     const base::ListValue& arguments)
44     : object_(object.Pass()),
45       method_name_(method_name),
46       arguments_(arguments.DeepCopy()),
47       invocation_error_(kGinJavaBridgeNoError) {
48 }
49
50 GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {}
51
52 void GinJavaMethodInvocationHelper::Init(DispatcherDelegate* dispatcher) {
53   // Build on the UI thread a map of object_id -> WeakRef for Java objects from
54   // |arguments_|.  Then we can use this map on the background thread without
55   // accessing |dispatcher|.
56   BuildObjectRefsFromListValue(dispatcher, arguments_.get());
57 }
58
59 // As V8ValueConverter has finite recursion depth when serializing
60 // JavaScript values, we don't bother about having a recursion threshold here.
61 void GinJavaMethodInvocationHelper::BuildObjectRefsFromListValue(
62     DispatcherDelegate* dispatcher,
63     const base::Value* list_value) {
64   DCHECK(list_value->IsType(base::Value::TYPE_LIST));
65   const base::ListValue* list;
66   list_value->GetAsList(&list);
67   for (base::ListValue::const_iterator iter = list->begin();
68        iter != list->end();
69        ++iter) {
70     if (AppendObjectRef(dispatcher, *iter))
71       continue;
72     if ((*iter)->IsType(base::Value::TYPE_LIST)) {
73       BuildObjectRefsFromListValue(dispatcher, *iter);
74     } else if ((*iter)->IsType(base::Value::TYPE_DICTIONARY)) {
75       BuildObjectRefsFromDictionaryValue(dispatcher, *iter);
76     }
77   }
78 }
79
80 void GinJavaMethodInvocationHelper::BuildObjectRefsFromDictionaryValue(
81     DispatcherDelegate* dispatcher,
82     const base::Value* dict_value) {
83   DCHECK(dict_value->IsType(base::Value::TYPE_DICTIONARY));
84   const base::DictionaryValue* dict;
85   dict_value->GetAsDictionary(&dict);
86   for (base::DictionaryValue::Iterator iter(*dict);
87        !iter.IsAtEnd();
88        iter.Advance()) {
89     if (AppendObjectRef(dispatcher, &iter.value()))
90       continue;
91     if (iter.value().IsType(base::Value::TYPE_LIST)) {
92       BuildObjectRefsFromListValue(dispatcher, &iter.value());
93     } else if (iter.value().IsType(base::Value::TYPE_DICTIONARY)) {
94       BuildObjectRefsFromDictionaryValue(dispatcher, &iter.value());
95     }
96   }
97 }
98
99 bool GinJavaMethodInvocationHelper::AppendObjectRef(
100     DispatcherDelegate* dispatcher,
101     const base::Value* raw_value) {
102   if (!GinJavaBridgeValue::ContainsGinJavaBridgeValue(raw_value))
103     return false;
104   scoped_ptr<const GinJavaBridgeValue> value(
105       GinJavaBridgeValue::FromValue(raw_value));
106   if (!value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID))
107     return false;
108   GinJavaBoundObject::ObjectID object_id;
109   if (value->GetAsObjectID(&object_id)) {
110     ObjectRefs::iterator iter = object_refs_.find(object_id);
111     if (iter == object_refs_.end()) {
112       JavaObjectWeakGlobalRef object_ref(
113           dispatcher->GetObjectWeakRef(object_id));
114       if (!object_ref.is_empty()) {
115         object_refs_.insert(std::make_pair(object_id, object_ref));
116       }
117     }
118   }
119   return true;
120 }
121
122 void GinJavaMethodInvocationHelper::Invoke() {
123   JNIEnv* env = AttachCurrentThread();
124   const JavaMethod* method =
125       object_->FindMethod(method_name_, arguments_->GetSize());
126   if (!method) {
127     SetInvocationError(kGinJavaBridgeMethodNotFound);
128     return;
129   }
130
131   if (object_->IsObjectGetClassMethod(method)) {
132     base::android::EventLogWriteInt(kObjectGetClassInvocationAttemptLogTag,
133                                     getuid());
134     SetInvocationError(kGinJavaBridgeAccessToObjectGetClassIsBlocked);
135     return;
136   }
137
138   ScopedJavaLocalRef<jobject> obj;
139   ScopedJavaLocalRef<jclass> cls;
140   if (method->is_static()) {
141     cls = object_->GetLocalClassRef(env);
142   } else {
143     obj = object_->GetLocalRef(env);
144   }
145   if (obj.is_null() && cls.is_null()) {
146     SetInvocationError(kGinJavaBridgeObjectIsGone);
147     return;
148   }
149
150   GinJavaBridgeError coercion_error = kGinJavaBridgeNoError;
151   std::vector<jvalue> parameters(method->num_parameters());
152   for (size_t i = 0; i < method->num_parameters(); ++i) {
153     const base::Value* argument;
154     arguments_->Get(i, &argument);
155     parameters[i] = CoerceJavaScriptValueToJavaValue(env,
156                                                      argument,
157                                                      method->parameter_type(i),
158                                                      true,
159                                                      object_refs_,
160                                                      &coercion_error);
161   }
162
163   if (coercion_error == kGinJavaBridgeNoError) {
164     if (method->is_static()) {
165       InvokeMethod(
166           NULL, cls.obj(), method->return_type(), method->id(), &parameters[0]);
167     } else {
168       InvokeMethod(
169           obj.obj(), NULL, method->return_type(), method->id(), &parameters[0]);
170     }
171   } else {
172     SetInvocationError(coercion_error);
173   }
174
175   // Now that we're done with the jvalue, release any local references created
176   // by CoerceJavaScriptValueToJavaValue().
177   for (size_t i = 0; i < method->num_parameters(); ++i) {
178     ReleaseJavaValueIfRequired(env, &parameters[i], method->parameter_type(i));
179   }
180 }
181
182 void GinJavaMethodInvocationHelper::SetInvocationError(
183     GinJavaBridgeError error) {
184   holds_primitive_result_ = true;
185   primitive_result_.reset(new base::ListValue());
186   invocation_error_ = error;
187 }
188
189 void GinJavaMethodInvocationHelper::SetPrimitiveResult(
190     const base::ListValue& result_wrapper) {
191   holds_primitive_result_ = true;
192   primitive_result_.reset(result_wrapper.DeepCopy());
193 }
194
195 void GinJavaMethodInvocationHelper::SetObjectResult(
196     const base::android::JavaRef<jobject>& object,
197     const base::android::JavaRef<jclass>& safe_annotation_clazz) {
198   holds_primitive_result_ = false;
199   object_result_.Reset(object);
200   safe_annotation_clazz_.Reset(safe_annotation_clazz);
201 }
202
203 bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() {
204   return holds_primitive_result_;
205 }
206
207 const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() {
208   return *primitive_result_.get();
209 }
210
211 const base::android::JavaRef<jobject>&
212 GinJavaMethodInvocationHelper::GetObjectResult() {
213   return object_result_;
214 }
215
216 const base::android::JavaRef<jclass>&
217 GinJavaMethodInvocationHelper::GetSafeAnnotationClass() {
218   return safe_annotation_clazz_;
219 }
220
221 const GinJavaBridgeError GinJavaMethodInvocationHelper::GetInvocationError() {
222   return invocation_error_;
223 }
224
225 void GinJavaMethodInvocationHelper::InvokeMethod(jobject object,
226                                                  jclass clazz,
227                                                  const JavaType& return_type,
228                                                  jmethodID id,
229                                                  jvalue* parameters) {
230   DCHECK(object || clazz);
231   JNIEnv* env = AttachCurrentThread();
232   base::ListValue result_wrapper;
233   switch (return_type.type) {
234     case JavaType::TypeBoolean:
235       result_wrapper.AppendBoolean(
236           object ? env->CallBooleanMethodA(object, id, parameters)
237                  : env->CallStaticBooleanMethodA(clazz, id, parameters));
238       break;
239     case JavaType::TypeByte:
240       result_wrapper.AppendInteger(
241           object ? env->CallByteMethodA(object, id, parameters)
242                  : env->CallStaticByteMethodA(clazz, id, parameters));
243       break;
244     case JavaType::TypeChar:
245       result_wrapper.AppendInteger(
246           object ? env->CallCharMethodA(object, id, parameters)
247                  : env->CallStaticCharMethodA(clazz, id, parameters));
248       break;
249     case JavaType::TypeShort:
250       result_wrapper.AppendInteger(
251           object ? env->CallShortMethodA(object, id, parameters)
252                  : env->CallStaticShortMethodA(clazz, id, parameters));
253       break;
254     case JavaType::TypeInt:
255       result_wrapper.AppendInteger(
256           object ? env->CallIntMethodA(object, id, parameters)
257                  : env->CallStaticIntMethodA(clazz, id, parameters));
258       break;
259     case JavaType::TypeLong:
260       result_wrapper.AppendDouble(
261           object ? env->CallLongMethodA(object, id, parameters)
262                  : env->CallStaticLongMethodA(clazz, id, parameters));
263       break;
264     case JavaType::TypeFloat: {
265       float result = object
266                          ? env->CallFloatMethodA(object, id, parameters)
267                          : env->CallStaticFloatMethodA(clazz, id, parameters);
268       if (base::IsFinite(result)) {
269         result_wrapper.AppendDouble(result);
270       } else {
271         result_wrapper.Append(
272             GinJavaBridgeValue::CreateNonFiniteValue(result).release());
273       }
274       break;
275     }
276     case JavaType::TypeDouble: {
277       double result = object
278                           ? env->CallDoubleMethodA(object, id, parameters)
279                           : env->CallStaticDoubleMethodA(clazz, id, parameters);
280       if (base::IsFinite(result)) {
281         result_wrapper.AppendDouble(result);
282       } else {
283         result_wrapper.Append(
284             GinJavaBridgeValue::CreateNonFiniteValue(result).release());
285       }
286       break;
287     }
288     case JavaType::TypeVoid:
289       if (object)
290         env->CallVoidMethodA(object, id, parameters);
291       else
292         env->CallStaticVoidMethodA(clazz, id, parameters);
293       result_wrapper.Append(
294           GinJavaBridgeValue::CreateUndefinedValue().release());
295       break;
296     case JavaType::TypeArray:
297       // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that
298       // return arrays. Spec requires calling the method and converting the
299       // result to a JavaScript array.
300       result_wrapper.Append(
301           GinJavaBridgeValue::CreateUndefinedValue().release());
302       break;
303     case JavaType::TypeString: {
304       jstring java_string = static_cast<jstring>(
305           object ? env->CallObjectMethodA(object, id, parameters)
306                  : env->CallStaticObjectMethodA(clazz, id, parameters));
307       // If an exception was raised, we must clear it before calling most JNI
308       // methods. ScopedJavaLocalRef is liable to make such calls, so we test
309       // first.
310       if (base::android::ClearException(env)) {
311         SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
312         return;
313       }
314       ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string);
315       if (!scoped_java_string.obj()) {
316         // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined.
317         // Spec requires returning a null string.
318         result_wrapper.Append(
319             GinJavaBridgeValue::CreateUndefinedValue().release());
320         break;
321       }
322       result_wrapper.AppendString(
323           ConvertJavaStringToUTF8(env, scoped_java_string.obj()));
324       break;
325     }
326     case JavaType::TypeObject: {
327       // If an exception was raised, we must clear it before calling most JNI
328       // methods. ScopedJavaLocalRef is liable to make such calls, so we test
329       // first.
330       jobject java_object =
331           object ? env->CallObjectMethodA(object, id, parameters)
332                  : env->CallStaticObjectMethodA(clazz, id, parameters);
333       if (base::android::ClearException(env)) {
334         SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
335         return;
336       }
337       ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object);
338       if (!scoped_java_object.obj()) {
339         result_wrapper.Append(base::Value::CreateNullValue());
340         break;
341       }
342       SetObjectResult(scoped_java_object, object_->GetSafeAnnotationClass());
343       return;
344     }
345   }
346   // This is for all cases except JavaType::TypeObject.
347   if (!base::android::ClearException(env)) {
348     SetPrimitiveResult(result_wrapper);
349   } else {
350     SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
351   }
352 }
353
354 }  // namespace content