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 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(), ¶meters[0]);
167 } else {
168 InvokeMethod(
169 obj.obj(), NULL, method->return_type(), method->id(), ¶meters[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, ¶meters[i], method->parameter_type(i));
179 }
180 }
181
SetInvocationError(GinJavaBridgeError error)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
SetPrimitiveResult(const base::ListValue & result_wrapper)189 void GinJavaMethodInvocationHelper::SetPrimitiveResult(
190 const base::ListValue& result_wrapper) {
191 holds_primitive_result_ = true;
192 primitive_result_.reset(result_wrapper.DeepCopy());
193 }
194
SetObjectResult(const base::android::JavaRef<jobject> & object,const base::android::JavaRef<jclass> & safe_annotation_clazz)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
HoldsPrimitiveResult()203 bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() {
204 return holds_primitive_result_;
205 }
206
GetPrimitiveResult()207 const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() {
208 return *primitive_result_.get();
209 }
210
211 const base::android::JavaRef<jobject>&
GetObjectResult()212 GinJavaMethodInvocationHelper::GetObjectResult() {
213 return object_result_;
214 }
215
216 const base::android::JavaRef<jclass>&
GetSafeAnnotationClass()217 GinJavaMethodInvocationHelper::GetSafeAnnotationClass() {
218 return safe_annotation_clazz_;
219 }
220
GetInvocationError()221 const GinJavaBridgeError GinJavaMethodInvocationHelper::GetInvocationError() {
222 return invocation_error_;
223 }
224
InvokeMethod(jobject object,jclass clazz,const JavaType & return_type,jmethodID id,jvalue * parameters)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
355