Chromium Code Reviews| Index: content/browser/renderer_host/java/java_bound_object.cc |
| 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 0000000000000000000000000000000000000000..987ceaef04b68c51ab72c6de2cc94b8d41df8de7 |
| --- /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; |
|
M-A Ruel
2011/11/11 16:22:28
optional: in general we always put (), e.g. new Ja
Steve Block
2011/11/11 16:31:34
Done.
|
| + 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 { |
| + const_cast<JavaBoundObject*>(this)->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() { |
| + 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)); |
| + } |
| +} |