| 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..5c8abb6a9b3be79803fca6fe23aa295e51935099
|
| --- /dev/null
|
| +++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper.cc
|
| @@ -0,0 +1,310 @@
|
| +// 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 "content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.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::ScopedJavaLocalRef;
|
| +
|
| +namespace content {
|
| +
|
| +namespace {
|
| +
|
| +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";
|
| +
|
| +// See frameworks/base/core/java/android/webkit/EventLogTags.logtags
|
| +const int kObjectGetClassInvocationAttemptLogTag = 70151;
|
| +
|
| +} // 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_empty()) {
|
| + object_refs_.insert(std::make_pair(object_id, object_ref));
|
| + }
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +void GinJavaMethodInvocationHelper::Invoke() {
|
| + JNIEnv* env = AttachCurrentThread();
|
| + 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)) {
|
| + base::android::EventLogWriteInt(kObjectGetClassInvocationAttemptLogTag,
|
| + getuid());
|
| + 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(
|
| + env, argument, method->parameter_type(i), true, object_refs_);
|
| + }
|
| + 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);
|
| + }
|
| +}
|
| +
|
| +} // namespace content
|
|
|