Chromium Code Reviews| Index: content/browser/renderer_host/java/gin_java_method_invocation_helper.cc |
| diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc b/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..5b10753dbe3b242aa8fcedf088b9cb667a96a6a1 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc |
| @@ -0,0 +1,1002 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/renderer_host/java/gin_java_method_invocation_helper.h" |
| + |
| +#include <unistd.h> |
| + |
| +#include "base/android/event_log.h" |
| +#include "base/android/jni_android.h" |
| +#include "base/android/jni_string.h" |
| +#include "base/float_util.h" |
| +#include "base/strings/string_number_conversions.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "content/browser/renderer_host/java/java_method.h" |
| +#include "content/browser/renderer_host/java/jni_helper.h" |
| +#include "content/common/android/gin_java_bridge_value.h" |
| +#include "content/public/browser/browser_thread.h" |
| + |
| +using base::android::AttachCurrentThread; |
| +using base::android::ConvertUTF8ToJavaString; |
| +using base::android::ScopedJavaLocalRef; |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +const char kJavaLangString[] = "java/lang/String"; |
| +const char kObjectIsGone[] = "Java object is gone"; |
| +const char kMethodNotFound[] = "Method not found"; |
| +const char kAccessToObjectGetClassIsBlocked[] = |
| + "Access to java.lang.Object.getClass is blocked"; |
| +const char kJavaExceptionRaised[] = |
| + "Java exception has been raised during method invocation"; |
| +const char kUndefined[] = "undefined"; |
| + |
| +} // namespace |
| + |
| +GinJavaMethodInvocationHelper::GinJavaMethodInvocationHelper( |
| + scoped_ptr<ObjectDelegate> object, |
| + const std::string& method_name, |
| + const base::ListValue& arguments) |
| + : object_(object.Pass()), |
| + method_name_(method_name), |
| + arguments_(arguments.DeepCopy()) { |
| +} |
| + |
| +GinJavaMethodInvocationHelper::~GinJavaMethodInvocationHelper() {} |
| + |
| +void GinJavaMethodInvocationHelper::Init(DispatcherDelegate* dispatcher) { |
| + // Build on the UI thread a map of object_id -> WeakRef for Java objects from |
| + // |arguments_|. Then we can use this map on the background thread without |
| + // accessing |dispatcher|. |
| + BuildObjectRefsFromListValue(dispatcher, arguments_.get()); |
| +} |
| + |
| +// As V8ValueConverter has finite recursion depth when serializing |
| +// JavaScript values, we don't bother about having a recursion threshold here. |
| +void GinJavaMethodInvocationHelper::BuildObjectRefsFromListValue( |
| + DispatcherDelegate* dispatcher, |
| + const base::Value* list_value) { |
| + DCHECK(list_value->IsType(base::Value::TYPE_LIST)); |
| + const base::ListValue* list; |
| + list_value->GetAsList(&list); |
| + for (base::ListValue::const_iterator iter = list->begin(); |
| + iter != list->end(); |
| + ++iter) { |
| + if (AppendObjectRef(dispatcher, *iter)) |
| + continue; |
| + if ((*iter)->IsType(base::Value::TYPE_LIST)) { |
| + BuildObjectRefsFromListValue(dispatcher, *iter); |
| + } else if ((*iter)->IsType(base::Value::TYPE_DICTIONARY)) { |
| + BuildObjectRefsFromDictionaryValue(dispatcher, *iter); |
| + } |
| + } |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::BuildObjectRefsFromDictionaryValue( |
| + DispatcherDelegate* dispatcher, |
| + const base::Value* dict_value) { |
| + DCHECK(dict_value->IsType(base::Value::TYPE_DICTIONARY)); |
| + const base::DictionaryValue* dict; |
| + dict_value->GetAsDictionary(&dict); |
| + for (base::DictionaryValue::Iterator iter(*dict); |
| + !iter.IsAtEnd(); |
| + iter.Advance()) { |
| + if (AppendObjectRef(dispatcher, &iter.value())) |
| + continue; |
| + if (iter.value().IsType(base::Value::TYPE_LIST)) { |
| + BuildObjectRefsFromListValue(dispatcher, &iter.value()); |
| + } else if (iter.value().IsType(base::Value::TYPE_DICTIONARY)) { |
| + BuildObjectRefsFromDictionaryValue(dispatcher, &iter.value()); |
| + } |
| + } |
| +} |
| + |
| +bool GinJavaMethodInvocationHelper::AppendObjectRef( |
| + DispatcherDelegate* dispatcher, |
| + const base::Value* raw_value) { |
| + if (!GinJavaBridgeValue::ContainsGinJavaBridgeValue(raw_value)) |
| + return false; |
| + scoped_ptr<const GinJavaBridgeValue> value( |
| + GinJavaBridgeValue::FromValue(raw_value)); |
| + if (!value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID)) |
| + return false; |
| + GinJavaBoundObject::ObjectID object_id; |
| + if (value->GetAsObjectID(&object_id)) { |
| + ObjectRefs::iterator iter = object_refs_.find(object_id); |
| + if (iter == object_refs_.end()) { |
| + JavaObjectWeakGlobalRef object_ref( |
| + dispatcher->GetObjectWeakRef(object_id)); |
| + if (!object_ref.is_null()) { |
| + object_refs_.insert(std::make_pair(object_id, object_ref)); |
| + } |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::Invoke() { |
| + JNIEnv* env = AttachCurrentThread(); |
| + base::android::ScopedJavaLocalRef<jobject> obj(object_->GetLocalRef(env)); |
| + if (obj.is_null()) { |
| + SetInvocationFailure(kObjectIsGone); |
| + return; |
| + } |
| + const JavaMethod* method = |
| + object_->FindMethod(method_name_, arguments_->GetSize()); |
| + if (!method) { |
| + SetInvocationFailure(kMethodNotFound); |
| + return; |
| + } |
| + |
| + if (object_->IsObjectGetClassMethod(method)) { |
| + // See frameworks/base/core/java/android/webkit/EventLogTags.logtags |
| + 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
|
| + SetInvocationFailure(kAccessToObjectGetClassIsBlocked); |
| + return; |
| + } |
| + |
| + std::vector<jvalue> parameters(method->num_parameters()); |
| + for (size_t i = 0; i < method->num_parameters(); ++i) { |
| + const base::Value* argument; |
| + arguments_->Get(i, &argument); |
| + parameters[i] = CoerceJavaScriptValueToJavaValue(argument, |
| + method->parameter_type(i), |
| + true); |
| + } |
| + InvokeMethod(obj.obj(), |
| + method->return_type(), |
| + method->id(), |
| + ¶meters[0]); |
| + |
| + // Now that we're done with the jvalue, release any local references created |
| + // by CoerceJavaScriptValueToJavaValue(). |
| + for (size_t i = 0; i < method->num_parameters(); ++i) { |
| + ReleaseJavaValueIfRequired(env, ¶meters[i], method->parameter_type(i)); |
| + } |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::SetInvocationFailure( |
| + const char* error_message) { |
| + holds_primitive_result_ = true; |
| + primitive_result_.reset(new base::ListValue()); |
| + error_message_ = error_message; |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::SetPrimitiveResult( |
| + const base::ListValue& result_wrapper) { |
| + holds_primitive_result_ = true; |
| + primitive_result_.reset(result_wrapper.DeepCopy()); |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::SetObjectResult( |
| + const base::android::JavaRef<jobject>& object, |
| + const base::android::JavaRef<jclass>& safe_annotation_clazz) { |
| + holds_primitive_result_ = false; |
| + object_result_.Reset(object); |
| + safe_annotation_clazz_.Reset(safe_annotation_clazz); |
| +} |
| + |
| +bool GinJavaMethodInvocationHelper::HoldsPrimitiveResult() { |
| + return holds_primitive_result_; |
| +} |
| + |
| +const base::ListValue& GinJavaMethodInvocationHelper::GetPrimitiveResult() { |
| + return *primitive_result_.get(); |
| +} |
| + |
| +const base::android::JavaRef<jobject>& |
| +GinJavaMethodInvocationHelper::GetObjectResult() { |
| + return object_result_; |
| +} |
| + |
| +const base::android::JavaRef<jclass>& |
| +GinJavaMethodInvocationHelper::GetSafeAnnotationClass() { |
| + return safe_annotation_clazz_; |
| +} |
| + |
| +const std::string& GinJavaMethodInvocationHelper::GetErrorMessage() { |
| + return error_message_; |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::InvokeMethod(jobject object, |
| + const JavaType& return_type, |
| + jmethodID id, |
| + jvalue* parameters) { |
| + JNIEnv* env = AttachCurrentThread(); |
| + base::ListValue result_wrapper; |
| + switch (return_type.type) { |
| + case JavaType::TypeBoolean: |
| + result_wrapper.AppendBoolean( |
| + env->CallBooleanMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeByte: |
| + result_wrapper.AppendInteger( |
| + env->CallByteMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeChar: |
| + result_wrapper.AppendInteger( |
| + env->CallCharMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeShort: |
| + result_wrapper.AppendInteger( |
| + env->CallShortMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeInt: |
| + result_wrapper.AppendInteger( |
| + env->CallIntMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeLong: |
| + result_wrapper.AppendDouble( |
| + env->CallLongMethodA(object, id, parameters)); |
| + break; |
| + case JavaType::TypeFloat: { |
| + float result = env->CallFloatMethodA(object, id, parameters); |
| + if (base::IsFinite(result)) { |
| + result_wrapper.AppendDouble(result); |
| + } else { |
| + result_wrapper.Append( |
| + GinJavaBridgeValue::CreateNonFiniteValue(result).release()); |
| + } |
| + break; |
| + } |
| + case JavaType::TypeDouble: { |
| + double result = env->CallDoubleMethodA(object, id, parameters); |
| + if (base::IsFinite(result)) { |
| + result_wrapper.AppendDouble(result); |
| + } else { |
| + result_wrapper.Append( |
| + GinJavaBridgeValue::CreateNonFiniteValue(result).release()); |
| + } |
| + break; |
| + } |
| + case JavaType::TypeVoid: |
| + env->CallVoidMethodA(object, id, parameters); |
| + result_wrapper.Append( |
| + GinJavaBridgeValue::CreateUndefinedValue().release()); |
| + break; |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that |
| + // return arrays. Spec requires calling the method and converting the |
| + // result to a JavaScript array. |
| + result_wrapper.Append( |
| + GinJavaBridgeValue::CreateUndefinedValue().release()); |
| + break; |
| + case JavaType::TypeString: { |
| + jstring java_string = static_cast<jstring>( |
| + env->CallObjectMethodA(object, id, parameters)); |
| + // If an exception was raised, we must clear it before calling most JNI |
| + // methods. ScopedJavaLocalRef is liable to make such calls, so we test |
| + // first. |
| + if (base::android::ClearException(env)) { |
| + SetInvocationFailure(kJavaExceptionRaised); |
| + return; |
| + } |
| + ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string); |
| + if (!scoped_java_string.obj()) { |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined. |
| + // Spec requires returning a null string. |
| + result_wrapper.Append( |
| + GinJavaBridgeValue::CreateUndefinedValue().release()); |
| + break; |
| + } |
| + result_wrapper.AppendString( |
| + base::android::ConvertJavaStringToUTF8(scoped_java_string)); |
| + break; |
| + } |
| + case JavaType::TypeObject: { |
| + // If an exception was raised, we must clear it before calling most JNI |
| + // methods. ScopedJavaLocalRef is liable to make such calls, so we test |
| + // first. |
| + jobject java_object = env->CallObjectMethodA(object, id, parameters); |
| + if (base::android::ClearException(env)) { |
| + SetInvocationFailure(kJavaExceptionRaised); |
| + return; |
| + } |
| + ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object); |
| + if (!scoped_java_object.obj()) { |
| + result_wrapper.Append(base::Value::CreateNullValue()); |
| + break; |
| + } |
| + SetObjectResult(scoped_java_object, object_->GetSafeAnnotationClass()); |
| + return; |
| + } |
| + } |
| + // This is for all cases except JavaType::TypeObject. |
| + if (!base::android::ClearException(env)) { |
| + SetPrimitiveResult(result_wrapper); |
| + } else { |
| + SetInvocationFailure(kJavaExceptionRaised); |
| + } |
| +} |
| + |
| +void GinJavaMethodInvocationHelper::ReleaseJavaValueIfRequired(JNIEnv* env, |
| + jvalue* value, |
| + 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.
|
| + if (type.type == JavaType::TypeString || |
| + type.type == JavaType::TypeObject || |
| + type.type == JavaType::TypeArray) { |
| + env->DeleteLocalRef(value->l); |
| + value->l = NULL; |
| + } |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptValueToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + // Note that in all these conversions, the relevant field of the jvalue must |
| + // always be explicitly set, as jvalue does not initialize its fields. |
| + |
| + switch (value->GetType()) { |
| + case base::Value::TYPE_INTEGER: |
| + return CoerceJavaScriptIntegerToJavaValue(value, target_type, |
| + coerce_to_string); |
|
bulach
2014/06/09 11:44:33
nit: align
mnaganov (inactive)
2014/06/11 13:50:44
Done, thanks!
|
| + case base::Value::TYPE_DOUBLE: { |
| + double double_value; |
| + value->GetAsDouble(&double_value); |
| + return CoerceJavaScriptDoubleToJavaValue(double_value, target_type, |
| + coerce_to_string); |
| + } |
| + case base::Value::TYPE_BOOLEAN: |
| + return CoerceJavaScriptBooleanToJavaValue(value, target_type, |
| + coerce_to_string); |
| + case base::Value::TYPE_STRING: |
| + return CoerceJavaScriptStringToJavaValue(value, target_type); |
| + case base::Value::TYPE_DICTIONARY: |
| + case base::Value::TYPE_LIST: |
| + return CoerceJavaScriptObjectToJavaValue(value, target_type, |
| + coerce_to_string); |
| + case base::Value::TYPE_NULL: |
| + return CoerceJavaScriptNullOrUndefinedToJavaValue(value, target_type, |
| + coerce_to_string); |
| + case base::Value::TYPE_BINARY: |
| + return CoerceGinJavaBridgeValueToJavaValue(value, target_type, |
| + coerce_to_string); |
| + } |
| + NOTREACHED(); |
| + return jvalue(); |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceGinJavaBridgeValueToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + DCHECK(GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)); |
| + scoped_ptr<const GinJavaBridgeValue> gin_value( |
| + GinJavaBridgeValue::FromValue(value)); |
| + switch (gin_value->GetType()) { |
| + case GinJavaBridgeValue::TYPE_UNDEFINED: |
| + return CoerceJavaScriptNullOrUndefinedToJavaValue( |
| + value, target_type, coerce_to_string); |
| + case GinJavaBridgeValue::TYPE_NONFINITE: { |
| + float float_value; |
| + gin_value->GetAsNonFinite(&float_value); |
| + return CoerceJavaScriptDoubleToJavaValue( |
| + float_value, target_type, coerce_to_string); |
| + } |
| + case GinJavaBridgeValue::TYPE_OBJECT_ID: |
| + return CoerceJavaScriptObjectToJavaValue( |
| + value, target_type, coerce_to_string); |
| + default: |
| + NOTREACHED(); |
| + } |
| + return jvalue(); |
| +} |
| + |
| +namespace { |
| + |
| +double RoundDoubleTowardsZero(const double& x) { |
| + if (std::isnan(x)) { |
| + return 0.0; |
| + } |
| + return x > 0.0 ? floor(x) : ceil(x); |
| +} |
| + |
| +// Rounds to jlong using Java's type conversion rules. |
| +jlong RoundDoubleToLong(const double& x) { |
| + double intermediate = RoundDoubleTowardsZero(x); |
| + // The int64 limits can not be converted exactly to double values, so we |
| + // compare to custom constants. kint64max is 2^63 - 1, but the spacing |
| + // between double values in the the range 2^62 to 2^63 is 2^10. The cast is |
| + // required to silence a spurious gcc warning for integer overflow. |
| + 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.
|
| + DCHECK(limit > 0); |
| + const double kLargestDoubleLessThanInt64Max = limit; |
| + const double kSmallestDoubleGreaterThanInt64Min = -limit; |
| + if (intermediate > kLargestDoubleLessThanInt64Max) { |
| + return kint64max; |
| + } |
| + if (intermediate < kSmallestDoubleGreaterThanInt64Min) { |
| + return kint64min; |
| + } |
| + return static_cast<jlong>(intermediate); |
| +} |
| + |
| +// Rounds to jint using Java's type conversion rules. |
| +jint RoundDoubleToInt(const double& x) { |
| + double intermediate = RoundDoubleTowardsZero(x); |
| + // The int32 limits cast exactly to double values. |
| + intermediate = std::min(intermediate, static_cast<double>(kint32max)); |
| + intermediate = std::max(intermediate, static_cast<double>(kint32min)); |
| + return static_cast<jint>(intermediate); |
| +} |
| + |
| +} // namespace |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptIntegerToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES. |
| + |
| + // For conversion to numeric types, we need to replicate Java's type |
| + // conversion rules. This requires that for integer values, we simply discard |
| + // all but the lowest n buts, where n is the number of bits in the target |
| + // type. |
| + jvalue result; |
| + int int_value; |
| + value->GetAsInteger(&int_value); |
| + switch (target_type.type) { |
| + case JavaType::TypeByte: |
| + result.b = static_cast<jbyte>(int_value); |
| + break; |
| + case JavaType::TypeChar: |
| + result.c = static_cast<jchar>(int_value); |
| + break; |
| + case JavaType::TypeShort: |
| + result.s = static_cast<jshort>(int_value); |
| + break; |
| + case JavaType::TypeInt: |
| + result.i = int_value; |
| + break; |
| + case JavaType::TypeLong: |
| + result.j = int_value; |
| + break; |
| + case JavaType::TypeFloat: |
| + result.f = int_value; |
| + break; |
| + case JavaType::TypeDouble: |
| + result.d = int_value; |
| + break; |
| + case JavaType::TypeObject: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec |
| + // requires handling object equivalents of primitive types. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeString: |
| + result.l = coerce_to_string |
| + ? ConvertUTF8ToJavaString(AttachCurrentThread(), |
| + base::Int64ToString(int_value)) |
| + .Release() |
| + : NULL; |
| + break; |
| + case JavaType::TypeBoolean: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec |
| + // requires converting to false for 0 or NaN, true otherwise. |
| + result.z = JNI_FALSE; |
| + break; |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec |
| + // requires raising a JavaScript exception. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptDoubleToJavaValue( |
| + double double_value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES. |
| + // For conversion to numeric types, we need to replicate Java's type |
| + // conversion rules. |
| + jvalue result; |
| + switch (target_type.type) { |
| + case JavaType::TypeByte: |
| + result.b = static_cast<jbyte>(RoundDoubleToInt(double_value)); |
| + break; |
| + case JavaType::TypeChar: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0. |
| + // Spec requires converting doubles similarly to how we convert doubles to |
| + // other numeric types. |
| + result.c = 0; |
| + break; |
| + case JavaType::TypeShort: |
| + result.s = static_cast<jshort>(RoundDoubleToInt(double_value)); |
| + break; |
| + case JavaType::TypeInt: |
| + result.i = RoundDoubleToInt(double_value); |
| + break; |
| + case JavaType::TypeLong: |
| + result.j = RoundDoubleToLong(double_value); |
| + break; |
| + case JavaType::TypeFloat: |
| + result.f = static_cast<jfloat>(double_value); |
| + break; |
| + case JavaType::TypeDouble: |
| + result.d = double_value; |
| + break; |
| + case JavaType::TypeObject: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec |
| + // requires handling object equivalents of primitive types. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeString: |
| + result.l = coerce_to_string |
| + ? ConvertUTF8ToJavaString( |
| + AttachCurrentThread(), |
| + base::StringPrintf("%.6lg", double_value)).Release() |
| + : NULL; |
| + break; |
| + case JavaType::TypeBoolean: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec |
| + // requires converting to false for 0 or NaN, true otherwise. |
| + result.z = JNI_FALSE; |
| + break; |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec |
| + // requires raising a JavaScript exception. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptBooleanToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES. |
| + bool boolean_value; |
| + value->GetAsBoolean(&boolean_value); |
| + jvalue result; |
| + switch (target_type.type) { |
| + case JavaType::TypeBoolean: |
| + result.z = boolean_value ? JNI_TRUE : JNI_FALSE; |
| + break; |
| + case JavaType::TypeObject: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec |
| + // requires handling java.lang.Boolean and java.lang.Object. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeString: |
| + result.l = coerce_to_string ? |
| + ConvertUTF8ToJavaString(AttachCurrentThread(), |
| + boolean_value ? "true" : "false").Release() : |
| + NULL; |
| + break; |
| + case JavaType::TypeByte: |
| + case JavaType::TypeChar: |
| + case JavaType::TypeShort: |
| + case JavaType::TypeInt: |
| + case JavaType::TypeLong: |
| + case JavaType::TypeFloat: |
| + case JavaType::TypeDouble: { |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec |
| + // requires converting to 0 or 1. |
| + jvalue null_value = {0}; |
| + result = null_value; |
| + break; |
| + } |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec |
| + // requires raising a JavaScript exception. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptStringToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type) { |
| + // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES. |
| + jvalue result; |
| + switch (target_type.type) { |
| + case JavaType::TypeString: { |
| + std::string string_result; |
| + value->GetAsString(&string_result); |
| + result.l = ConvertUTF8ToJavaString(AttachCurrentThread(), string_result) |
| + .Release(); |
| + break; |
| + } |
| + case JavaType::TypeObject: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec |
| + // requires handling java.lang.Object. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeByte: |
| + case JavaType::TypeShort: |
| + case JavaType::TypeInt: |
| + case JavaType::TypeLong: |
| + case JavaType::TypeFloat: |
| + case JavaType::TypeDouble: { |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec |
| + // requires using valueOf() method of corresponding object type. |
| + jvalue null_value = {0}; |
| + result = null_value; |
| + break; |
| + } |
| + case JavaType::TypeChar: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec |
| + // requires using java.lang.Short.decode(). |
| + result.c = 0; |
| + break; |
| + case JavaType::TypeBoolean: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec |
| + // requires converting the empty string to false, otherwise true. |
| + result.z = JNI_FALSE; |
| + break; |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec |
| + // requires raising a JavaScript exception. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +jvalue GinJavaMethodInvocationHelper::CoerceJavaScriptObjectToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + // This covers both JavaScript objects (including arrays) and Java objects. |
| + // See http://jdk6.java.net/plugin2/liveconnect/#JS_OTHER_OBJECTS, |
| + // http://jdk6.java.net/plugin2/liveconnect/#JS_ARRAY_VALUES and |
| + // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS |
| + jvalue result; |
| + switch (target_type.type) { |
| + case JavaType::TypeObject: { |
| + if (GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)) { |
| + scoped_ptr<const GinJavaBridgeValue> gin_value( |
| + GinJavaBridgeValue::FromValue(value)); |
| + DCHECK(gin_value); |
| + DCHECK(gin_value->IsType(GinJavaBridgeValue::TYPE_OBJECT_ID)); |
| + base::android::ScopedJavaLocalRef<jobject> obj; |
| + GinJavaBoundObject::ObjectID object_id; |
| + if (gin_value->GetAsObjectID(&object_id)) { |
| + ObjectRefs::iterator iter = object_refs_.find(object_id); |
| + if (iter != object_refs_.end()) { |
| + obj.Reset(iter->second.get(AttachCurrentThread())); |
| + } |
| + } |
| + result.l = obj.Release(); |
| + } else { |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec |
| + // requires converting if the target type is |
| + // netscape.javascript.JSObject, otherwise raising a JavaScript |
| + // exception. |
| + result.l = NULL; |
| + } |
| + break; |
| + } |
| + case JavaType::TypeString: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to |
| + // "undefined". Spec requires calling toString() on the Java object. |
| + result.l = coerce_to_string |
| + ? ConvertUTF8ToJavaString(AttachCurrentThread(), |
| + kUndefined).Release() |
| + : NULL; |
| + break; |
| + case JavaType::TypeByte: |
| + case JavaType::TypeShort: |
| + case JavaType::TypeInt: |
| + case JavaType::TypeLong: |
| + case JavaType::TypeFloat: |
| + case JavaType::TypeDouble: |
| + case JavaType::TypeChar: { |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec |
| + // requires raising a JavaScript exception. |
| + jvalue null_value = {0}; |
| + result = null_value; |
| + break; |
| + } |
| + case JavaType::TypeBoolean: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec |
| + // requires raising a JavaScript exception. |
| + result.z = JNI_FALSE; |
| + break; |
| + case JavaType::TypeArray: |
| + if (value->IsType(base::Value::TYPE_DICTIONARY)) { |
| + result.l = CoerceJavaScriptDictionaryToArray(value, target_type); |
| + } else if (value->IsType(base::Value::TYPE_LIST)) { |
| + result.l = CoerceJavaScriptListToArray(value, target_type); |
| + } else { |
| + result.l = NULL; |
| + } |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +jobject GinJavaMethodInvocationHelper::CoerceJavaScriptListToArray( |
| + const base::Value* value, |
| + const JavaType& target_type) { |
| + DCHECK_EQ(JavaType::TypeArray, target_type.type); |
| + const JavaType& target_inner_type = *target_type.inner_type.get(); |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for |
| + // multi-dimensional arrays. Spec requires handling multi-demensional arrays. |
| + if (target_inner_type.type == JavaType::TypeArray) { |
| + return NULL; |
| + } |
| + |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object |
| + // arrays. Spec requires handling object arrays. |
| + if (target_inner_type.type == JavaType::TypeObject) { |
| + return NULL; |
| + } |
| + |
| + const base::ListValue* list_value; |
| + value->GetAsList(&list_value); |
| + // Create the Java array. |
| + jsize length = static_cast<jsize>(list_value->GetSize()); |
| + jobject result = CreateJavaArray(target_inner_type, length); |
| + if (!result) { |
| + return NULL; |
| + } |
| + JNIEnv* env = AttachCurrentThread(); |
| + scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); |
| + for (jsize i = 0; i < length; ++i) { |
| + const base::Value* value_element = null_value.get(); |
| + list_value->Get(i, &value_element); |
| + jvalue element = CoerceJavaScriptValueToJavaValue(value_element, |
| + target_inner_type, |
| + false); |
| + SetArrayElement(result, target_inner_type, i, element); |
| + // CoerceJavaScriptValueToJavaValue() creates new local references to |
| + // strings, objects and arrays. Of these, only strings can occur here. |
| + // SetArrayElement() causes the array to take its own reference to the |
| + // string, so we can now release the local reference. |
| + DCHECK_NE(JavaType::TypeObject, target_inner_type.type); |
| + DCHECK_NE(JavaType::TypeArray, target_inner_type.type); |
| + ReleaseJavaValueIfRequired(env, &element, target_inner_type); |
| + } |
| + |
| + return result; |
| +} |
| + |
| +jobject GinJavaMethodInvocationHelper::CoerceJavaScriptDictionaryToArray( |
| + const base::Value* value, |
| + const JavaType& target_type) { |
| + DCHECK_EQ(JavaType::TypeArray, target_type.type); |
| + |
| + const JavaType& target_inner_type = *target_type.inner_type.get(); |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for |
| + // multi-dimensional arrays. Spec requires handling multi-demensional arrays. |
| + if (target_inner_type.type == JavaType::TypeArray) { |
| + return NULL; |
| + } |
| + |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object |
| + // arrays. Spec requires handling object arrays. |
| + if (target_inner_type.type == JavaType::TypeObject) { |
| + return NULL; |
| + } |
| + |
| + const base::DictionaryValue* dictionary_value; |
| + value->GetAsDictionary(&dictionary_value); |
| + const base::Value* length_value; |
| + // If the object does not have a length property, return null. |
| + if (!dictionary_value->Get("length", &length_value)) { |
| + return NULL; |
| + } |
| + |
| + // If the length property does not have numeric type, or is outside the valid |
| + // range for a Java array length, return null. |
| + jsize length = -1; |
| + if (length_value->IsType(base::Value::TYPE_INTEGER)) { |
| + int int_length; |
| + length_value->GetAsInteger(&int_length); |
| + if (int_length >= 0 && int_length <= kint32max) { |
| + length = static_cast<jsize>(int_length); |
| + } |
| + } else if (length_value->IsType(base::Value::TYPE_DOUBLE)) { |
| + double double_length; |
| + length_value->GetAsDouble(&double_length); |
| + if (double_length >= 0.0 && double_length <= kint32max) { |
| + length = static_cast<jsize>(double_length); |
| + } |
| + } |
| + if (length == -1) { |
| + return NULL; |
| + } |
| + |
| + jobject result = CreateJavaArray(target_inner_type, length); |
| + if (!result) { |
| + return NULL; |
| + } |
| + scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); |
| + JNIEnv* env = AttachCurrentThread(); |
| + for (jsize i = 0; i < length; ++i) { |
| + const std::string key(base::IntToString(i)); |
| + const base::Value* value_element = null_value.get(); |
| + if (dictionary_value->HasKey(key)) { |
| + dictionary_value->Get(key, &value_element); |
| + } |
| + jvalue element = CoerceJavaScriptValueToJavaValue(value_element, |
| + target_inner_type, |
| + false); |
| + SetArrayElement(result, target_inner_type, i, element); |
| + // CoerceJavaScriptValueToJavaValue() creates new local references to |
| + // strings, objects and arrays. Of these, only strings can occur here. |
| + // SetArrayElement() causes the array to take its own reference to the |
| + // string, so we can now release the local reference. |
| + DCHECK_NE(JavaType::TypeObject, target_inner_type.type); |
| + DCHECK_NE(JavaType::TypeArray, target_inner_type.type); |
| + ReleaseJavaValueIfRequired(env, &element, target_inner_type); |
| + } |
| + |
| + return result; |
| +} |
| + |
| +// Note that this only handles primitive types and strings. |
| +jobject GinJavaMethodInvocationHelper::CreateJavaArray(const JavaType& type, |
| + jsize length) { |
| + JNIEnv* env = AttachCurrentThread(); |
| + switch (type.type) { |
| + case JavaType::TypeBoolean: |
| + return env->NewBooleanArray(length); |
| + case JavaType::TypeByte: |
| + return env->NewByteArray(length); |
| + case JavaType::TypeChar: |
| + return env->NewCharArray(length); |
| + case JavaType::TypeShort: |
| + return env->NewShortArray(length); |
| + case JavaType::TypeInt: |
| + return env->NewIntArray(length); |
| + case JavaType::TypeLong: |
| + return env->NewLongArray(length); |
| + case JavaType::TypeFloat: |
| + return env->NewFloatArray(length); |
| + case JavaType::TypeDouble: |
| + return env->NewDoubleArray(length); |
| + case JavaType::TypeString: { |
| + ScopedJavaLocalRef<jclass> clazz( |
| + base::android::GetClass(env, kJavaLangString)); |
| + return env->NewObjectArray(length, clazz.obj(), NULL); |
| + } |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + case JavaType::TypeArray: |
| + case JavaType::TypeObject: |
| + // Not handled. |
| + NOTREACHED(); |
| + } |
| + return NULL; |
| +} |
| + |
| +// Sets the specified element of the supplied array to the value of the |
| +// supplied jvalue. Requires that the type of the array matches that of the |
| +// jvalue. Handles only primitive types and strings. Note that in the case of a |
| +// string, the array takes a new reference to the string object. |
| +void GinJavaMethodInvocationHelper::SetArrayElement(jobject array, |
| + const JavaType& type, |
|
bulach
2014/06/09 11:44:33
nit: align
mnaganov (inactive)
2014/06/11 13:50:44
Done.
|
| + jsize index, |
| + const jvalue& value) { |
| + JNIEnv* env = AttachCurrentThread(); |
| + switch (type.type) { |
| + case JavaType::TypeBoolean: |
| + env->SetBooleanArrayRegion(static_cast<jbooleanArray>(array), index, 1, |
| + &value.z); |
| + break; |
| + case JavaType::TypeByte: |
| + env->SetByteArrayRegion(static_cast<jbyteArray>(array), index, 1, |
| + &value.b); |
| + break; |
| + case JavaType::TypeChar: |
| + env->SetCharArrayRegion(static_cast<jcharArray>(array), index, 1, |
| + &value.c); |
| + break; |
| + case JavaType::TypeShort: |
| + env->SetShortArrayRegion(static_cast<jshortArray>(array), index, 1, |
| + &value.s); |
| + break; |
| + case JavaType::TypeInt: |
| + env->SetIntArrayRegion(static_cast<jintArray>(array), index, 1, |
| + &value.i); |
| + break; |
| + case JavaType::TypeLong: |
| + env->SetLongArrayRegion(static_cast<jlongArray>(array), index, 1, |
| + &value.j); |
| + break; |
| + case JavaType::TypeFloat: |
| + env->SetFloatArrayRegion(static_cast<jfloatArray>(array), index, 1, |
| + &value.f); |
| + break; |
| + case JavaType::TypeDouble: |
| + env->SetDoubleArrayRegion(static_cast<jdoubleArray>(array), index, 1, |
| + &value.d); |
| + break; |
| + case JavaType::TypeString: |
| + env->SetObjectArrayElement(static_cast<jobjectArray>(array), index, |
| + value.l); |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + case JavaType::TypeArray: |
| + case JavaType::TypeObject: |
| + // Not handled. |
| + NOTREACHED(); |
| + } |
| + base::android::CheckException(env); |
| +} |
| + |
| +jvalue |
| +GinJavaMethodInvocationHelper::CoerceJavaScriptNullOrUndefinedToJavaValue( |
| + const base::Value* value, |
| + const JavaType& target_type, |
| + bool coerce_to_string) { |
| + bool is_undefined = false; |
| + scoped_ptr<const GinJavaBridgeValue> gin_value; |
| + if (GinJavaBridgeValue::ContainsGinJavaBridgeValue(value)) { |
| + gin_value = GinJavaBridgeValue::FromValue(value); |
| + if (gin_value->IsType(GinJavaBridgeValue::TYPE_UNDEFINED)) { |
| + is_undefined = true; |
| + } |
| + } |
| + jvalue result; |
| + switch (target_type.type) { |
| + case JavaType::TypeObject: |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeString: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to |
| + // "undefined". Spec requires converting undefined to NULL. |
| + result.l = (coerce_to_string && is_undefined) |
| + ? ConvertUTF8ToJavaString(AttachCurrentThread(), |
| + kUndefined).Release() |
| + : NULL; |
| + break; |
| + case JavaType::TypeByte: |
| + case JavaType::TypeChar: |
| + case JavaType::TypeShort: |
| + case JavaType::TypeInt: |
| + case JavaType::TypeLong: |
| + case JavaType::TypeFloat: |
| + case JavaType::TypeDouble: { |
| + jvalue null_value = {0}; |
| + result = null_value; |
| + break; |
| + } |
| + case JavaType::TypeBoolean: |
| + result.z = JNI_FALSE; |
| + break; |
| + case JavaType::TypeArray: |
| + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec |
| + // requires raising a JavaScript exception. |
| + result.l = NULL; |
| + break; |
| + case JavaType::TypeVoid: |
| + // Conversion to void must never happen. |
| + NOTREACHED(); |
| + break; |
| + } |
| + return result; |
| +} |
| + |
| +} // namespace content |