diff options
author | steveblock@chromium.org <steveblock@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-11 17:08:35 +0000 |
---|---|---|
committer | steveblock@chromium.org <steveblock@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-11 17:08:35 +0000 |
commit | ebed17c7823a01b70934460d2cbe0fec70261a41 (patch) | |
tree | fe6a0e5e62f5e6a02a1f8dc07abc9dd6af9fbe79 /content/browser | |
parent | 3826a8a0942fd634616d009a45f5eafb7ee567c6 (diff) | |
download | chromium_src-ebed17c7823a01b70934460d2cbe0fec70261a41.zip chromium_src-ebed17c7823a01b70934460d2cbe0fec70261a41.tar.gz chromium_src-ebed17c7823a01b70934460d2cbe0fec70261a41.tar.bz2 |
Add JavaBoundObject
This represents a Java object for use in the Java bridge. It handles all of the
JNI required to interrogate the Java object and call methods on it.
Also adds some JNI utility methods to simpify the new code.
BUG=96703
Review URL: http://codereview.chromium.org/8509019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@109645 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
-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 |
4 files changed, 994 insertions, 0 deletions
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_ |