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.
ConvertJavaStringToUTF8(JNIEnv * env,jstring str)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
GinJavaMethodInvocationHelper(scoped_ptr<ObjectDelegate> object,const std::string & method_name,const base::ListValue & arguments)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
~GinJavaMethodInvocationHelper()50 GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {}
51
Init(DispatcherDelegate * dispatcher)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.
BuildObjectRefsFromListValue(DispatcherDelegate * dispatcher,const base::Value * list_value)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
BuildObjectRefsFromDictionaryValue(DispatcherDelegate * dispatcher,const base::Value * dict_value)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
AppendObjectRef(DispatcherDelegate * dispatcher,const base::Value * raw_value)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
Invoke()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 std::vector<jvalue> parameters(method->num_parameters());
151 for (size_t i = 0; i < method->num_parameters(); ++i) {
152 const base::Value* argument;
153 arguments_->Get(i, &argument);
154 parameters[i] = CoerceJavaScriptValueToJavaValue(
155 env, argument, method->parameter_type(i), true, object_refs_);
156 }
157 if (method->is_static()) {
158 InvokeMethod(
159 NULL, cls.obj(), method->return_type(), method->id(), ¶meters[0]);
160 } else {
161 InvokeMethod(
162 obj.obj(), NULL, method->return_type(), method->id(), ¶meters[0]);
163 }
164
165 // Now that we're done with the jvalue, release any local references created
166 // by CoerceJavaScriptValueToJavaValue().
167 for (size_t i = 0; i < method->num_parameters(); ++i) {
168 ReleaseJavaValueIfRequired(env, ¶meters[i], method->parameter_type(i));
169 }
170 }
171
SetInvocationError(GinJavaBridgeError error)172 void GinJavaMethodInvocationHelper::SetInvocationError(
173 GinJavaBridgeError error) {
174 holds_primitive_result_ = true;
175 primitive_result_.reset(new base::ListValue());
176 invocation_error_ = error;
177 }
178
SetPrimitiveResult(const base::ListValue & result_wrapper)179 void GinJavaMethodInvocationHelper::SetPrimitiveResult(
180 const base::ListValue& result_wrapper) {
181 holds_primitive_result_ = true;
182 primitive_result_.reset(result_wrapper.DeepCopy());
183 }
184
SetObjectResult(const base::android::JavaRef<jobject> & object,const base::android::JavaRef<jclass> & safe_annotation_clazz)185 void GinJavaMethodInvocationHelper::SetObjectResult(
186 const base::android::JavaRef<jobject>& object,
187 const base::android::JavaRef<jclass>& safe_annotation_clazz) {
188 holds_primitive_result_ = false;
189 object_result_.Reset(object);
190 safe_annotation_clazz_.Reset(safe_annotation_clazz);
191 }
192
HoldsPrimitiveResult()193 bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() {
194 return holds_primitive_result_;
195 }
196
GetPrimitiveResult()197 const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() {
198 return *primitive_result_.get();
199 }
200
201 const base::android::JavaRef<jobject>&
GetObjectResult()202 GinJavaMethodInvocationHelper::GetObjectResult() {
203 return object_result_;
204 }
205
206 const base::android::JavaRef<jclass>&
GetSafeAnnotationClass()207 GinJavaMethodInvocationHelper::GetSafeAnnotationClass() {
208 return safe_annotation_clazz_;
209 }
210
GetInvocationError()211 const GinJavaBridgeError GinJavaMethodInvocationHelper::GetInvocationError() {
212 return invocation_error_;
213 }
214
InvokeMethod(jobject object,jclass clazz,const JavaType & return_type,jmethodID id,jvalue * parameters)215 void GinJavaMethodInvocationHelper::InvokeMethod(jobject object,
216 jclass clazz,
217 const JavaType& return_type,
218 jmethodID id,
219 jvalue* parameters) {
220 DCHECK(object || clazz);
221 JNIEnv* env = AttachCurrentThread();
222 base::ListValue result_wrapper;
223 switch (return_type.type) {
224 case JavaType::TypeBoolean:
225 result_wrapper.AppendBoolean(
226 object ? env->CallBooleanMethodA(object, id, parameters)
227 : env->CallStaticBooleanMethodA(clazz, id, parameters));
228 break;
229 case JavaType::TypeByte:
230 result_wrapper.AppendInteger(
231 object ? env->CallByteMethodA(object, id, parameters)
232 : env->CallStaticByteMethodA(clazz, id, parameters));
233 break;
234 case JavaType::TypeChar:
235 result_wrapper.AppendInteger(
236 object ? env->CallCharMethodA(object, id, parameters)
237 : env->CallStaticCharMethodA(clazz, id, parameters));
238 break;
239 case JavaType::TypeShort:
240 result_wrapper.AppendInteger(
241 object ? env->CallShortMethodA(object, id, parameters)
242 : env->CallStaticShortMethodA(clazz, id, parameters));
243 break;
244 case JavaType::TypeInt:
245 result_wrapper.AppendInteger(
246 object ? env->CallIntMethodA(object, id, parameters)
247 : env->CallStaticIntMethodA(clazz, id, parameters));
248 break;
249 case JavaType::TypeLong:
250 result_wrapper.AppendDouble(
251 object ? env->CallLongMethodA(object, id, parameters)
252 : env->CallStaticLongMethodA(clazz, id, parameters));
253 break;
254 case JavaType::TypeFloat: {
255 float result = object
256 ? env->CallFloatMethodA(object, id, parameters)
257 : env->CallStaticFloatMethodA(clazz, id, parameters);
258 if (base::IsFinite(result)) {
259 result_wrapper.AppendDouble(result);
260 } else {
261 result_wrapper.Append(
262 GinJavaBridgeValue::CreateNonFiniteValue(result).release());
263 }
264 break;
265 }
266 case JavaType::TypeDouble: {
267 double result = object
268 ? env->CallDoubleMethodA(object, id, parameters)
269 : env->CallStaticDoubleMethodA(clazz, id, parameters);
270 if (base::IsFinite(result)) {
271 result_wrapper.AppendDouble(result);
272 } else {
273 result_wrapper.Append(
274 GinJavaBridgeValue::CreateNonFiniteValue(result).release());
275 }
276 break;
277 }
278 case JavaType::TypeVoid:
279 if (object)
280 env->CallVoidMethodA(object, id, parameters);
281 else
282 env->CallStaticVoidMethodA(clazz, id, parameters);
283 result_wrapper.Append(
284 GinJavaBridgeValue::CreateUndefinedValue().release());
285 break;
286 case JavaType::TypeArray:
287 // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that
288 // return arrays. Spec requires calling the method and converting the
289 // result to a JavaScript array.
290 result_wrapper.Append(
291 GinJavaBridgeValue::CreateUndefinedValue().release());
292 break;
293 case JavaType::TypeString: {
294 jstring java_string = static_cast<jstring>(
295 object ? env->CallObjectMethodA(object, id, parameters)
296 : env->CallStaticObjectMethodA(clazz, id, parameters));
297 // If an exception was raised, we must clear it before calling most JNI
298 // methods. ScopedJavaLocalRef is liable to make such calls, so we test
299 // first.
300 if (base::android::ClearException(env)) {
301 SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
302 return;
303 }
304 ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string);
305 if (!scoped_java_string.obj()) {
306 // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined.
307 // Spec requires returning a null string.
308 result_wrapper.Append(
309 GinJavaBridgeValue::CreateUndefinedValue().release());
310 break;
311 }
312 result_wrapper.AppendString(
313 ConvertJavaStringToUTF8(env, scoped_java_string.obj()));
314 break;
315 }
316 case JavaType::TypeObject: {
317 // If an exception was raised, we must clear it before calling most JNI
318 // methods. ScopedJavaLocalRef is liable to make such calls, so we test
319 // first.
320 jobject java_object =
321 object ? env->CallObjectMethodA(object, id, parameters)
322 : env->CallStaticObjectMethodA(clazz, id, parameters);
323 if (base::android::ClearException(env)) {
324 SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
325 return;
326 }
327 ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object);
328 if (!scoped_java_object.obj()) {
329 result_wrapper.Append(base::Value::CreateNullValue());
330 break;
331 }
332 SetObjectResult(scoped_java_object, object_->GetSafeAnnotationClass());
333 return;
334 }
335 }
336 // This is for all cases except JavaType::TypeObject.
337 if (!base::android::ClearException(env)) {
338 SetPrimitiveResult(result_wrapper);
339 } else {
340 SetInvocationError(kGinJavaBridgeJavaExceptionRaised);
341 }
342 }
343
344 } // namespace content
345