OLD | NEW |
---|---|
(Empty) | |
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/renderer_host/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/android/jni_string.h" | |
12 #include "base/float_util.h" | |
13 #include "base/strings/string_number_conversions.h" | |
14 #include "base/strings/stringprintf.h" | |
15 #include "base/strings/utf_string_conversions.h" | |
16 #include "content/browser/renderer_host/java/java_method.h" | |
17 #include "content/browser/renderer_host/java/jni_helper.h" | |
18 #include "content/common/android/gin_java_bridge_value.h" | |
19 #include "content/public/browser/browser_thread.h" | |
20 | |
21 using base::android::AttachCurrentThread; | |
22 using base::android::ConvertUTF8ToJavaString; | |
23 using base::android::ScopedJavaLocalRef; | |
24 | |
25 namespace content { | |
26 | |
27 namespace { | |
28 | |
29 const char kJavaLangString[] = "java/lang/String"; | |
30 const char kObjectIsGone[] = "Java object is gone"; | |
31 const char kMethodNotFound[] = "Method not found"; | |
32 const char kAccessToObjectGetClassIsBlocked[] = | |
33 "Access to java.lang.Object.getClass is blocked"; | |
34 const char kJavaExceptionRaised[] = | |
35 "Java exception has been raised during method invocation"; | |
36 const char kUndefined[] = "undefined"; | |
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 } | |
48 | |
49 GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {} | |
50 | |
51 void GinJavaMethodInvocationHelper::Init(DispatcherDelegate* dispatcher) { | |
52 // Build on the UI thread a map of object_id -> WeakRef for Java objects from | |
53 // |arguments_|. Then we can use this map on the background thread without | |
54 // accessing |dispatcher|. | |
55 BuildObjectRefsFromListValue(dispatcher, arguments_.get()); | |
56 } | |
57 | |
58 // As V8ValueConverter has finite recursion depth when serializing | |
59 // JavaScript values, we don't bother about having a recursion threshold here. | |
60 void GinJavaMethodInvocationHelper::BuildObjectRefsFromListValue( | |
61 DispatcherDelegate* dispatcher, | |
62 const base::Value* list_value) { | |
63 DCHECK(list_value->IsType(base::Value::TYPE_LIST)); | |
64 const base::ListValue* list; | |
65 list_value->GetAsList(&list); | |
66 for (base::ListValue::const_iterator iter = list->begin(); | |
67 iter != list->end(); | |
68 ++iter) { | |
69 if (AppendObjectRef(dispatcher, *iter)) | |
70 continue; | |
71 if ((*iter)->IsType(base::Value::TYPE_LIST)) { | |
72 BuildObjectRefsFromListValue(dispatcher, *iter); | |
73 } else if ((*iter)->IsType(base::Value::TYPE_DICTIONARY)) { | |
74 BuildObjectRefsFromDictionaryValue(dispatcher, *iter); | |
75 } | |
76 } | |
77 } | |
78 | |
79 void GinJavaMethodInvocationHelper::BuildObjectRefsFromDictionaryValue( | |
80 DispatcherDelegate* dispatcher, | |
81 const base::Value* dict_value) { | |
82 DCHECK(dict_value->IsType(base::Value::TYPE_DICTIONARY)); | |
83 const base::DictionaryValue* dict; | |
84 dict_value->GetAsDictionary(&dict); | |
85 for (base::DictionaryValue::Iterator iter(*dict); | |
86 !iter.IsAtEnd(); | |
87 iter.Advance()) { | |
88 if (AppendObjectRef(dispatcher, &iter.value())) | |
89 continue; | |
90 if (iter.value().IsType(base::Value::TYPE_LIST)) { | |
91 BuildObjectRefsFromListValue(dispatcher, &iter.value()); | |
92 } else if (iter.value().IsType(base::Value::TYPE_DICTIONARY)) { | |
93 BuildObjectRefsFromDictionaryValue(dispatcher, &iter.value()); | |
94 } | |
95 } | |
96 } | |
97 | |
98 bool GinJavaMethodInvocationHelper::AppendObjectRef( | |
99 DispatcherDelegate* dispatcher, | |
100 const base::Value* raw_value) { | |
101 if (!GinJavaBridgeValue::ContainsGinJavaBridgeValue(raw_value)) | |
102 return false; | |
103 scoped_ptr<const GinJavaBridgeValue> value( | |
104 GinJavaBridgeValue::FromValue(raw_value)); | |
105 if (!value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID)) | |
106 return false; | |
107 GinJavaBoundObject::ObjectID object_id; | |
108 if (value->GetAsObjectID(&object_id)) { | |
109 ObjectRefs::iterator iter = object_refs_.find(object_id); | |
110 if (iter == object_refs_.end()) { | |
111 JavaObjectWeakGlobalRef object_ref( | |
112 dispatcher->GetObjectWeakRef(object_id)); | |
113 if (!object_ref.is_null()) { | |
114 object_refs_.insert(std::make_pair(object_id, object_ref)); | |
115 } | |
116 } | |
117 } | |
118 return true; | |
119 } | |
120 | |
121 void GinJavaMethodInvocationHelper::Invoke() { | |
122 JNIEnv* env = AttachCurrentThread(); | |
123 base::android::ScopedJavaLocalRef<jobject> obj(object_->GetLocalRef(env)); | |
124 if (obj.is_null()) { | |
125 SetInvocationFailure(kObjectIsGone); | |
126 return; | |
127 } | |
128 const JavaMethod* method = | |
129 object_->FindMethod(method_name_, arguments_->GetSize()); | |
130 if (!method) { | |
131 SetInvocationFailure(kMethodNotFound); | |
132 return; | |
133 } | |
134 | |
135 if (object_->IsObjectGetClassMethod(method)) { | |
136 // See frameworks/base/core/java/android/webkit/EventLogTags.logtags | |
137 base::android::eventLogWriteInt(70151, getuid()); | |
bulach
2014/06/09 11:44:33
any chance we could have those defined here?!
mnaganov (inactive)
2014/06/11 13:50:44
I have moved the value into a named constant. Ther
| |
138 SetInvocationFailure(kAccessToObjectGetClassIsBlocked); | |
139 return; | |
140 } | |
141 | |
142 std::vector<jvalue> parameters(method->num_parameters()); | |
143 for (size_t i = 0; i < method->num_parameters(); ++i) { | |
144 const base::Value* argument; | |
145 arguments_->Get(i, &argument); | |
146 parameters[i] = CoerceJavaScriptValueToJavaValue(argument, | |
147 method->parameter_type(i), | |
148 true); | |
149 } | |
150 InvokeMethod(obj.obj(), | |
151 method->return_type(), | |
152 method->id(), | |
153 ¶meters[0]); | |
154 | |
155 // Now that we're done with the jvalue, release any local references created | |
156 // by CoerceJavaScriptValueToJavaValue(). | |
157 for (size_t i = 0; i < method->num_parameters(); ++i) { | |
158 ReleaseJavaValueIfRequired(env, ¶meters[i], method->parameter_type(i)); | |
159 } | |
160 } | |
161 | |
162 void GinJavaMethodInvocationHelper::SetInvocationFailure( | |
163 const char* error_message) { | |
164 holds_primitive_result_ = true; | |
165 primitive_result_.reset(new base::ListValue()); | |
166 error_message_ = error_message; | |
167 } | |
168 | |
169 void GinJavaMethodInvocationHelper::SetPrimitiveResult( | |
170 const base::ListValue& result_wrapper) { | |
171 holds_primitive_result_ = true; | |
172 primitive_result_.reset(result_wrapper.DeepCopy()); | |
173 } | |
174 | |
175 void GinJavaMethodInvocationHelper::SetObjectResult( | |
176 const base::android::JavaRef<jobject>& object, | |
177 const base::android::JavaRef<jclass>& safe_annotation_clazz) { | |
178 holds_primitive_result_ = false; | |
179 object_result_.Reset(object); | |
180 safe_annotation_clazz_.Reset(safe_annotation_clazz); | |
181 } | |
182 | |
183 bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() { | |
184 return holds_primitive_result_; | |
185 } | |
186 | |
187 const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() { | |
188 return *primitive_result_.get(); | |
189 } | |
190 | |
191 const base::android::JavaRef<jobject>& | |
192 GinJavaMethodInvocationHelper::GetObjectResult() { | |
193 return object_result_; | |
194 } | |
195 | |
196 const base::android::JavaRef<jclass>& | |
197 GinJavaMethodInvocationHelper::GetSafeAnnotationClass() { | |
198 return safe_annotation_clazz_; | |
199 } | |
200 | |
201 const std::string& GinJavaMethodInvocationHelper::GetErrorMessage() { | |
202 return error_message_; | |
203 } | |
204 | |
205 void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, | |
206 const JavaType& return_type, | |
207 jmethodID id, | |
208 jvalue* parameters) { | |
209 JNIEnv* env = AttachCurrentThread(); | |
210 base::ListValue result_wrapper; | |
211 switch (return_type.type) { | |
212 case JavaType::TypeBoolean: | |
213 result_wrapper.AppendBoolean( | |
214 env->CallBooleanMethodA(object, id, parameters)); | |
215 break; | |
216 case JavaType::TypeByte: | |
217 result_wrapper.AppendInteger( | |
218 env->CallByteMethodA(object, id, parameters)); | |
219 break; | |
220 case JavaType::TypeChar: | |
221 result_wrapper.AppendInteger( | |
222 env->CallCharMethodA(object, id, parameters)); | |
223 break; | |
224 case JavaType::TypeShort: | |
225 result_wrapper.AppendInteger( | |
226 env->CallShortMethodA(object, id, parameters)); | |
227 break; | |
228 case JavaType::TypeInt: | |
229 result_wrapper.AppendInteger( | |
230 env->CallIntMethodA(object, id, parameters)); | |
231 break; | |
232 case JavaType::TypeLong: | |
233 result_wrapper.AppendDouble( | |
234 env->CallLongMethodA(object, id, parameters)); | |
235 break; | |
236 case JavaType::TypeFloat: { | |
237 float result = env->CallFloatMethodA(object, id, parameters); | |
238 if (base::IsFinite(result)) { | |
239 result_wrapper.AppendDouble(result); | |
240 } else { | |
241 result_wrapper.Append( | |
242 GinJavaBridgeValue::CreateNonFiniteValue(result).release()); | |
243 } | |
244 break; | |
245 } | |
246 case JavaType::TypeDouble: { | |
247 double result = env->CallDoubleMethodA(object, id, parameters); | |
248 if (base::IsFinite(result)) { | |
249 result_wrapper.AppendDouble(result); | |
250 } else { | |
251 result_wrapper.Append( | |
252 GinJavaBridgeValue::CreateNonFiniteValue(result).release()); | |
253 } | |
254 break; | |
255 } | |
256 case JavaType::TypeVoid: | |
257 env->CallVoidMethodA(object, id, parameters); | |
258 result_wrapper.Append( | |
259 GinJavaBridgeValue::CreateUndefinedValue().release()); | |
260 break; | |
261 case JavaType::TypeArray: | |
262 // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that | |
263 // return arrays. Spec requires calling the method and converting the | |
264 // result to a JavaScript array. | |
265 result_wrapper.Append( | |
266 GinJavaBridgeValue::CreateUndefinedValue().release()); | |
267 break; | |
268 case JavaType::TypeString: { | |
269 jstring java_string = static_cast<jstring>( | |
270 env->CallObjectMethodA(object, id, parameters)); | |
271 // If an exception was raised, we must clear it before calling most JNI | |
272 // methods. ScopedJavaLocalRef is liable to make such calls, so we test | |
273 // first. | |
274 if (base::android::ClearException(env)) { | |
275 SetInvocationFailure(kJavaExceptionRaised); | |
276 return; | |
277 } | |
278 ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string); | |
279 if (!scoped_java_string.obj()) { | |
280 // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined. | |
281 // Spec requires returning a null string. | |
282 result_wrapper.Append( | |
283 GinJavaBridgeValue::CreateUndefinedValue().release()); | |
284 break; | |
285 } | |
286 result_wrapper.AppendString( | |
287 base::android::ConvertJavaStringToUTF8(scoped_java_string)); | |
288 break; | |
289 } | |
290 case JavaType::TypeObject: { | |
291 // If an exception was raised, we must clear it before calling most JNI | |
292 // methods. ScopedJavaLocalRef is liable to make such calls, so we test | |
293 // first. | |
294 jobject java_object = env->CallObjectMethodA(object, id, parameters); | |
295 if (base::android::ClearException(env)) { | |
296 SetInvocationFailure(kJavaExceptionRaised); | |
297 return; | |
298 } | |
299 ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object); | |
300 if (!scoped_java_object.obj()) { | |
301 result_wrapper.Append(base::Value::CreateNullValue()); | |
302 break; | |
303 } | |
304 SetObjectResult(scoped_java_object, object_->GetSafeAnnotationClass()); | |
305 return; | |
306 } | |
307 } | |
308 // This is for all cases except JavaType::TypeObject. | |
309 if (!base::android::ClearException(env)) { | |
310 SetPrimitiveResult(result_wrapper); | |
311 } else { | |
312 SetInvocationFailure(kJavaExceptionRaised); | |
313 } | |
314 } | |
315 | |
316 void GinJavaMethodInvocationHelper::ReleaseJavaValueIfRequired(JNIEnv* env, | |
317 jvalue* value, | |
318 const JavaType& type) { | |
bulach
2014/06/09 11:44:33
nit: left align, or keep all indented by +4 like t
mnaganov (inactive)
2014/06/11 13:50:44
Done.
| |
319 if (type.type == JavaType::TypeString || | |
320 type.type == JavaType::TypeObject || | |
321 type.type == JavaType::TypeArray) { | |
322 env->DeleteLocalRef(value->l); | |
323 value->l = NULL; | |
324 } | |
325 } | |
326 | |
327 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptValueToJavaValue( | |
328 const base::Value* value, | |
329 const JavaType& target_type, | |
330 bool coerce_to_string) { | |
331 // Note that in all these conversions, the relevant field of the jvalue must | |
332 // always be explicitly set, as jvalue does not initialize its fields. | |
333 | |
334 switch (value->GetType()) { | |
335 case base::Value::TYPE_INTEGER: | |
336 return CoerceJavaScriptIntegerToJavaValue(value, target_type, | |
337 coerce_to_string); | |
bulach
2014/06/09 11:44:33
nit: align
mnaganov (inactive)
2014/06/11 13:50:44
Done, thanks!
| |
338 case base::Value::TYPE_DOUBLE: { | |
339 double double_value; | |
340 value->GetAsDouble(&double_value); | |
341 return CoerceJavaScriptDoubleToJavaValue(double_value, target_type, | |
342 coerce_to_string); | |
343 } | |
344 case base::Value::TYPE_BOOLEAN: | |
345 return CoerceJavaScriptBooleanToJavaValue(value, target_type, | |
346 coerce_to_string); | |
347 case base::Value::TYPE_STRING: | |
348 return CoerceJavaScriptStringToJavaValue(value, target_type); | |
349 case base::Value::TYPE_DICTIONARY: | |
350 case base::Value::TYPE_LIST: | |
351 return CoerceJavaScriptObjectToJavaValue(value, target_type, | |
352 coerce_to_string); | |
353 case base::Value::TYPE_NULL: | |
354 return CoerceJavaScriptNullOrUndefinedToJavaValue(value, target_type, | |
355 coerce_to_string); | |
356 case base::Value::TYPE_BINARY: | |
357 return CoerceGinJavaBridgeValueToJavaValue(value, target_type, | |
358 coerce_to_string); | |
359 } | |
360 NOTREACHED(); | |
361 return jvalue(); | |
362 } | |
363 | |
364 jvalue GinJavaMethodInvocationHelper::CoerceGinJavaBridgeValueToJavaValue( | |
365 const base::Value* value, | |
366 const JavaType& target_type, | |
367 bool coerce_to_string) { | |
368 DCHECK(GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)); | |
369 scoped_ptr<const GinJavaBridgeValue> gin_value( | |
370 GinJavaBridgeValue::FromValue(value)); | |
371 switch (gin_value->GetType()) { | |
372 case GinJavaBridgeValue::TYPE_UNDEFINED: | |
373 return CoerceJavaScriptNullOrUndefinedToJavaValue( | |
374 value, target_type, coerce_to_string); | |
375 case GinJavaBridgeValue::TYPE_NONFINITE: { | |
376 float float_value; | |
377 gin_value->GetAsNonFinite(&float_value); | |
378 return CoerceJavaScriptDoubleToJavaValue( | |
379 float_value, target_type, coerce_to_string); | |
380 } | |
381 case GinJavaBridgeValue::TYPE_OBJECT_ID: | |
382 return CoerceJavaScriptObjectToJavaValue( | |
383 value, target_type, coerce_to_string); | |
384 default: | |
385 NOTREACHED(); | |
386 } | |
387 return jvalue(); | |
388 } | |
389 | |
390 namespace { | |
391 | |
392 double RoundDoubleTowardsZero(const double& x) { | |
393 if (std::isnan(x)) { | |
394 return 0.0; | |
395 } | |
396 return x > 0.0 ? floor(x) : ceil(x); | |
397 } | |
398 | |
399 // Rounds to jlong using Java's type conversion rules. | |
400 jlong RoundDoubleToLong(const double& x) { | |
401 double intermediate = RoundDoubleTowardsZero(x); | |
402 // The int64 limits can not be converted exactly to double values, so we | |
403 // compare to custom constants. kint64max is 2^63 - 1, but the spacing | |
404 // between double values in the the range 2^62 to 2^63 is 2^10. The cast is | |
405 // required to silence a spurious gcc warning for integer overflow. | |
406 const int64 limit = (GG_INT64_C(1) << 63) - static_cast<uint64>(1 << 10); | |
bulach
2014/06/09 11:44:33
nit: kLimit
mnaganov (inactive)
2014/06/11 13:50:44
Done.
| |
407 DCHECK(limit > 0); | |
408 const double kLargestDoubleLessThanInt64Max = limit; | |
409 const double kSmallestDoubleGreaterThanInt64Min = -limit; | |
410 if (intermediate > kLargestDoubleLessThanInt64Max) { | |
411 return kint64max; | |
412 } | |
413 if (intermediate < kSmallestDoubleGreaterThanInt64Min) { | |
414 return kint64min; | |
415 } | |
416 return static_cast<jlong>(intermediate); | |
417 } | |
418 | |
419 // Rounds to jint using Java's type conversion rules. | |
420 jint RoundDoubleToInt(const double& x) { | |
421 double intermediate = RoundDoubleTowardsZero(x); | |
422 // The int32 limits cast exactly to double values. | |
423 intermediate = std::min(intermediate, static_cast<double>(kint32max)); | |
424 intermediate = std::max(intermediate, static_cast<double>(kint32min)); | |
425 return static_cast<jint>(intermediate); | |
426 } | |
427 | |
428 } // namespace | |
429 | |
430 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptIntegerToJavaValue( | |
431 const base::Value* value, | |
432 const JavaType& target_type, | |
433 bool coerce_to_string) { | |
434 // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES. | |
435 | |
436 // For conversion to numeric types, we need to replicate Java's type | |
437 // conversion rules. This requires that for integer values, we simply discard | |
438 // all but the lowest n buts, where n is the number of bits in the target | |
439 // type. | |
440 jvalue result; | |
441 int int_value; | |
442 value->GetAsInteger(&int_value); | |
443 switch (target_type.type) { | |
444 case JavaType::TypeByte: | |
445 result.b = static_cast<jbyte>(int_value); | |
446 break; | |
447 case JavaType::TypeChar: | |
448 result.c = static_cast<jchar>(int_value); | |
449 break; | |
450 case JavaType::TypeShort: | |
451 result.s = static_cast<jshort>(int_value); | |
452 break; | |
453 case JavaType::TypeInt: | |
454 result.i = int_value; | |
455 break; | |
456 case JavaType::TypeLong: | |
457 result.j = int_value; | |
458 break; | |
459 case JavaType::TypeFloat: | |
460 result.f = int_value; | |
461 break; | |
462 case JavaType::TypeDouble: | |
463 result.d = int_value; | |
464 break; | |
465 case JavaType::TypeObject: | |
466 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec | |
467 // requires handling object equivalents of primitive types. | |
468 result.l = NULL; | |
469 break; | |
470 case JavaType::TypeString: | |
471 result.l = coerce_to_string | |
472 ? ConvertUTF8ToJavaString(AttachCurrentThread(), | |
473 base::Int64ToString(int_value)) | |
474 .Release() | |
475 : NULL; | |
476 break; | |
477 case JavaType::TypeBoolean: | |
478 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec | |
479 // requires converting to false for 0 or NaN, true otherwise. | |
480 result.z = JNI_FALSE; | |
481 break; | |
482 case JavaType::TypeArray: | |
483 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec | |
484 // requires raising a JavaScript exception. | |
485 result.l = NULL; | |
486 break; | |
487 case JavaType::TypeVoid: | |
488 // Conversion to void must never happen. | |
489 NOTREACHED(); | |
490 break; | |
491 } | |
492 return result; | |
493 } | |
494 | |
495 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptDoubleToJavaValue( | |
496 double double_value, | |
497 const JavaType& target_type, | |
498 bool coerce_to_string) { | |
499 // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES. | |
500 // For conversion to numeric types, we need to replicate Java's type | |
501 // conversion rules. | |
502 jvalue result; | |
503 switch (target_type.type) { | |
504 case JavaType::TypeByte: | |
505 result.b = static_cast<jbyte>(RoundDoubleToInt(double_value)); | |
506 break; | |
507 case JavaType::TypeChar: | |
508 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0. | |
509 // Spec requires converting doubles similarly to how we convert doubles to | |
510 // other numeric types. | |
511 result.c = 0; | |
512 break; | |
513 case JavaType::TypeShort: | |
514 result.s = static_cast<jshort>(RoundDoubleToInt(double_value)); | |
515 break; | |
516 case JavaType::TypeInt: | |
517 result.i = RoundDoubleToInt(double_value); | |
518 break; | |
519 case JavaType::TypeLong: | |
520 result.j = RoundDoubleToLong(double_value); | |
521 break; | |
522 case JavaType::TypeFloat: | |
523 result.f = static_cast<jfloat>(double_value); | |
524 break; | |
525 case JavaType::TypeDouble: | |
526 result.d = double_value; | |
527 break; | |
528 case JavaType::TypeObject: | |
529 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec | |
530 // requires handling object equivalents of primitive types. | |
531 result.l = NULL; | |
532 break; | |
533 case JavaType::TypeString: | |
534 result.l = coerce_to_string | |
535 ? ConvertUTF8ToJavaString( | |
536 AttachCurrentThread(), | |
537 base::StringPrintf("%.6lg", double_value)).Release() | |
538 : NULL; | |
539 break; | |
540 case JavaType::TypeBoolean: | |
541 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec | |
542 // requires converting to false for 0 or NaN, true otherwise. | |
543 result.z = JNI_FALSE; | |
544 break; | |
545 case JavaType::TypeArray: | |
546 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec | |
547 // requires raising a JavaScript exception. | |
548 result.l = NULL; | |
549 break; | |
550 case JavaType::TypeVoid: | |
551 // Conversion to void must never happen. | |
552 NOTREACHED(); | |
553 break; | |
554 } | |
555 return result; | |
556 } | |
557 | |
558 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptBooleanToJavaValue( | |
559 const base::Value* value, | |
560 const JavaType& target_type, | |
561 bool coerce_to_string) { | |
562 // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES. | |
563 bool boolean_value; | |
564 value->GetAsBoolean(&boolean_value); | |
565 jvalue result; | |
566 switch (target_type.type) { | |
567 case JavaType::TypeBoolean: | |
568 result.z = boolean_value ? JNI_TRUE : JNI_FALSE; | |
569 break; | |
570 case JavaType::TypeObject: | |
571 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec | |
572 // requires handling java.lang.Boolean and java.lang.Object. | |
573 result.l = NULL; | |
574 break; | |
575 case JavaType::TypeString: | |
576 result.l = coerce_to_string ? | |
577 ConvertUTF8ToJavaString(AttachCurrentThread(), | |
578 boolean_value ? "true" : "false").Release() : | |
579 NULL; | |
580 break; | |
581 case JavaType::TypeByte: | |
582 case JavaType::TypeChar: | |
583 case JavaType::TypeShort: | |
584 case JavaType::TypeInt: | |
585 case JavaType::TypeLong: | |
586 case JavaType::TypeFloat: | |
587 case JavaType::TypeDouble: { | |
588 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec | |
589 // requires converting to 0 or 1. | |
590 jvalue null_value = {0}; | |
591 result = null_value; | |
592 break; | |
593 } | |
594 case JavaType::TypeArray: | |
595 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec | |
596 // requires raising a JavaScript exception. | |
597 result.l = NULL; | |
598 break; | |
599 case JavaType::TypeVoid: | |
600 // Conversion to void must never happen. | |
601 NOTREACHED(); | |
602 break; | |
603 } | |
604 return result; | |
605 } | |
606 | |
607 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptStringToJavaValue( | |
608 const base::Value* value, | |
609 const JavaType& target_type) { | |
610 // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES. | |
611 jvalue result; | |
612 switch (target_type.type) { | |
613 case JavaType::TypeString: { | |
614 std::string string_result; | |
615 value->GetAsString(&string_result); | |
616 result.l = ConvertUTF8ToJavaString(AttachCurrentThread(), string_result) | |
617 .Release(); | |
618 break; | |
619 } | |
620 case JavaType::TypeObject: | |
621 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec | |
622 // requires handling java.lang.Object. | |
623 result.l = NULL; | |
624 break; | |
625 case JavaType::TypeByte: | |
626 case JavaType::TypeShort: | |
627 case JavaType::TypeInt: | |
628 case JavaType::TypeLong: | |
629 case JavaType::TypeFloat: | |
630 case JavaType::TypeDouble: { | |
631 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec | |
632 // requires using valueOf() method of corresponding object type. | |
633 jvalue null_value = {0}; | |
634 result = null_value; | |
635 break; | |
636 } | |
637 case JavaType::TypeChar: | |
638 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec | |
639 // requires using java.lang.Short.decode(). | |
640 result.c = 0; | |
641 break; | |
642 case JavaType::TypeBoolean: | |
643 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec | |
644 // requires converting the empty string to false, otherwise true. | |
645 result.z = JNI_FALSE; | |
646 break; | |
647 case JavaType::TypeArray: | |
648 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec | |
649 // requires raising a JavaScript exception. | |
650 result.l = NULL; | |
651 break; | |
652 case JavaType::TypeVoid: | |
653 // Conversion to void must never happen. | |
654 NOTREACHED(); | |
655 break; | |
656 } | |
657 return result; | |
658 } | |
659 | |
660 jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptObjectToJavaValue( | |
661 const base::Value* value, | |
662 const JavaType& target_type, | |
663 bool coerce_to_string) { | |
664 // This covers both JavaScript objects (including arrays) and Java objects. | |
665 // See http://jdk6.java.net/plugin2/liveconnect/#JS_OTHER_OBJECTS, | |
666 // http://jdk6.java.net/plugin2/liveconnect/#JS_ARRAY_VALUES and | |
667 // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS | |
668 jvalue result; | |
669 switch (target_type.type) { | |
670 case JavaType::TypeObject: { | |
671 if (GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)) { | |
672 scoped_ptr<const GinJavaBridgeValue> gin_value( | |
673 GinJavaBridgeValue::FromValue(value)); | |
674 DCHECK(gin_value); | |
675 DCHECK(gin_value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID)); | |
676 base::android::ScopedJavaLocalRef<jobject> obj; | |
677 GinJavaBoundObject::ObjectID object_id; | |
678 if (gin_value->GetAsObjectID(&object_id)) { | |
679 ObjectRefs::iterator iter = object_refs_.find(object_id); | |
680 if (iter != object_refs_.end()) { | |
681 obj.Reset(iter->second.get(AttachCurrentThread())); | |
682 } | |
683 } | |
684 result.l = obj.Release(); | |
685 } else { | |
686 // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec | |
687 // requires converting if the target type is | |
688 // netscape.javascript.JSObject, otherwise raising a JavaScript | |
689 // exception. | |
690 result.l = NULL; | |
691 } | |
692 break; | |
693 } | |
694 case JavaType::TypeString: | |
695 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to | |
696 // "undefined". Spec requires calling toString() on the Java object. | |
697 result.l = coerce_to_string | |
698 ? ConvertUTF8ToJavaString(AttachCurrentThread(), | |
699 kUndefined).Release() | |
700 : NULL; | |
701 break; | |
702 case JavaType::TypeByte: | |
703 case JavaType::TypeShort: | |
704 case JavaType::TypeInt: | |
705 case JavaType::TypeLong: | |
706 case JavaType::TypeFloat: | |
707 case JavaType::TypeDouble: | |
708 case JavaType::TypeChar: { | |
709 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec | |
710 // requires raising a JavaScript exception. | |
711 jvalue null_value = {0}; | |
712 result = null_value; | |
713 break; | |
714 } | |
715 case JavaType::TypeBoolean: | |
716 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec | |
717 // requires raising a JavaScript exception. | |
718 result.z = JNI_FALSE; | |
719 break; | |
720 case JavaType::TypeArray: | |
721 if (value->IsType(base::Value::TYPE_DICTIONARY)) { | |
722 result.l = CoerceJavaScriptDictionaryToArray(value, target_type); | |
723 } else if (value->IsType(base::Value::TYPE_LIST)) { | |
724 result.l = CoerceJavaScriptListToArray(value, target_type); | |
725 } else { | |
726 result.l = NULL; | |
727 } | |
728 break; | |
729 case JavaType::TypeVoid: | |
730 // Conversion to void must never happen. | |
731 NOTREACHED(); | |
732 break; | |
733 } | |
734 return result; | |
735 } | |
736 | |
737 jobject GinJavaMethodInvocationHelper::CoerceJavaScriptListToArray( | |
738 const base::Value* value, | |
739 const JavaType& target_type) { | |
740 DCHECK_EQ(JavaType::TypeArray, target_type.type); | |
741 const JavaType& target_inner_type = *target_type.inner_type.get(); | |
742 // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for | |
743 // multi-dimensional arrays. Spec requires handling multi-demensional arrays. | |
744 if (target_inner_type.type == JavaType::TypeArray) { | |
745 return NULL; | |
746 } | |
747 | |
748 // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object | |
749 // arrays. Spec requires handling object arrays. | |
750 if (target_inner_type.type == JavaType::TypeObject) { | |
751 return NULL; | |
752 } | |
753 | |
754 const base::ListValue* list_value; | |
755 value->GetAsList(&list_value); | |
756 // Create the Java array. | |
757 jsize length = static_cast<jsize>(list_value->GetSize()); | |
758 jobject result = CreateJavaArray(target_inner_type, length); | |
759 if (!result) { | |
760 return NULL; | |
761 } | |
762 JNIEnv* env = AttachCurrentThread(); | |
763 scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); | |
764 for (jsize i = 0; i < length; ++i) { | |
765 const base::Value* value_element = null_value.get(); | |
766 list_value->Get(i, &value_element); | |
767 jvalue element = CoerceJavaScriptValueToJavaValue(value_element, | |
768 target_inner_type, | |
769 false); | |
770 SetArrayElement(result, target_inner_type, i, element); | |
771 // CoerceJavaScriptValueToJavaValue() creates new local references to | |
772 // strings, objects and arrays. Of these, only strings can occur here. | |
773 // SetArrayElement() causes the array to take its own reference to the | |
774 // string, so we can now release the local reference. | |
775 DCHECK_NE(JavaType::TypeObject, target_inner_type.type); | |
776 DCHECK_NE(JavaType::TypeArray, target_inner_type.type); | |
777 ReleaseJavaValueIfRequired(env, &element, target_inner_type); | |
778 } | |
779 | |
780 return result; | |
781 } | |
782 | |
783 jobject GinJavaMethodInvocationHelper::CoerceJavaScriptDictionaryToArray( | |
784 const base::Value* value, | |
785 const JavaType& target_type) { | |
786 DCHECK_EQ(JavaType::TypeArray, target_type.type); | |
787 | |
788 const JavaType& target_inner_type = *target_type.inner_type.get(); | |
789 // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for | |
790 // multi-dimensional arrays. Spec requires handling multi-demensional arrays. | |
791 if (target_inner_type.type == JavaType::TypeArray) { | |
792 return NULL; | |
793 } | |
794 | |
795 // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object | |
796 // arrays. Spec requires handling object arrays. | |
797 if (target_inner_type.type == JavaType::TypeObject) { | |
798 return NULL; | |
799 } | |
800 | |
801 const base::DictionaryValue* dictionary_value; | |
802 value->GetAsDictionary(&dictionary_value); | |
803 const base::Value* length_value; | |
804 // If the object does not have a length property, return null. | |
805 if (!dictionary_value->Get("length", &length_value)) { | |
806 return NULL; | |
807 } | |
808 | |
809 // If the length property does not have numeric type, or is outside the valid | |
810 // range for a Java array length, return null. | |
811 jsize length = -1; | |
812 if (length_value->IsType(base::Value::TYPE_INTEGER)) { | |
813 int int_length; | |
814 length_value->GetAsInteger(&int_length); | |
815 if (int_length >= 0 && int_length <= kint32max) { | |
816 length = static_cast<jsize>(int_length); | |
817 } | |
818 } else if (length_value->IsType(base::Value::TYPE_DOUBLE)) { | |
819 double double_length; | |
820 length_value->GetAsDouble(&double_length); | |
821 if (double_length >= 0.0 && double_length <= kint32max) { | |
822 length = static_cast<jsize>(double_length); | |
823 } | |
824 } | |
825 if (length == -1) { | |
826 return NULL; | |
827 } | |
828 | |
829 jobject result = CreateJavaArray(target_inner_type, length); | |
830 if (!result) { | |
831 return NULL; | |
832 } | |
833 scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); | |
834 JNIEnv* env = AttachCurrentThread(); | |
835 for (jsize i = 0; i < length; ++i) { | |
836 const std::string key(base::IntToString(i)); | |
837 const base::Value* value_element = null_value.get(); | |
838 if (dictionary_value->HasKey(key)) { | |
839 dictionary_value->Get(key, &value_element); | |
840 } | |
841 jvalue element = CoerceJavaScriptValueToJavaValue(value_element, | |
842 target_inner_type, | |
843 false); | |
844 SetArrayElement(result, target_inner_type, i, element); | |
845 // CoerceJavaScriptValueToJavaValue() creates new local references to | |
846 // strings, objects and arrays. Of these, only strings can occur here. | |
847 // SetArrayElement() causes the array to take its own reference to the | |
848 // string, so we can now release the local reference. | |
849 DCHECK_NE(JavaType::TypeObject, target_inner_type.type); | |
850 DCHECK_NE(JavaType::TypeArray, target_inner_type.type); | |
851 ReleaseJavaValueIfRequired(env, &element, target_inner_type); | |
852 } | |
853 | |
854 return result; | |
855 } | |
856 | |
857 // Note that this only handles primitive types and strings. | |
858 jobject GinJavaMethodInvocationHelper::CreateJavaArray(const JavaType& type, | |
859 jsize length) { | |
860 JNIEnv* env = AttachCurrentThread(); | |
861 switch (type.type) { | |
862 case JavaType::TypeBoolean: | |
863 return env->NewBooleanArray(length); | |
864 case JavaType::TypeByte: | |
865 return env->NewByteArray(length); | |
866 case JavaType::TypeChar: | |
867 return env->NewCharArray(length); | |
868 case JavaType::TypeShort: | |
869 return env->NewShortArray(length); | |
870 case JavaType::TypeInt: | |
871 return env->NewIntArray(length); | |
872 case JavaType::TypeLong: | |
873 return env->NewLongArray(length); | |
874 case JavaType::TypeFloat: | |
875 return env->NewFloatArray(length); | |
876 case JavaType::TypeDouble: | |
877 return env->NewDoubleArray(length); | |
878 case JavaType::TypeString: { | |
879 ScopedJavaLocalRef<jclass> clazz( | |
880 base::android::GetClass(env, kJavaLangString)); | |
881 return env->NewObjectArray(length, clazz.obj(), NULL); | |
882 } | |
883 case JavaType::TypeVoid: | |
884 // Conversion to void must never happen. | |
885 case JavaType::TypeArray: | |
886 case JavaType::TypeObject: | |
887 // Not handled. | |
888 NOTREACHED(); | |
889 } | |
890 return NULL; | |
891 } | |
892 | |
893 // Sets the specified element of the supplied array to the value of the | |
894 // supplied jvalue. Requires that the type of the array matches that of the | |
895 // jvalue. Handles only primitive types and strings. Note that in the case of a | |
896 // string, the array takes a new reference to the string object. | |
897 void GinJavaMethodInvocationHelper::SetArrayElement(jobject array, | |
898 const JavaType& type, | |
bulach
2014/06/09 11:44:33
nit: align
mnaganov (inactive)
2014/06/11 13:50:44
Done.
| |
899 jsize index, | |
900 const jvalue& value) { | |
901 JNIEnv* env = AttachCurrentThread(); | |
902 switch (type.type) { | |
903 case JavaType::TypeBoolean: | |
904 env->SetBooleanArrayRegion(static_cast<jbooleanArray>(array), index, 1, | |
905 &value.z); | |
906 break; | |
907 case JavaType::TypeByte: | |
908 env->SetByteArrayRegion(static_cast<jbyteArray>(array), index, 1, | |
909 &value.b); | |
910 break; | |
911 case JavaType::TypeChar: | |
912 env->SetCharArrayRegion(static_cast<jcharArray>(array), index, 1, | |
913 &value.c); | |
914 break; | |
915 case JavaType::TypeShort: | |
916 env->SetShortArrayRegion(static_cast<jshortArray>(array), index, 1, | |
917 &value.s); | |
918 break; | |
919 case JavaType::TypeInt: | |
920 env->SetIntArrayRegion(static_cast<jintArray>(array), index, 1, | |
921 &value.i); | |
922 break; | |
923 case JavaType::TypeLong: | |
924 env->SetLongArrayRegion(static_cast<jlongArray>(array), index, 1, | |
925 &value.j); | |
926 break; | |
927 case JavaType::TypeFloat: | |
928 env->SetFloatArrayRegion(static_cast<jfloatArray>(array), index, 1, | |
929 &value.f); | |
930 break; | |
931 case JavaType::TypeDouble: | |
932 env->SetDoubleArrayRegion(static_cast<jdoubleArray>(array), index, 1, | |
933 &value.d); | |
934 break; | |
935 case JavaType::TypeString: | |
936 env->SetObjectArrayElement(static_cast<jobjectArray>(array), index, | |
937 value.l); | |
938 break; | |
939 case JavaType::TypeVoid: | |
940 // Conversion to void must never happen. | |
941 case JavaType::TypeArray: | |
942 case JavaType::TypeObject: | |
943 // Not handled. | |
944 NOTREACHED(); | |
945 } | |
946 base::android::CheckException(env); | |
947 } | |
948 | |
949 jvalue | |
950 GinJavaMethodInvocationHelper::CoerceJavaScriptNullOrUndefinedToJavaValue( | |
951 const base::Value* value, | |
952 const JavaType& target_type, | |
953 bool coerce_to_string) { | |
954 bool is_undefined = false; | |
955 scoped_ptr<const GinJavaBridgeValue> gin_value; | |
956 if (GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)) { | |
957 gin_value = GinJavaBridgeValue::FromValue(value); | |
958 if (gin_value->IsType(GinJavaBridgeValue::TYPE_UNDEFINED)) { | |
959 is_undefined = true; | |
960 } | |
961 } | |
962 jvalue result; | |
963 switch (target_type.type) { | |
964 case JavaType::TypeObject: | |
965 result.l = NULL; | |
966 break; | |
967 case JavaType::TypeString: | |
968 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to | |
969 // "undefined". Spec requires converting undefined to NULL. | |
970 result.l = (coerce_to_string && is_undefined) | |
971 ? ConvertUTF8ToJavaString(AttachCurrentThread(), | |
972 kUndefined).Release() | |
973 : NULL; | |
974 break; | |
975 case JavaType::TypeByte: | |
976 case JavaType::TypeChar: | |
977 case JavaType::TypeShort: | |
978 case JavaType::TypeInt: | |
979 case JavaType::TypeLong: | |
980 case JavaType::TypeFloat: | |
981 case JavaType::TypeDouble: { | |
982 jvalue null_value = {0}; | |
983 result = null_value; | |
984 break; | |
985 } | |
986 case JavaType::TypeBoolean: | |
987 result.z = JNI_FALSE; | |
988 break; | |
989 case JavaType::TypeArray: | |
990 // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec | |
991 // requires raising a JavaScript exception. | |
992 result.l = NULL; | |
993 break; | |
994 case JavaType::TypeVoid: | |
995 // Conversion to void must never happen. | |
996 NOTREACHED(); | |
997 break; | |
998 } | |
999 return result; | |
1000 } | |
1001 | |
1002 } // namespace content | |
OLD | NEW |