diff options
-rw-r--r-- | base/android/jni_android.cc | 17 | ||||
-rw-r--r-- | base/android/jni_android.h | 25 | ||||
-rw-r--r-- | content/browser/renderer_host/java/java_bound_object.cc | 614 | ||||
-rw-r--r-- | content/browser/renderer_host/java/java_bound_object.h | 60 | ||||
-rw-r--r-- | content/browser/renderer_host/java/java_method.cc | 258 | ||||
-rw-r--r-- | content/browser/renderer_host/java/java_method.h | 62 | ||||
-rw-r--r-- | content/content_browser.gypi | 8 |
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(), + ¶meters[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, ¶meter_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', ], }], ], |