summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-12 08:36:08 +0000
committermnaganov@chromium.org <mnaganov@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-12 08:36:08 +0000
commit869365302661dbf642e6be8e8265862496dbae5c (patch)
tree8d865e7ffce75687c89d4add9a06b435cff05ab4
parent62115ba4d4bbeadcd491cd3cfdacd292d4bf34f8 (diff)
downloadchromium_src-869365302661dbf642e6be8e8265862496dbae5c.zip
chromium_src-869365302661dbf642e6be8e8265862496dbae5c.tar.gz
chromium_src-869365302661dbf642e6be8e8265862496dbae5c.tar.bz2
[Android] Java Bridge with Gin: implement Java methods invocation
This patch adds GinJavaMethodInvocationHelper class which serves for coercion of arguments and return values of Java methods to / from base::Value and GinJavaBridgeValue. The coercion code is taken from the existing implementation (JavaBoundObject) with required conversions to use base::Value and GinJavaBridgeValue instead of NPVARIANT and friends. There are extensive Java tests for coercion, so here we only add some unit tests for edge cases. This patch also adds a trivial EventLog class for writing into Android Event Log from C++ code. BUG=355644 R=bulach@chromium.org, torne@chromium.org TBR=brettw@chromium.org Review URL: https://codereview.chromium.org/302173006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276586 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/BUILD.gn3
-rw-r--r--base/android/base_jni_registrar.cc2
-rw-r--r--base/android/event_log.cc20
-rw-r--r--base/android/event_log.h22
-rw-r--r--base/android/java/src/org/chromium/base/EventLog.java17
-rw-r--r--base/android/jni_weak_ref.h2
-rw-r--r--base/base.gyp1
-rw-r--r--base/base.gypi2
-rw-r--r--content/browser/renderer_host/java/gin_java_bound_object.h27
-rw-r--r--content/browser/renderer_host/java/gin_java_method_invocation_helper.cc310
-rw-r--r--content/browser/renderer_host/java/gin_java_method_invocation_helper.h113
-rw-r--r--content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc280
-rw-r--r--content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc705
-rw-r--r--content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.h33
-rw-r--r--content/content_browser.gypi5
-rw-r--r--content/content_tests.gypi1
16 files changed, 1543 insertions, 0 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 7b11288..53084d5 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -28,6 +28,8 @@ component("base") {
"android/content_uri_utils.cc",
"android/content_uri_utils.h",
"android/cpu_features.cc",
+ "android/event_log.cc",
+ "android/event_log.h",
"android/fifo_utils.cc",
"android/fifo_utils.h",
"android/important_file_writer_android.cc",
@@ -1317,6 +1319,7 @@ if (is_android) {
"android/java/src/org/chromium/base/CommandLine.java",
"android/java/src/org/chromium/base/ContentUriUtils.java",
"android/java/src/org/chromium/base/CpuFeatures.java",
+ "android/java/src/org/chromium/base/EventLog.java",
"android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
"android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
"android/java/src/org/chromium/base/MemoryPressureListener.java",
diff --git a/base/android/base_jni_registrar.cc b/base/android/base_jni_registrar.cc
index e14fd44..97c8fcf 100644
--- a/base/android/base_jni_registrar.cc
+++ b/base/android/base_jni_registrar.cc
@@ -9,6 +9,7 @@
#include "base/android/command_line_android.h"
#include "base/android/content_uri_utils.h"
#include "base/android/cpu_features.h"
+#include "base/android/event_log.h"
#include "base/android/important_file_writer_android.h"
#include "base/android/java_handler_thread.h"
#include "base/android/jni_android.h"
@@ -34,6 +35,7 @@ static RegistrationMethod kBaseRegisteredMethods[] = {
{ "CommandLine", base::android::RegisterCommandLine },
{ "ContentUriUtils", base::RegisterContentUriUtils },
{ "CpuFeatures", base::android::RegisterCpuFeatures },
+ { "EventLog", base::android::RegisterEventLog },
{ "ImportantFileWriterAndroid",
base::android::RegisterImportantFileWriterAndroid },
{ "MemoryPressureListenerAndroid",
diff --git a/base/android/event_log.cc b/base/android/event_log.cc
new file mode 100644
index 0000000..a4b1dd1
--- /dev/null
+++ b/base/android/event_log.cc
@@ -0,0 +1,20 @@
+// Copyright 2013 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 "base/android/event_log.h"
+#include "jni/EventLog_jni.h"
+
+namespace base {
+namespace android {
+
+void EventLogWriteInt(int tag, int value) {
+ Java_EventLog_writeEvent(AttachCurrentThread(), tag, value);
+}
+
+bool RegisterEventLog(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace android
+} // namespace base
diff --git a/base/android/event_log.h b/base/android/event_log.h
new file mode 100644
index 0000000..dad4e4c
--- /dev/null
+++ b/base/android/event_log.h
@@ -0,0 +1,22 @@
+// 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.
+
+#ifndef BASE_ANDROID_EVENT_LOG_H_
+#define BASE_ANDROID_EVENT_LOG_H_
+
+#include <jni.h>
+
+#include "base/base_export.h"
+
+namespace base {
+namespace android {
+
+void BASE_EXPORT EventLogWriteInt(int tag, int value);
+
+bool RegisterEventLog(JNIEnv* env);
+
+} // namespace android
+} // namespace base
+
+#endif // BASE_ANDROID_EVENT_LOG_H_
diff --git a/base/android/java/src/org/chromium/base/EventLog.java b/base/android/java/src/org/chromium/base/EventLog.java
new file mode 100644
index 0000000..894de15
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/EventLog.java
@@ -0,0 +1,17 @@
+// 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.
+
+package org.chromium.base;
+
+/**
+ * A simple interface to Android's EventLog to be used by native code.
+ */
+@JNINamespace("base::android")
+public class EventLog {
+
+ @CalledByNative
+ public static void writeEvent(int tag, int value) {
+ android.util.EventLog.writeEvent(tag, value);
+ }
+}
diff --git a/base/android/jni_weak_ref.h b/base/android/jni_weak_ref.h
index ef39b8a..c851046 100644
--- a/base/android/jni_weak_ref.h
+++ b/base/android/jni_weak_ref.h
@@ -25,6 +25,8 @@ class BASE_EXPORT JavaObjectWeakGlobalRef {
base::android::ScopedJavaLocalRef<jobject> get(JNIEnv* env) const;
+ bool is_empty() const { return obj_ == NULL; }
+
void reset();
private:
diff --git a/base/base.gyp b/base/base.gyp
index cf00752..4353509 100644
--- a/base/base.gyp
+++ b/base/base.gyp
@@ -1282,6 +1282,7 @@
'android/java/src/org/chromium/base/CommandLine.java',
'android/java/src/org/chromium/base/ContentUriUtils.java',
'android/java/src/org/chromium/base/CpuFeatures.java',
+ 'android/java/src/org/chromium/base/EventLog.java',
'android/java/src/org/chromium/base/ImportantFileWriterAndroid.java',
'android/java/src/org/chromium/base/library_loader/LibraryLoader.java',
'android/java/src/org/chromium/base/MemoryPressureListener.java',
diff --git a/base/base.gypi b/base/base.gypi
index 87bab04..274dc3d 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -37,6 +37,8 @@
'android/content_uri_utils.cc',
'android/content_uri_utils.h',
'android/cpu_features.cc',
+ 'android/event_log.cc',
+ 'android/event_log.h',
'android/fifo_utils.cc',
'android/fifo_utils.h',
'android/important_file_writer_android.cc',
diff --git a/content/browser/renderer_host/java/gin_java_bound_object.h b/content/browser/renderer_host/java/gin_java_bound_object.h
new file mode 100644
index 0000000..522a48a
--- /dev/null
+++ b/content/browser/renderer_host/java/gin_java_bound_object.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
+
+#include "base/id_map.h"
+#include "base/memory/ref_counted.h"
+
+namespace content {
+
+class GinJavaBoundObject
+ : public base::RefCountedThreadSafe<GinJavaBoundObject> {
+ public:
+ typedef IDMap<scoped_refptr<GinJavaBoundObject>, IDMapOwnPointer> ObjectMap;
+ typedef ObjectMap::KeyType ObjectID;
+
+ private:
+ friend class base::RefCountedThreadSafe<GinJavaBoundObject>;
+ GinJavaBoundObject() {}
+ ~GinJavaBoundObject() {}
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_BOUND_OBJECT_H_
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 0000000..5c8abb6
--- /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(), &parameters[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, &parameters[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
diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper.h b/content/browser/renderer_host/java/gin_java_method_invocation_helper.h
new file mode 100644
index 0000000..5498fda
--- /dev/null
+++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper.h
@@ -0,0 +1,113 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
+
+#include <map>
+
+#include "base/android/jni_weak_ref.h"
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/java/gin_java_bound_object.h"
+#include "content/browser/renderer_host/java/java_type.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class JavaMethod;
+
+// Instances of this class are created and initialized on the UI thread, do
+// their work on the background thread, and then again examined on the UI
+// thread. They don't work on both threads simultaneously, though.
+class CONTENT_EXPORT GinJavaMethodInvocationHelper
+ : public base::RefCountedThreadSafe<GinJavaMethodInvocationHelper> {
+ public:
+ // DispatcherDelegate is used on the UI thread
+ class DispatcherDelegate {
+ public:
+ DispatcherDelegate() {}
+ virtual ~DispatcherDelegate() {}
+ virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
+ GinJavaBoundObject::ObjectID object_id) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DispatcherDelegate);
+ };
+
+ // ObjectDelegate is used in the background thread
+ class ObjectDelegate {
+ public:
+ ObjectDelegate() {}
+ virtual ~ObjectDelegate() {}
+ virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
+ JNIEnv* env) = 0;
+ virtual const JavaMethod* FindMethod(const std::string& method_name,
+ size_t num_parameters) = 0;
+ virtual bool IsObjectGetClassMethod(const JavaMethod* method) = 0;
+ virtual const base::android::JavaRef<jclass>& GetSafeAnnotationClass() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ObjectDelegate);
+ };
+
+ GinJavaMethodInvocationHelper(scoped_ptr<ObjectDelegate> object,
+ const std::string& method_name,
+ const base::ListValue& arguments);
+ void Init(DispatcherDelegate* dispatcher);
+
+ // Called on the background thread
+ void Invoke();
+
+ // Called on the UI thread
+ bool HoldsPrimitiveResult();
+ const base::ListValue& GetPrimitiveResult();
+ const base::android::JavaRef<jobject>& GetObjectResult();
+ const base::android::JavaRef<jclass>& GetSafeAnnotationClass();
+ const std::string& GetErrorMessage();
+
+ private:
+ friend class base::RefCountedThreadSafe<GinJavaMethodInvocationHelper>;
+ ~GinJavaMethodInvocationHelper();
+
+ // Called on the UI thread
+ void BuildObjectRefsFromListValue(DispatcherDelegate* dispatcher,
+ const base::Value* list_value);
+ void BuildObjectRefsFromDictionaryValue(DispatcherDelegate* dispatcher,
+ const base::Value* dict_value);
+
+ bool AppendObjectRef(DispatcherDelegate* dispatcher,
+ const base::Value* raw_value);
+
+ // Called on the background thread.
+ void InvokeMethod(jobject object,
+ const JavaType& return_type,
+ jmethodID id,
+ jvalue* parameters);
+ void SetInvocationFailure(const char* error_message);
+ void SetPrimitiveResult(const base::ListValue& result_wrapper);
+ void SetObjectResult(
+ const base::android::JavaRef<jobject>& object,
+ const base::android::JavaRef<jclass>& safe_annotation_clazz);
+
+ typedef std::map<GinJavaBoundObject::ObjectID,
+ JavaObjectWeakGlobalRef> ObjectRefs;
+
+ scoped_ptr<ObjectDelegate> object_;
+ const std::string method_name_;
+ scoped_ptr<base::ListValue> arguments_;
+ ObjectRefs object_refs_;
+ bool holds_primitive_result_;
+ scoped_ptr<base::ListValue> primitive_result_;
+ std::string error_message_;
+ base::android::ScopedJavaGlobalRef<jobject> object_result_;
+ base::android::ScopedJavaGlobalRef<jclass> safe_annotation_clazz_;
+
+ DISALLOW_COPY_AND_ASSIGN(GinJavaMethodInvocationHelper);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_METHOD_INVOCATION_HELPER_H_
diff --git a/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc b/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc
new file mode 100644
index 0000000..82267d8
--- /dev/null
+++ b/content/browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc
@@ -0,0 +1,280 @@
+// 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 "base/android/jni_android.h"
+#include "content/common/android/gin_java_bridge_value.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+
+namespace {
+
+class NullObjectDelegate
+ : public GinJavaMethodInvocationHelper::ObjectDelegate {
+ public:
+ NullObjectDelegate() {}
+
+ virtual ~NullObjectDelegate() {}
+
+ virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
+ JNIEnv* env) OVERRIDE {
+ return base::android::ScopedJavaLocalRef<jobject>();
+ }
+
+ virtual const JavaMethod* FindMethod(const std::string& method_name,
+ size_t num_parameters) OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool IsObjectGetClassMethod(const JavaMethod* method) OVERRIDE {
+ return false;
+ }
+
+ virtual const base::android::JavaRef<jclass>& GetSafeAnnotationClass()
+ OVERRIDE {
+ return safe_annotation_class_;
+ }
+
+ private:
+ base::android::ScopedJavaLocalRef<jclass> safe_annotation_class_;
+
+ DISALLOW_COPY_AND_ASSIGN(NullObjectDelegate);
+};
+
+class NullDispatcherDelegate
+ : public GinJavaMethodInvocationHelper::DispatcherDelegate {
+ public:
+ NullDispatcherDelegate() {}
+
+ virtual ~NullDispatcherDelegate() {}
+
+ virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
+ GinJavaBoundObject::ObjectID object_id) OVERRIDE {
+ return JavaObjectWeakGlobalRef();
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NullDispatcherDelegate);
+};
+
+} // namespace
+
+class GinJavaMethodInvocationHelperTest : public testing::Test {
+};
+
+namespace {
+
+class CountingDispatcherDelegate
+ : public GinJavaMethodInvocationHelper::DispatcherDelegate {
+ public:
+ CountingDispatcherDelegate() {}
+
+ virtual ~CountingDispatcherDelegate() {}
+
+ virtual JavaObjectWeakGlobalRef GetObjectWeakRef(
+ GinJavaBoundObject::ObjectID object_id) OVERRIDE {
+ counters_[object_id]++;
+ return JavaObjectWeakGlobalRef();
+ }
+
+ void AssertInvocationsCount(GinJavaBoundObject::ObjectID begin_object_id,
+ GinJavaBoundObject::ObjectID end_object_id) {
+ EXPECT_EQ(end_object_id - begin_object_id,
+ static_cast<int>(counters_.size()));
+ for (GinJavaBoundObject::ObjectID i = begin_object_id;
+ i < end_object_id; ++i) {
+ EXPECT_LT(0, counters_[i]) << "ObjectID: " << i;
+ }
+ }
+
+ private:
+ typedef std::map<GinJavaBoundObject::ObjectID, int> Counters;
+ Counters counters_;
+
+ DISALLOW_COPY_AND_ASSIGN(CountingDispatcherDelegate);
+};
+
+} // namespace
+
+TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsNoObjects) {
+ base::ListValue no_objects;
+ for (int i = 0; i < 10; ++i) {
+ no_objects.AppendInteger(i);
+ }
+
+ scoped_refptr<GinJavaMethodInvocationHelper> helper =
+ new GinJavaMethodInvocationHelper(
+ scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
+ new NullObjectDelegate()),
+ "foo",
+ no_objects);
+ CountingDispatcherDelegate counter;
+ helper->Init(&counter);
+ counter.AssertInvocationsCount(0, 0);
+}
+
+TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsHaveObjects) {
+ base::ListValue objects;
+ objects.AppendInteger(100);
+ objects.Append(GinJavaBridgeValue::CreateObjectIDValue(1).release());
+ base::ListValue* sub_list = new base::ListValue();
+ sub_list->AppendInteger(200);
+ sub_list->Append(GinJavaBridgeValue::CreateObjectIDValue(2).release());
+ objects.Append(sub_list);
+ base::DictionaryValue* sub_dict = new base::DictionaryValue();
+ sub_dict->SetInteger("1", 300);
+ sub_dict->Set("2", GinJavaBridgeValue::CreateObjectIDValue(3).release());
+ objects.Append(sub_dict);
+ base::ListValue* sub_list_with_dict = new base::ListValue();
+ base::DictionaryValue* sub_sub_dict = new base::DictionaryValue();
+ sub_sub_dict->Set("1", GinJavaBridgeValue::CreateObjectIDValue(4).release());
+ sub_list_with_dict->Append(sub_sub_dict);
+ objects.Append(sub_list_with_dict);
+ base::DictionaryValue* sub_dict_with_list = new base::DictionaryValue();
+ base::ListValue* sub_sub_list = new base::ListValue();
+ sub_sub_list->Append(GinJavaBridgeValue::CreateObjectIDValue(5).release());
+ sub_dict_with_list->Set("1", sub_sub_list);
+ objects.Append(sub_dict_with_list);
+
+ scoped_refptr<GinJavaMethodInvocationHelper> helper =
+ new GinJavaMethodInvocationHelper(
+ scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
+ new NullObjectDelegate()),
+ "foo",
+ objects);
+ CountingDispatcherDelegate counter;
+ helper->Init(&counter);
+ counter.AssertInvocationsCount(1, 6);
+}
+
+TEST_F(GinJavaMethodInvocationHelperTest, HandleObjectIsGone) {
+ base::ListValue no_objects;
+ scoped_refptr<GinJavaMethodInvocationHelper> helper =
+ new GinJavaMethodInvocationHelper(
+ scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
+ new NullObjectDelegate()),
+ "foo",
+ no_objects);
+ NullDispatcherDelegate dispatcher;
+ helper->Init(&dispatcher);
+ EXPECT_TRUE(helper->GetErrorMessage().empty());
+ helper->Invoke();
+ EXPECT_TRUE(helper->HoldsPrimitiveResult());
+ EXPECT_TRUE(helper->GetPrimitiveResult().empty());
+ EXPECT_FALSE(helper->GetErrorMessage().empty());
+}
+
+namespace {
+
+class MethodNotFoundObjectDelegate : public NullObjectDelegate {
+ public:
+ MethodNotFoundObjectDelegate() : find_method_called_(false) {}
+
+ virtual ~MethodNotFoundObjectDelegate() {}
+
+ virtual base::android::ScopedJavaLocalRef<jobject> GetLocalRef(
+ JNIEnv* env) OVERRIDE {
+ return base::android::ScopedJavaLocalRef<jobject>(
+ env, static_cast<jobject>(env->FindClass("java/lang/String")));
+ }
+
+ virtual const JavaMethod* FindMethod(const std::string& method_name,
+ size_t num_parameters) OVERRIDE {
+ find_method_called_ = true;
+ return NULL;
+ }
+
+ bool find_method_called() const { return find_method_called_; }
+
+ protected:
+ bool find_method_called_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MethodNotFoundObjectDelegate);
+};
+
+} // namespace
+
+TEST_F(GinJavaMethodInvocationHelperTest, HandleMethodNotFound) {
+ base::ListValue no_objects;
+ MethodNotFoundObjectDelegate* object_delegate =
+ new MethodNotFoundObjectDelegate();
+ scoped_refptr<GinJavaMethodInvocationHelper> helper =
+ new GinJavaMethodInvocationHelper(
+ scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
+ object_delegate),
+ "foo",
+ no_objects);
+ NullDispatcherDelegate dispatcher;
+ helper->Init(&dispatcher);
+ EXPECT_FALSE(object_delegate->find_method_called());
+ EXPECT_TRUE(helper->GetErrorMessage().empty());
+ helper->Invoke();
+ EXPECT_TRUE(object_delegate->find_method_called());
+ EXPECT_TRUE(helper->HoldsPrimitiveResult());
+ EXPECT_TRUE(helper->GetPrimitiveResult().empty());
+ EXPECT_FALSE(helper->GetErrorMessage().empty());
+}
+
+namespace {
+
+class GetClassObjectDelegate : public MethodNotFoundObjectDelegate {
+ public:
+ GetClassObjectDelegate() : get_class_called_(false) {}
+
+ virtual ~GetClassObjectDelegate() {}
+
+ virtual const JavaMethod* FindMethod(const std::string& method_name,
+ size_t num_parameters) OVERRIDE {
+ find_method_called_ = true;
+ return kFakeGetClass;
+ }
+
+ virtual bool IsObjectGetClassMethod(const JavaMethod* method) OVERRIDE {
+ get_class_called_ = true;
+ return kFakeGetClass == method;
+ }
+
+ bool get_class_called() const { return get_class_called_; }
+
+ private:
+ static const JavaMethod* kFakeGetClass;
+ bool get_class_called_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetClassObjectDelegate);
+};
+
+// We don't expect GinJavaMethodInvocationHelper to actually invoke the
+// method, since the point of the test is to verify whether calls to
+// 'getClass' get blocked.
+const JavaMethod* GetClassObjectDelegate::kFakeGetClass =
+ (JavaMethod*)0xdeadbeef;
+
+} // namespace
+
+TEST_F(GinJavaMethodInvocationHelperTest, HandleGetClassInvocation) {
+ base::ListValue no_objects;
+ GetClassObjectDelegate* object_delegate =
+ new GetClassObjectDelegate();
+ scoped_refptr<GinJavaMethodInvocationHelper> helper =
+ new GinJavaMethodInvocationHelper(
+ scoped_ptr<GinJavaMethodInvocationHelper::ObjectDelegate>(
+ object_delegate),
+ "foo",
+ no_objects);
+ NullDispatcherDelegate dispatcher;
+ helper->Init(&dispatcher);
+ EXPECT_FALSE(object_delegate->find_method_called());
+ EXPECT_FALSE(object_delegate->get_class_called());
+ EXPECT_TRUE(helper->GetErrorMessage().empty());
+ helper->Invoke();
+ EXPECT_TRUE(object_delegate->find_method_called());
+ EXPECT_TRUE(object_delegate->get_class_called());
+ EXPECT_TRUE(helper->HoldsPrimitiveResult());
+ EXPECT_TRUE(helper->GetPrimitiveResult().empty());
+ EXPECT_FALSE(helper->GetErrorMessage().empty());
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc b/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc
new file mode 100644
index 0000000..8f7c29f
--- /dev/null
+++ b/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc
@@ -0,0 +1,705 @@
+// 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_script_to_java_types_coercion.h"
+
+#include <unistd.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/common/android/gin_java_bridge_value.h"
+
+using base::android::ConvertUTF8ToJavaString;
+
+namespace content {
+
+namespace {
+
+const char kJavaLangString[] = "java/lang/String";
+const char kUndefined[] = "undefined";
+
+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 kLimit = (GG_INT64_C(1) << 63) - static_cast<uint64>(1 << 10);
+ DCHECK(kLimit > 0);
+ const double kLargestDoubleLessThanInt64Max = kLimit;
+ const double kSmallestDoubleGreaterThanInt64Min = -kLimit;
+ 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);
+}
+
+jvalue CoerceJavaScriptIntegerToJavaValue(JNIEnv* env,
+ 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(
+ env, 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 CoerceJavaScriptDoubleToJavaValue(JNIEnv* env,
+ 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(
+ env, 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 CoerceJavaScriptBooleanToJavaValue(JNIEnv* env,
+ 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(
+ env, 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 CoerceJavaScriptStringToJavaValue(JNIEnv* env,
+ 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(env, 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;
+}
+
+// Note that this only handles primitive types and strings.
+jobject CreateJavaArray(JNIEnv* env, const JavaType& type, jsize length) {
+ 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: {
+ base::android::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 SetArrayElement(JNIEnv* env,
+ jobject array,
+ const JavaType& type,
+ jsize index,
+ const jvalue& value) {
+ 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 CoerceJavaScriptNullOrUndefinedToJavaValue(JNIEnv* env,
+ 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(env, 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;
+}
+
+jobject CoerceJavaScriptListToArray(JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ const ObjectRefs& object_refs) {
+ 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(env, target_inner_type, length);
+ if (!result) {
+ return NULL;
+ }
+ 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(
+ env, value_element, target_inner_type, false, object_refs);
+ SetArrayElement(env, 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 CoerceJavaScriptDictionaryToArray(JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ const ObjectRefs& object_refs) {
+ 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(env, target_inner_type, length);
+ if (!result) {
+ return NULL;
+ }
+ scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
+ 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(
+ env, value_element, target_inner_type, false, object_refs);
+ SetArrayElement(env, 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;
+}
+
+jvalue CoerceJavaScriptObjectToJavaValue(JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ bool coerce_to_string,
+ const ObjectRefs& object_refs) {
+ // 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::const_iterator iter = object_refs.find(object_id);
+ if (iter != object_refs.end()) {
+ obj.Reset(iter->second.get(env));
+ }
+ }
+ 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(env, 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(
+ env, value, target_type, object_refs);
+ } else if (value->IsType(base::Value::TYPE_LIST)) {
+ result.l =
+ CoerceJavaScriptListToArray(env, value, target_type, object_refs);
+ } else {
+ result.l = NULL;
+ }
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceGinJavaBridgeValueToJavaValue(JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ bool coerce_to_string,
+ const ObjectRefs& object_refs) {
+ DCHECK(GinJavaBridgeValue::ContainsGinJavaBridgeValue(value));
+ scoped_ptr<const GinJavaBridgeValue> gin_value(
+ GinJavaBridgeValue::FromValue(value));
+ switch (gin_value->GetType()) {
+ case GinJavaBridgeValue::TYPE_UNDEFINED:
+ return CoerceJavaScriptNullOrUndefinedToJavaValue(
+ env, value, target_type, coerce_to_string);
+ case GinJavaBridgeValue::TYPE_NONFINITE: {
+ float float_value;
+ gin_value->GetAsNonFinite(&float_value);
+ return CoerceJavaScriptDoubleToJavaValue(
+ env, float_value, target_type, coerce_to_string);
+ }
+ case GinJavaBridgeValue::TYPE_OBJECT_ID:
+ return CoerceJavaScriptObjectToJavaValue(
+ env, value, target_type, coerce_to_string, object_refs);
+ default:
+ NOTREACHED();
+ }
+ return jvalue();
+}
+
+} // namespace
+
+
+void ReleaseJavaValueIfRequired(JNIEnv* env,
+ jvalue* value,
+ const JavaType& type) {
+ if (type.type == JavaType::TypeString || type.type == JavaType::TypeObject ||
+ type.type == JavaType::TypeArray) {
+ env->DeleteLocalRef(value->l);
+ value->l = NULL;
+ }
+}
+
+jvalue CoerceJavaScriptValueToJavaValue(JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ bool coerce_to_string,
+ const ObjectRefs& object_refs) {
+ // 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(
+ env, value, target_type, coerce_to_string);
+ case base::Value::TYPE_DOUBLE: {
+ double double_value;
+ value->GetAsDouble(&double_value);
+ return CoerceJavaScriptDoubleToJavaValue(
+ env, double_value, target_type, coerce_to_string);
+ }
+ case base::Value::TYPE_BOOLEAN:
+ return CoerceJavaScriptBooleanToJavaValue(
+ env, value, target_type, coerce_to_string);
+ case base::Value::TYPE_STRING:
+ return CoerceJavaScriptStringToJavaValue(env, value, target_type);
+ case base::Value::TYPE_DICTIONARY:
+ case base::Value::TYPE_LIST:
+ return CoerceJavaScriptObjectToJavaValue(
+ env, value, target_type, coerce_to_string, object_refs);
+ case base::Value::TYPE_NULL:
+ return CoerceJavaScriptNullOrUndefinedToJavaValue(
+ env, value, target_type, coerce_to_string);
+ case base::Value::TYPE_BINARY:
+ return CoerceGinJavaBridgeValueToJavaValue(
+ env, value, target_type, coerce_to_string, object_refs);
+ }
+ NOTREACHED();
+ return jvalue();
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.h b/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.h
new file mode 100644
index 0000000..1df7345
--- /dev/null
+++ b/content/browser/renderer_host/java/gin_java_script_to_java_types_coercion.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
+
+#include <map>
+
+#include "base/android/jni_weak_ref.h"
+#include "base/values.h"
+#include "content/browser/renderer_host/java/gin_java_bound_object.h"
+#include "content/browser/renderer_host/java/java_type.h"
+
+namespace content {
+
+typedef std::map<GinJavaBoundObject::ObjectID, JavaObjectWeakGlobalRef>
+ ObjectRefs;
+
+jvalue CoerceJavaScriptValueToJavaValue(
+ JNIEnv* env,
+ const base::Value* value,
+ const JavaType& target_type,
+ bool coerce_to_string,
+ const ObjectRefs& object_refs);
+
+void ReleaseJavaValueIfRequired(JNIEnv* env,
+ jvalue* value,
+ const JavaType& type);
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_GIN_JAVA_SCRIPT_TO_JAVA_TYPES_COERCION_H_
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 7144e6b..23a57fe 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -964,6 +964,11 @@
'browser/renderer_host/input/web_input_event_util.h',
'browser/renderer_host/input/web_input_event_util_posix.cc',
'browser/renderer_host/input/web_input_event_util_posix.h',
+ 'browser/renderer_host/java/gin_java_bound_object.h',
+ 'browser/renderer_host/java/gin_java_method_invocation_helper.cc',
+ 'browser/renderer_host/java/gin_java_method_invocation_helper.h',
+ 'browser/renderer_host/java/gin_java_script_to_java_types_coercion.cc',
+ 'browser/renderer_host/java/gin_java_script_to_java_types_coercion.h',
'browser/renderer_host/java/java_bound_object.cc',
'browser/renderer_host/java/java_bound_object.h',
'browser/renderer_host/java/java_bridge_channel_host.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 8ffe45e..48fb198 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -875,6 +875,7 @@
}],
['OS == "android"', {
'sources': [
+ 'browser/renderer_host/java/gin_java_method_invocation_helper_unittest.cc',
'browser/renderer_host/java/jni_helper_unittest.cc',
'renderer/java/gin_java_bridge_value_converter_unittest.cc',
],