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
19using base::android::AttachCurrentThread;
20using base::android::ScopedJavaLocalRef;
21
22namespace content {
23
24namespace {
25
26// See frameworks/base/core/java/android/webkit/EventLogTags.logtags
27const int kObjectGetClassInvocationAttemptLogTag = 70151;
28
29// This is an intermediate solution until we fix http://crbug.com/391492.
30std::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
40GinJavaMethodInvocationHelper::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
50GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {}
51
52void 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.
61void 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
80void 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
99bool 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
122void 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
182void GinJavaMethodInvocationHelper::SetInvocationError(
183    GinJavaBridgeError error) {
184  holds_primitive_result_ = true;
185  primitive_result_.reset(new base::ListValue());
186  invocation_error_ = error;
187}
188
189void GinJavaMethodInvocationHelper::SetPrimitiveResult(
190    const base::ListValue& result_wrapper) {
191  holds_primitive_result_ = true;
192  primitive_result_.reset(result_wrapper.DeepCopy());
193}
194
195void 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
203bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() {
204  return holds_primitive_result_;
205}
206
207const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() {
208  return *primitive_result_.get();
209}
210
211const base::android::JavaRef<jobject>&
212GinJavaMethodInvocationHelper::GetObjectResult() {
213  return object_result_;
214}
215
216const base::android::JavaRef<jclass>&
217GinJavaMethodInvocationHelper::GetSafeAnnotationClass() {
218  return safe_annotation_clazz_;
219}
220
221const GinJavaBridgeError GinJavaMethodInvocationHelper::GetInvocationError() {
222  return invocation_error_;
223}
224
225void 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
355