summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/android/jni_android.cc17
-rw-r--r--base/android/jni_android.h25
-rw-r--r--content/browser/renderer_host/java/java_bound_object.cc614
-rw-r--r--content/browser/renderer_host/java/java_bound_object.h60
-rw-r--r--content/browser/renderer_host/java/java_method.cc258
-rw-r--r--content/browser/renderer_host/java/java_method.h62
-rw-r--r--content/content_browser.gypi8
7 files changed, 1042 insertions, 2 deletions
diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc
index 1dc02af..e6d6e56 100644
--- a/base/android/jni_android.cc
+++ b/base/android/jni_android.cc
@@ -4,6 +4,7 @@
#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
#include "base/logging.h"
namespace {
@@ -46,6 +47,12 @@ jobject GetApplicationContext() {
return g_application_context;
}
+MethodID::MethodID(JNIEnv* env, const char* class_name, const char* method,
+ const char* jni_signature) {
+ ScopedJavaLocalRef<jclass> clazz(env, env->FindClass(class_name));
+ id_ = GetMethodID(env, clazz.obj(), method, jni_signature);
+}
+
jmethodID GetMethodID(JNIEnv* env,
jclass clazz,
const char* const method,
@@ -66,6 +73,16 @@ jmethodID GetStaticMethodID(JNIEnv* env,
return id;
}
+jfieldID GetFieldID(JNIEnv* env,
+ jclass clazz,
+ const char* field,
+ const char* jni_signature) {
+ jfieldID id = env->GetFieldID(clazz, field, jni_signature);
+ DCHECK(id) << field;
+ CheckException(env);
+ return id;
+}
+
bool CheckException(JNIEnv* env) {
if (env->ExceptionCheck() == JNI_FALSE)
return false;
diff --git a/base/android/jni_android.h b/base/android/jni_android.h
index 66b3134..462173f 100644
--- a/base/android/jni_android.h
+++ b/base/android/jni_android.h
@@ -30,6 +30,20 @@ void InitApplicationContext(jobject context);
// Returns the application context assigned by InitApplicationContext().
jobject GetApplicationContext();
+// Wraps a method ID.
+class MethodID {
+ public:
+ jmethodID id() { return id_; }
+ protected:
+ // Gets the method ID from the class name. Clears the pending Java exception
+ // and returns NULL if the method is not found. Note that GetMethodID() below
+ // avoids a class lookup, so should be used in preference when possible.
+ MethodID(JNIEnv* env, const char* class_name, const char* method,
+ const char* jni_signature);
+ private:
+ jmethodID id_;
+};
+
// Get the method ID for a method. Will clear the pending Java
// exception and return 0 if the method is not found.
jmethodID GetMethodID(JNIEnv* env,
@@ -44,8 +58,15 @@ jmethodID GetStaticMethodID(JNIEnv* env,
const char* const method,
const char* const jni_signature);
-// Returns true if an exception is pending in the provided JNIEnv*.
-// If an exception is pending, it is printed.
+// Gets the field ID for a class field. Clears the pending Java exception and
+// returns NULL if the field is not found.
+jfieldID GetFieldID(JNIEnv* env,
+ jclass clazz,
+ const char* field,
+ const char* jni_signature);
+
+// Returns true if an exception is pending in the provided JNIEnv*. If an
+// exception is pending, this function prints and then clears it.
bool CheckException(JNIEnv* env);
} // namespace android
diff --git a/content/browser/renderer_host/java/java_bound_object.cc b/content/browser/renderer_host/java/java_bound_object.cc
new file mode 100644
index 0000000..ff113d4
--- /dev/null
+++ b/content/browser/renderer_host/java/java_bound_object.cc
@@ -0,0 +1,614 @@
+// Copyright (c) 2011 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/java_bound_object.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/memory/singleton.h"
+#include "base/string_number_conversions.h"
+#include "base/stringprintf.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
+
+using base::StringPrintf;
+using base::android::AttachCurrentThread;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaRef;
+using base::android::MethodID;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using WebKit::WebBindings;
+
+// The conversion between JavaScript and Java types is based on the Live
+// Connect 2 spec. See
+// http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
+
+// Note that in some cases, we differ from from the spec in order to maintain
+// existing behavior. These areas are marked LIVECONNECT_COMPLIANCE. We may
+// revisit this decision in the future.
+
+namespace {
+
+std::string NPIdentifierToString(NPIdentifier identifier) {
+ const NPUTF8* string;
+ int32_t number;
+ bool is_string;
+ WebBindings::extractIdentifierData(identifier, string, number, is_string);
+ DCHECK(is_string);
+ return string;
+}
+
+// Our special NPObject type. We extend an NPObject with a pointer to a
+// JavaBoundObject. We also add static methods for each of the NPObject
+// callbacks, which are registered by our NPClass. These methods simply
+// delegate to the private implementation methods of JavaBoundObject.
+struct JavaNPObject : public NPObject {
+ JavaBoundObject* bound_object;
+
+ static const NPClass kNPClass;
+
+ static NPObject* Allocate(NPP npp, NPClass* np_class);
+ static void Deallocate(NPObject* np_object);
+ static bool HasMethod(NPObject* np_object, NPIdentifier np_identifier);
+ static bool Invoke(NPObject* np_object, NPIdentifier np_identifier,
+ const NPVariant *args, uint32_t arg_count,
+ NPVariant *result);
+ static bool HasProperty(NPObject* np_object, NPIdentifier np_identifier);
+ static bool GetProperty(NPObject* np_object, NPIdentifier np_identifier,
+ NPVariant *result);
+};
+
+const NPClass JavaNPObject::kNPClass = {
+ NP_CLASS_STRUCT_VERSION,
+ JavaNPObject::Allocate,
+ JavaNPObject::Deallocate,
+ NULL, // NPInvalidate
+ JavaNPObject::HasMethod,
+ JavaNPObject::Invoke,
+ NULL, // NPInvokeDefault
+ JavaNPObject::HasProperty,
+ JavaNPObject::GetProperty,
+ NULL, // NPSetProperty,
+ NULL, // NPRemoveProperty
+};
+
+NPObject* JavaNPObject::Allocate(NPP npp, NPClass* np_class) {
+ JavaNPObject* obj = new JavaNPObject();
+ return obj;
+}
+
+void JavaNPObject::Deallocate(NPObject* np_object) {
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ delete obj->bound_object;
+ delete obj;
+}
+
+bool JavaNPObject::HasMethod(NPObject* np_object, NPIdentifier np_identifier) {
+ std::string name = NPIdentifierToString(np_identifier);
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ return obj->bound_object->HasMethod(name);
+}
+
+bool JavaNPObject::Invoke(NPObject* np_object, NPIdentifier np_identifier,
+ const NPVariant* args, uint32_t arg_count,
+ NPVariant* result) {
+ std::string name = NPIdentifierToString(np_identifier);
+ JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
+ return obj->bound_object->Invoke(name, args, arg_count, result);
+}
+
+bool JavaNPObject::HasProperty(NPObject* np_object,
+ NPIdentifier np_identifier) {
+ // LIVECONNECT_COMPLIANCE: Return false to indicate that the property is not
+ // present. We should support this correctly.
+ return false;
+}
+
+bool JavaNPObject::GetProperty(NPObject* np_object,
+ NPIdentifier np_identifier,
+ NPVariant* result) {
+ // LIVECONNECT_COMPLIANCE: Return false to indicate that the property is
+ // undefined. We should support this correctly.
+ return false;
+}
+
+// Calls a Java method through JNI and returns the result as an NPVariant. Note
+// that this method does not do any type coercion. The Java return value is
+// simply converted to the corresponding NPAPI type.
+NPVariant CallJNIMethod(jobject object, JavaType::Type return_type,
+ jmethodID id, jvalue* parameters) {
+ JNIEnv* env = AttachCurrentThread();
+ NPVariant result;
+ switch (return_type) {
+ case JavaType::TypeBoolean:
+ BOOLEAN_TO_NPVARIANT(env->CallBooleanMethodA(object, id, parameters),
+ result);
+ break;
+ case JavaType::TypeByte:
+ INT32_TO_NPVARIANT(env->CallByteMethodA(object, id, parameters), result);
+ break;
+ case JavaType::TypeChar:
+ INT32_TO_NPVARIANT(env->CallCharMethodA(object, id, parameters), result);
+ break;
+ case JavaType::TypeShort:
+ INT32_TO_NPVARIANT(env->CallShortMethodA(object, id, parameters), result);
+ break;
+ case JavaType::TypeInt:
+ INT32_TO_NPVARIANT(env->CallIntMethodA(object, id, parameters), result);
+ break;
+ case JavaType::TypeLong:
+ DOUBLE_TO_NPVARIANT(env->CallLongMethodA(object, id, parameters), result);
+ break;
+ case JavaType::TypeFloat:
+ DOUBLE_TO_NPVARIANT(env->CallFloatMethodA(object, id, parameters),
+ result);
+ break;
+ case JavaType::TypeDouble:
+ DOUBLE_TO_NPVARIANT(env->CallDoubleMethodA(object, id, parameters),
+ result);
+ break;
+ case JavaType::TypeVoid:
+ env->CallVoidMethodA(object, id, parameters);
+ VOID_TO_NPVARIANT(result);
+ break;
+ case JavaType::TypeArray:
+ // TODO(steveblock): Handle arrays
+ VOID_TO_NPVARIANT(result);
+ break;
+ case JavaType::TypeString: {
+ ScopedJavaLocalRef<jstring> java_string(env, static_cast<jstring>(
+ env->CallObjectMethodA(object, id, parameters)));
+ if (!java_string.obj()) {
+ // LIVECONNECT_COMPLIANCE: Return undefined to maintain existing
+ // behavior. We should return a null string.
+ VOID_TO_NPVARIANT(result);
+ break;
+ }
+ std::string str =
+ base::android::ConvertJavaStringToUTF8(env, java_string.obj());
+ // Take a copy and pass ownership to the variant. We must allocate using
+ // NPN_MemAlloc, to match NPN_ReleaseVariant, which uses NPN_MemFree.
+ size_t length = str.length();
+ char* buffer = static_cast<char*>(NPN_MemAlloc(length));
+ str.copy(buffer, length, 0);
+ STRINGN_TO_NPVARIANT(buffer, length, result);
+ break;
+ }
+ case JavaType::TypeObject: {
+ ScopedJavaLocalRef<jobject> java_object(
+ env,
+ env->CallObjectMethodA(object, id, parameters));
+ if (!java_object.obj()) {
+ NULL_TO_NPVARIANT(result);
+ break;
+ }
+ OBJECT_TO_NPVARIANT(JavaBoundObject::Create(java_object), result);
+ break;
+ }
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptNumberToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES.
+ jvalue result;
+ DCHECK(variant.type == NPVariantType_Int32 ||
+ variant.type == NPVariantType_Double);
+ bool is_double = variant.type == NPVariantType_Double;
+ switch (target_type) {
+ case JavaType::TypeByte:
+ result.b = is_double ? static_cast<jbyte>(NPVARIANT_TO_DOUBLE(variant)) :
+ static_cast<jbyte>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeChar:
+ // LIVECONNECT_COMPLIANCE: Convert double to 0 to maintain existing
+ // behavior.
+ result.c = is_double ? 0 :
+ static_cast<jchar>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeShort:
+ result.s = is_double ? static_cast<jshort>(NPVARIANT_TO_DOUBLE(variant)) :
+ static_cast<jshort>(NPVARIANT_TO_INT32(variant));
+ break;
+ case JavaType::TypeInt:
+ result.i = is_double ? static_cast<jint>(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeLong:
+ result.j = is_double ? static_cast<jlong>(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeFloat:
+ result.f = is_double ? static_cast<jfloat>(NPVARIANT_TO_DOUBLE(variant)) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeDouble:
+ result.d = is_double ? NPVARIANT_TO_DOUBLE(variant) :
+ NPVARIANT_TO_INT32(variant);
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Convert to null to maintain existing behavior.
+ // We should handle object equivalents of primitive types.
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ result.l = ConvertUTF8ToJavaString(
+ AttachCurrentThread(),
+ is_double ? StringPrintf("%.6lg", NPVARIANT_TO_DOUBLE(variant)) :
+ base::IntToString(NPVARIANT_TO_INT32(variant)));
+ break;
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Convert to false to maintain existing behavior.
+ // We should convert to false for o or NaN, true otherwise.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Convert to null to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptBooleanToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES.
+ DCHECK_EQ(NPVariantType_Bool, variant.type);
+ bool boolean_value = NPVARIANT_TO_BOOLEAN(variant);
+ jvalue result;
+ switch (target_type) {
+ case JavaType::TypeBoolean:
+ result.z = boolean_value ? JNI_TRUE : JNI_FALSE;
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Convert to NULL to maintain existing behavior.
+ // We should handle java.lang.Boolean and java.lang.Object.
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ result.l = ConvertUTF8ToJavaString(AttachCurrentThread(),
+ boolean_value ? "true" : "false");
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeChar:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble: {
+ // LIVECONNECT_COMPLIANCE: Convert to 0 to maintain existing behavior. We
+ // should convert to 0 or 1.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Convert to NULL to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptStringToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES.
+ DCHECK_EQ(NPVariantType_String, variant.type);
+ jvalue result;
+ switch (target_type) {
+ case JavaType::TypeString:
+ result.l = ConvertUTF8ToJavaString(
+ AttachCurrentThread(),
+ base::StringPiece(NPVARIANT_TO_STRING(variant).UTF8Characters,
+ NPVARIANT_TO_STRING(variant).UTF8Length));
+ break;
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Convert to NULL to maintain existing behavior.
+ // We should handle 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: Convert to 0 to maintain existing behavior. we
+ // should use valueOf() method of corresponding object type.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeChar:
+ // LIVECONNECT_COMPLIANCE: Convert to 0 to maintain existing behavior. we
+ // should use java.lang.Short.decode().
+ result.c = 0;
+ break;
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Convert to false to maintain existing behavior.
+ // We should convert the empty string to false, otherwise true.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Convert to NULL to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // We only handle Java objects. See
+ // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS.
+ // TODO(steveblock): Handle arrays.
+ // TODO(steveblock): Handle JavaScript objects.
+ DCHECK_EQ(NPVariantType_Object, variant.type);
+
+ // The only type of object we should encounter is a Java object, as
+ // other objects should have been converted to NULL in the renderer.
+ // See CreateNPVariantParam().
+ // TODO(steveblock): This will have to change once we support arrays and
+ // JavaScript objects.
+ NPObject* object = NPVARIANT_TO_OBJECT(variant);
+ DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
+
+ jvalue result;
+ switch (target_type) {
+ case JavaType::TypeObject:
+ // LIVECONNECT_COMPLIANCE: Pass all Java objects to maintain existing
+ // behavior. We should pass only Java objects which are
+ // assignment-compatibile.
+ result.l = AttachCurrentThread()->NewLocalRef(
+ JavaBoundObject::GetJavaObject(object));
+ break;
+ case JavaType::TypeString:
+ // LIVECONNECT_COMPLIANCE: Convert to "undefined" to maintain existing
+ // behavior. We should call toString() on the Java object.
+ result.l = ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined");
+ break;
+ case JavaType::TypeByte:
+ case JavaType::TypeShort:
+ case JavaType::TypeInt:
+ case JavaType::TypeLong:
+ case JavaType::TypeFloat:
+ case JavaType::TypeDouble:
+ case JavaType::TypeChar: {
+ // LIVECONNECT_COMPLIANCE: Convert to 0 to maintain existing behavior. We
+ // should raise a JavaScript exception.
+ jvalue null_value = {0};
+ result = null_value;
+ break;
+ }
+ case JavaType::TypeBoolean:
+ // LIVECONNECT_COMPLIANCE: Convert to false to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.z = JNI_FALSE;
+ break;
+ case JavaType::TypeArray:
+ // LIVECONNECT_COMPLIANCE: Convert to NULL to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptNullOrUndefinedToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // See http://jdk6.java.net/plugin2/liveconnect/#JS_NULL.
+ DCHECK(variant.type == NPVariantType_Null ||
+ variant.type == NPVariantType_Void);
+ jvalue result;
+ switch (target_type) {
+ case JavaType::TypeObject:
+ result.l = NULL;
+ break;
+ case JavaType::TypeString:
+ if (variant.type == NPVariantType_Void) {
+ // LIVECONNECT_COMPLIANCE: Convert undefined to "undefined" to maintain
+ // existing behavior. We should convert undefined to NULL.
+ result.l = ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined");
+ } else {
+ result.l = 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: Convert to NULL to maintain existing behavior.
+ // We should raise a JavaScript exception.
+ result.l = NULL;
+ break;
+ case JavaType::TypeVoid:
+ // Conversion to void must never happen.
+ NOTREACHED();
+ break;
+ }
+ return result;
+}
+
+jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant,
+ JavaType::Type target_type) {
+ // Note that in all these conversions, the relevant field of the jvalue must
+ // always be explicitly set, as jvalue does not initialize its fields.
+
+ // Some of these methods create new Java Strings. Note that we don't
+ // explicitly release the local ref to these new objects, as there's no simple
+ // way to do so.
+ switch (variant.type) {
+ case NPVariantType_Int32:
+ case NPVariantType_Double:
+ return CoerceJavaScriptNumberToJavaValue(variant, target_type);
+ case NPVariantType_Bool:
+ return CoerceJavaScriptBooleanToJavaValue(variant, target_type);
+ case NPVariantType_String:
+ return CoerceJavaScriptStringToJavaValue(variant, target_type);
+ case NPVariantType_Object:
+ return CoerceJavaScriptObjectToJavaValue(variant, target_type);
+ case NPVariantType_Null:
+ case NPVariantType_Void:
+ return CoerceJavaScriptNullOrUndefinedToJavaValue(variant, target_type);
+ }
+ NOTREACHED();
+ return jvalue();
+}
+
+class ObjectGetClassID : public MethodID {
+ public:
+ static ObjectGetClassID* GetInstance() {
+ return Singleton<ObjectGetClassID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<ObjectGetClassID>;
+ ObjectGetClassID()
+ : MethodID(AttachCurrentThread(), "java/lang/Object", "getClass",
+ "()Ljava/lang/Class;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(ObjectGetClassID);
+};
+
+class ClassGetMethodsID : public MethodID {
+ public:
+ static ClassGetMethodsID* GetInstance() {
+ return Singleton<ClassGetMethodsID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<ClassGetMethodsID>;
+ ClassGetMethodsID()
+ : MethodID(AttachCurrentThread(), "java/lang/Class", "getMethods",
+ "()[Ljava/lang/reflect/Method;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(ClassGetMethodsID);
+};
+
+} // namespace
+
+
+NPObject* JavaBoundObject::Create(const JavaRef<jobject>& object) {
+ // The first argument (a plugin's instance handle) is passed through to the
+ // allocate function directly, and we don't use it, so it's ok to be 0.
+ // The object is created with a ref count of one.
+ NPObject* np_object = WebBindings::createObject(0, const_cast<NPClass*>(
+ &JavaNPObject::kNPClass));
+ // The NPObject takes ownership of the JavaBoundObject.
+ reinterpret_cast<JavaNPObject*>(np_object)->bound_object =
+ new JavaBoundObject(object);
+ return np_object;
+}
+
+JavaBoundObject::JavaBoundObject(const JavaRef<jobject>& object)
+ : java_object_(object.env()->NewGlobalRef(object.obj())) {
+ // We don't do anything with our Java object when first created. We do it all
+ // lazily when a method is first invoked.
+}
+
+JavaBoundObject::~JavaBoundObject() {
+ // Use the current thread's JNI env to release our global ref to the Java
+ // object.
+ AttachCurrentThread()->DeleteGlobalRef(java_object_);
+}
+
+jobject JavaBoundObject::GetJavaObject(NPObject* object) {
+ DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
+ JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object;
+ return jbo->java_object_;
+}
+
+bool JavaBoundObject::HasMethod(const std::string& name) const {
+ EnsureMethodsAreSetUp();
+ return methods_.find(name) != methods_.end();
+}
+
+bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args,
+ size_t arg_count, NPVariant* result) {
+ EnsureMethodsAreSetUp();
+
+ // Get all methods with the correct name.
+ std::pair<JavaMethodMap::const_iterator, JavaMethodMap::const_iterator>
+ iters = methods_.equal_range(name);
+ if (iters.first == iters.second) {
+ return false;
+ }
+
+ // Take the first method with the correct number of arguments.
+ JavaMethod* method = NULL;
+ for (JavaMethodMap::const_iterator iter = iters.first; iter != iters.second;
+ ++iter) {
+ if (iter->second->num_parameters() == arg_count) {
+ method = iter->second.get();
+ break;
+ }
+ }
+ if (!method) {
+ return false;
+ }
+
+ // Coerce
+ std::vector<jvalue> parameters(arg_count);
+ for (size_t i = 0; i < arg_count; ++i) {
+ parameters[i] = CoerceJavaScriptValueToJavaValue(args[i],
+ method->parameter_type(i));
+ }
+
+ // Call
+ *result = CallJNIMethod(java_object_, method->return_type(), method->id(),
+ &parameters[0]);
+ return true;
+}
+
+void JavaBoundObject::EnsureMethodsAreSetUp() const {
+ if (!methods_.empty()) {
+ return;
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
+ env->CallObjectMethod(java_object_,
+ ObjectGetClassID::GetInstance()->id())));
+ ScopedJavaLocalRef<jobjectArray> methods(env, static_cast<jobjectArray>(
+ env->CallObjectMethod(clazz.obj(),
+ ClassGetMethodsID::GetInstance()->id())));
+ size_t num_methods = env->GetArrayLength(methods.obj());
+ DCHECK(num_methods) << "Java objects always have public methods";
+ for (size_t i = 0; i < num_methods; ++i) {
+ ScopedJavaLocalRef<jobject> java_method(
+ env,
+ env->GetObjectArrayElement(methods.obj(), i));
+ JavaMethod* method = new JavaMethod(java_method);
+ methods_.insert(std::make_pair(method->name(), method));
+ }
+}
diff --git a/content/browser/renderer_host/java/java_bound_object.h b/content/browser/renderer_host/java/java_bound_object.h
new file mode 100644
index 0000000..dffb2f9
--- /dev/null
+++ b/content/browser/renderer_host/java/java_bound_object.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 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_JAVA_BOUND_OBJECT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_
+
+#include <jni.h>
+#include <map>
+#include <string>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/linked_ptr.h"
+#include "content/browser/renderer_host/java/java_method.h"
+#include "third_party/npapi/bindings/npruntime.h"
+
+// Wrapper around a Java object.
+//
+// Represents a Java object for use in the Java bridge. Holds a global ref to
+// the Java object and provides the ability to invoke methods on it.
+// Interrogation of the Java object for its methods is done lazily. This class
+// is not generally threadsafe. However, it does allow for instances to be
+// created and destroyed on different threads.
+class JavaBoundObject {
+ public:
+ // Takes a Java object and creates a JavaBoundObject around it. Returns an
+ // NPObject with a ref count of one which owns the JavaBoundObject.
+ static NPObject* Create(const base::android::JavaRef<jobject>& object);
+
+ virtual ~JavaBoundObject();
+
+ // Gets the underlying JavaObject from a JavaBoundObject wrapped as an
+ // NPObject.
+ static jobject GetJavaObject(NPObject* object);
+
+ // Methods to implement the NPObject callbacks.
+ bool HasMethod(const std::string& name) const;
+ bool Invoke(const std::string& name, const NPVariant* args, size_t arg_count,
+ NPVariant* result);
+
+ private:
+ explicit JavaBoundObject(const base::android::JavaRef<jobject>& object);
+
+ void EnsureMethodsAreSetUp() const;
+
+ // Global ref to the underlying Java object. We use a naked jobject, rather
+ // than a ScopedJavaGlobalRef, as the global ref will be added and dropped on
+ // different threads.
+ jobject java_object_;
+
+ // Map of public methods, from method name to Method instance. Multiple
+ // entries will be present for overloaded methods. Note that we can't use
+ // scoped_ptr in STL containers as we can't copy it.
+ typedef std::multimap<std::string, linked_ptr<JavaMethod> > JavaMethodMap;
+ mutable JavaMethodMap methods_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JavaBoundObject);
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_
diff --git a/content/browser/renderer_host/java/java_method.cc b/content/browser/renderer_host/java/java_method.cc
new file mode 100644
index 0000000..d65bd0c
--- /dev/null
+++ b/content/browser/renderer_host/java/java_method.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2011 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/java_method.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "base/memory/singleton.h"
+#include "base/string_util.h" // For ReplaceSubstringsAfterOffset
+
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::MethodID;
+using base::android::ScopedJavaLocalRef;
+
+namespace {
+
+// Java's reflection API represents types as a string using an extended 'binary
+// name'. This converts to an enum which we store in place of the binary name
+// for simplicity.
+JavaType::Type BinaryNameToType(const std::string& binary_name) {
+ if (binary_name == "boolean") {
+ return JavaType::TypeBoolean;
+ } else if (binary_name == "byte") {
+ return JavaType::TypeByte;
+ } else if (binary_name == "char") {
+ return JavaType::TypeChar;
+ } else if (binary_name == "short") {
+ return JavaType::TypeShort;
+ } else if (binary_name == "int") {
+ return JavaType::TypeInt;
+ } else if (binary_name == "long") {
+ return JavaType::TypeLong;
+ } else if (binary_name == "float") {
+ return JavaType::TypeFloat;
+ } else if (binary_name == "double") {
+ return JavaType::TypeDouble;
+ } else if (binary_name == "void") {
+ return JavaType::TypeVoid;
+ } else if (binary_name[0] == '[') {
+ return JavaType::TypeArray;
+ } else if (binary_name == "java.lang.String") {
+ return JavaType::TypeString;
+ }
+ return JavaType::TypeObject;
+}
+
+std::string BinaryNameToJNIName(const std::string& binary_name,
+ JavaType::Type* type) {
+ DCHECK(type);
+ *type = BinaryNameToType(binary_name);
+ switch (*type) {
+ case JavaType::TypeBoolean:
+ return "Z";
+ case JavaType::TypeByte:
+ return "B";
+ case JavaType::TypeChar:
+ return "C";
+ case JavaType::TypeShort:
+ return "S";
+ case JavaType::TypeInt:
+ return "I";
+ case JavaType::TypeLong:
+ return "J";
+ case JavaType::TypeFloat:
+ return "F";
+ case JavaType::TypeDouble:
+ return "D";
+ case JavaType::TypeVoid:
+ return "V";
+ case JavaType::TypeArray:
+ return "[";
+ default:
+ DCHECK (*type == JavaType::TypeString || *type == JavaType::TypeObject);
+ std::string jni_name = "L" + binary_name + ";";
+ ReplaceSubstringsAfterOffset(&jni_name, 0, ".", "/");
+ return jni_name;
+ }
+}
+
+class MethodGetParameterTypesID : public MethodID {
+ public:
+ static MethodGetParameterTypesID* GetInstance() {
+ return Singleton<MethodGetParameterTypesID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<MethodGetParameterTypesID>;
+ MethodGetParameterTypesID()
+ : MethodID(AttachCurrentThread(), "java/lang/reflect/Method",
+ "getParameterTypes", "()[Ljava/lang/Class;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(MethodGetParameterTypesID);
+};
+
+class MethodGetNameID : public MethodID {
+ public:
+ static MethodGetNameID* GetInstance() {
+ return Singleton<MethodGetNameID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<MethodGetNameID>;
+ MethodGetNameID()
+ : MethodID(AttachCurrentThread(), "java/lang/reflect/Method",
+ "getName", "()Ljava/lang/String;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(MethodGetNameID);
+};
+
+class MethodGetReturnTypeID : public MethodID {
+ public:
+ static MethodGetReturnTypeID* GetInstance() {
+ return Singleton<MethodGetReturnTypeID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<MethodGetReturnTypeID>;
+ MethodGetReturnTypeID()
+ : MethodID(AttachCurrentThread(), "java/lang/reflect/Method",
+ "getReturnType", "()Ljava/lang/Class;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(MethodGetReturnTypeID);
+};
+
+class MethodGetDeclaringClassID : public MethodID {
+ public:
+ static MethodGetDeclaringClassID* GetInstance() {
+ return Singleton<MethodGetDeclaringClassID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<MethodGetDeclaringClassID>;
+ MethodGetDeclaringClassID()
+ : MethodID(AttachCurrentThread(), "java/lang/reflect/Method",
+ "getDeclaringClass", "()Ljava/lang/Class;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(MethodGetDeclaringClassID);
+};
+
+class ClassGetNameID : public MethodID {
+ public:
+ static ClassGetNameID* GetInstance() {
+ return Singleton<ClassGetNameID>::get();
+ }
+ private:
+ friend struct DefaultSingletonTraits<ClassGetNameID>;
+ ClassGetNameID()
+ : MethodID(AttachCurrentThread(), "java/lang/Class", "getName",
+ "()Ljava/lang/String;") {
+ }
+ DISALLOW_COPY_AND_ASSIGN(ClassGetNameID);
+};
+
+} // namespace
+
+JavaMethod::JavaMethod(const base::android::JavaRef<jobject>& method)
+ : java_method_(method),
+ have_calculated_num_parameters_(false),
+ id_(NULL) {
+ JNIEnv* env = java_method_.env();
+ // On construction, we do nothing except get the name. Everything else is
+ // done lazily.
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(java_method_.obj(),
+ MethodGetNameID::GetInstance()->id())));
+ name_ = ConvertJavaStringToUTF8(env, name.obj());
+}
+
+JavaMethod::~JavaMethod() {
+}
+
+size_t JavaMethod::num_parameters() const {
+ EnsureNumParametersIsSetUp();
+ return num_parameters_;
+}
+
+JavaType::Type JavaMethod::parameter_type(size_t index) const {
+ EnsureTypesAndIDAreSetUp();
+ return parameter_types_[index];
+}
+
+JavaType::Type JavaMethod::return_type() const {
+ EnsureTypesAndIDAreSetUp();
+ return return_type_;
+}
+
+jmethodID JavaMethod::id() const {
+ EnsureTypesAndIDAreSetUp();
+ return id_;
+}
+
+void JavaMethod::EnsureNumParametersIsSetUp() const {
+ if (have_calculated_num_parameters_) {
+ return;
+ }
+ have_calculated_num_parameters_ = true;
+
+ // The number of parameters will be used frequently when determining
+ // whether to call this method. We don't get the ID etc until actually
+ // required.
+ JNIEnv* env = java_method_.env();
+ ScopedJavaLocalRef<jarray> parameters(env, static_cast<jarray>(
+ env->CallObjectMethod(java_method_.obj(),
+ MethodGetParameterTypesID::GetInstance()->id())));
+ num_parameters_ = env->GetArrayLength(parameters.obj());
+}
+
+void JavaMethod::EnsureTypesAndIDAreSetUp() const {
+ if (id_) {
+ return;
+ }
+
+ // Get the parameters
+ JNIEnv* env = java_method_.env();
+ ScopedJavaLocalRef<jobjectArray> parameters(env, static_cast<jobjectArray>(
+ env->CallObjectMethod(java_method_.obj(),
+ MethodGetParameterTypesID::GetInstance()->id())));
+ // Usually, this will already have been called.
+ EnsureNumParametersIsSetUp();
+ DCHECK_EQ(num_parameters_,
+ static_cast<size_t>(env->GetArrayLength(parameters.obj())));
+
+ // Java gives us the argument type using an extended version of the 'binary
+ // name'. See
+ // http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#getName().
+ // If we build the signature now, there's no need to store the binary name
+ // of the arguments. We just store the simple type.
+ std::string signature("(");
+
+ // Form the signature and record the parameter types.
+ parameter_types_.resize(num_parameters_);
+ for (size_t i = 0; i < num_parameters_; ++i) {
+ ScopedJavaLocalRef<jobject> parameter(env, env->GetObjectArrayElement(
+ parameters.obj(), i));
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(parameter.obj(),
+ ClassGetNameID::GetInstance()->id())));
+ std::string name_utf8 = ConvertJavaStringToUTF8(env, name.obj());
+ signature += BinaryNameToJNIName(name_utf8, &parameter_types_[i]);
+ }
+ signature += ")";
+
+ // Get the return type
+ ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
+ env->CallObjectMethod(java_method_.obj(),
+ MethodGetReturnTypeID::GetInstance()->id())));
+ ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
+ env->CallObjectMethod(clazz.obj(), ClassGetNameID::GetInstance()->id())));
+ signature += BinaryNameToJNIName(ConvertJavaStringToUTF8(env, name.obj()),
+ &return_type_);
+
+ // Get the ID for this method.
+ ScopedJavaLocalRef<jclass> declaring_class(env, static_cast<jclass>(
+ env->CallObjectMethod(java_method_.obj(),
+ MethodGetDeclaringClassID::GetInstance()->id())));
+ id_ = base::android::GetMethodID(env, declaring_class.obj(), name_.c_str(),
+ signature.c_str());
+
+ java_method_.Reset();
+}
diff --git a/content/browser/renderer_host/java/java_method.h b/content/browser/renderer_host/java/java_method.h
new file mode 100644
index 0000000..fc6bcef
--- /dev/null
+++ b/content/browser/renderer_host/java/java_method.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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_JAVA_METHOD_H_
+#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_
+
+#include <jni.h>
+#include <string>
+#include <vector>
+
+#include "base/android/scoped_java_ref.h"
+
+namespace JavaType {
+enum Type {
+ TypeBoolean,
+ TypeByte,
+ TypeChar,
+ TypeShort,
+ TypeInt,
+ TypeLong,
+ TypeFloat,
+ TypeDouble,
+ // This is only used as a return type, so we should never convert from
+ // JavaScript with this type.
+ TypeVoid,
+ TypeArray,
+ // We special-case strings, as they get special handling when coercing.
+ TypeString,
+ TypeObject,
+};
+} // namespace JavaType
+
+// Wrapper around java.lang.reflect.Method. This class must be used on a single
+// thread only.
+class JavaMethod {
+ public:
+ explicit JavaMethod(const base::android::JavaRef<jobject>& method);
+ ~JavaMethod();
+
+ const std::string& name() const { return name_; }
+ size_t num_parameters() const;
+ JavaType::Type parameter_type(size_t index) const;
+ JavaType::Type return_type() const;
+ jmethodID id() const;
+
+ private:
+ void EnsureNumParametersIsSetUp() const;
+ void EnsureTypesAndIDAreSetUp() const;
+
+ std::string name_;
+ mutable base::android::ScopedJavaGlobalRef<jobject> java_method_;
+ mutable bool have_calculated_num_parameters_;
+ mutable size_t num_parameters_;
+ mutable std::vector<JavaType::Type> parameter_types_;
+ mutable JavaType::Type return_type_;
+ mutable jmethodID id_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JavaMethod);
+};
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 7d98a06..ee96cda 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -346,12 +346,16 @@
'browser/renderer_host/gtk_window_utils.h',
'browser/renderer_host/image_transport_client.cc',
'browser/renderer_host/image_transport_client.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',
'browser/renderer_host/java/java_bridge_channel_host.h',
'browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc',
'browser/renderer_host/java/java_bridge_dispatcher_host_manager.h',
'browser/renderer_host/java/java_bridge_dispatcher_host.cc',
'browser/renderer_host/java/java_bridge_dispatcher_host.h',
+ 'browser/renderer_host/java/java_method.cc',
+ 'browser/renderer_host/java/java_method.h',
'browser/renderer_host/media/audio_common.cc',
'browser/renderer_host/media/audio_common.h',
'browser/renderer_host/media/audio_input_device_manager.cc',
@@ -768,12 +772,16 @@
],
}, {
'sources!': [
+ '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',
'browser/renderer_host/java/java_bridge_channel_host.h',
'browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc',
'browser/renderer_host/java/java_bridge_dispatcher_host_manager.h',
'browser/renderer_host/java/java_bridge_dispatcher_host.cc',
'browser/renderer_host/java/java_bridge_dispatcher_host.h',
+ 'browser/renderer_host/java/java_method.cc',
+ 'browser/renderer_host/java/java_method.h',
],
}],
],