From 679147eead574d186ebf3069647b4c23e8ccace6 Mon Sep 17 00:00:00 2001 From: Zeno Albisser Date: Thu, 15 Aug 2013 21:46:11 +0200 Subject: Initial import. --- chromium/content/browser/renderer_host/java/DEPS | 4 + chromium/content/browser/renderer_host/java/OWNERS | 2 + .../renderer_host/java/java_bound_object.cc | 938 +++++++++++++++++++++ .../browser/renderer_host/java/java_bound_object.h | 89 ++ .../renderer_host/java/java_bridge_channel_host.cc | 95 +++ .../renderer_host/java/java_bridge_channel_host.h | 52 ++ .../java/java_bridge_dispatcher_host.cc | 173 ++++ .../java/java_bridge_dispatcher_host.h | 75 ++ .../java/java_bridge_dispatcher_host_manager.cc | 156 ++++ .../java/java_bridge_dispatcher_host_manager.h | 70 ++ .../browser/renderer_host/java/java_method.cc | 237 ++++++ .../browser/renderer_host/java/java_method.h | 47 ++ .../browser/renderer_host/java/java_type.cc | 114 +++ .../content/browser/renderer_host/java/java_type.h | 50 ++ 14 files changed, 2102 insertions(+) create mode 100644 chromium/content/browser/renderer_host/java/DEPS create mode 100644 chromium/content/browser/renderer_host/java/OWNERS create mode 100644 chromium/content/browser/renderer_host/java/java_bound_object.cc create mode 100644 chromium/content/browser/renderer_host/java/java_bound_object.h create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_channel_host.h create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc create mode 100644 chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h create mode 100644 chromium/content/browser/renderer_host/java/java_method.cc create mode 100644 chromium/content/browser/renderer_host/java/java_method.h create mode 100644 chromium/content/browser/renderer_host/java/java_type.cc create mode 100644 chromium/content/browser/renderer_host/java/java_type.h (limited to 'chromium/content/browser/renderer_host/java') diff --git a/chromium/content/browser/renderer_host/java/DEPS b/chromium/content/browser/renderer_host/java/DEPS new file mode 100644 index 00000000000..05512435883 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/child", # For java bridge bindings + "+third_party/WebKit/public/web/WebBindings.h", # For java bridge bindings +] diff --git a/chromium/content/browser/renderer_host/java/OWNERS b/chromium/content/browser/renderer_host/java/OWNERS new file mode 100644 index 00000000000..4b297c43ee6 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/OWNERS @@ -0,0 +1,2 @@ +joth@chromium.org +steveblock@chromium.org diff --git a/chromium/content/browser/renderer_host/java/java_bound_object.cc b/chromium/content/browser/renderer_host/java/java_bound_object.cc new file mode 100644 index 00000000000..4199cca77b8 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bound_object.cc @@ -0,0 +1,938 @@ +// Copyright (c) 2012 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/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h" +#include "content/browser/renderer_host/java/java_type.h" +#include "content/public/browser/browser_thread.h" +#include "third_party/WebKit/public/web/WebBindings.h" + +using base::StringPrintf; +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::GetClass; +using base::android::GetMethodIDFromClassName; +using base::android::JavaRef; +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 content { +namespace { + +const char kJavaLangClass[] = "java/lang/Class"; +const char kJavaLangObject[] = "java/lang/Object"; +const char kJavaLangReflectMethod[] = "java/lang/reflect/Method"; +const char kGetClass[] = "getClass"; +const char kGetMethods[] = "getMethods"; +const char kIsAnnotationPresent[] = "isAnnotationPresent"; +const char kReturningJavaLangClass[] = "()Ljava/lang/Class;"; +const char kReturningJavaLangReflectMethodArray[] = + "()[Ljava/lang/reflect/Method;"; +const char kTakesJavaLangClassReturningBoolean[] = "(Ljava/lang/Class;)Z"; + +// 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(np_object); + delete obj->bound_object; + delete obj; +} + +bool JavaNPObject::HasMethod(NPObject* np_object, NPIdentifier np_identifier) { + std::string name(WebBindings::utf8FromIdentifier(np_identifier)); + JavaNPObject* obj = reinterpret_cast(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(WebBindings::utf8FromIdentifier(np_identifier)); + JavaNPObject* obj = reinterpret_cast(np_object); + return obj->bound_object->Invoke(name, args, arg_count, result); +} + +bool JavaNPObject::HasProperty(NPObject* np_object, + NPIdentifier np_identifier) { + // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate + // that the property is not present. Spec requires supporting this correctly. + return false; +} + +bool JavaNPObject::GetProperty(NPObject* np_object, + NPIdentifier np_identifier, + NPVariant* result) { + // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate + // that the property is undefined. Spec requires supporting this correctly. + return false; +} + +// Calls a Java method through JNI. If the Java method raises an uncaught +// exception, it is cleared and this method returns false. Otherwise, this +// method returns true and the Java method's return value is provided 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. +bool CallJNIMethod( + jobject object, + const JavaType& return_type, + jmethodID id, + jvalue* parameters, + NPVariant* result, + const JavaRef& safe_annotation_clazz, + const base::WeakPtr& manager) { + JNIEnv* env = AttachCurrentThread(); + switch (return_type.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: + // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that + // return arrays. Spec requires calling the method and converting the + // result to a JavaScript array. + VOID_TO_NPVARIANT(*result); + break; + case JavaType::TypeString: { + jstring java_string = static_cast( + env->CallObjectMethodA(object, id, parameters)); + // If an exception was raised, we must clear it before calling most JNI + // methods. ScopedJavaLocalRef is liable to make such calls, so we test + // first. + if (base::android::ClearException(env)) { + return false; + } + ScopedJavaLocalRef scoped_java_string(env, java_string); + if (!scoped_java_string.obj()) { + // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined. + // Spec requires returning a null string. + VOID_TO_NPVARIANT(*result); + break; + } + std::string str = + base::android::ConvertJavaStringToUTF8(scoped_java_string); + size_t length = str.length(); + // This pointer is freed in _NPN_ReleaseVariantValue in + // third_party/WebKit/Source/WebCore/bindings/v8/npruntime.cpp. + char* buffer = static_cast(malloc(length)); + str.copy(buffer, length, 0); + STRINGN_TO_NPVARIANT(buffer, length, *result); + break; + } + case JavaType::TypeObject: { + // If an exception was raised, we must clear it before calling most JNI + // methods. ScopedJavaLocalRef is liable to make such calls, so we test + // first. + jobject java_object = env->CallObjectMethodA(object, id, parameters); + if (base::android::ClearException(env)) { + return false; + } + ScopedJavaLocalRef scoped_java_object(env, java_object); + if (!scoped_java_object.obj()) { + NULL_TO_NPVARIANT(*result); + break; + } + OBJECT_TO_NPVARIANT(JavaBoundObject::Create(scoped_java_object, + safe_annotation_clazz, + manager), + *result); + break; + } + } + return !base::android::ClearException(env); +} + +double RoundDoubleTowardsZero(const double& x) { + if (std::isnan(x)) { + return 0.0; + } + return x > 0.0 ? floor(x) : ceil(x); +} + +// Rounds to jlong using Java's type conversion rules. +jlong RoundDoubleToLong(const double& x) { + double intermediate = RoundDoubleTowardsZero(x); + // The int64 limits can not be converted exactly to double values, so we + // compare to custom constants. kint64max is 2^63 - 1, but the spacing + // between double values in the the range 2^62 to 2^63 is 2^10. The cast is + // required to silence a spurious gcc warning for integer overflow. + const int64 limit = (GG_INT64_C(1) << 63) - static_cast(1 << 10); + DCHECK(limit > 0); + const double kLargestDoubleLessThanInt64Max = limit; + const double kSmallestDoubleGreaterThanInt64Min = -limit; + if (intermediate > kLargestDoubleLessThanInt64Max) { + return kint64max; + } + if (intermediate < kSmallestDoubleGreaterThanInt64Min) { + return kint64min; + } + return static_cast(intermediate); +} + +// Rounds to jint using Java's type conversion rules. +jint RoundDoubleToInt(const double& x) { + double intermediate = RoundDoubleTowardsZero(x); + // The int32 limits cast exactly to double values. + intermediate = std::min(intermediate, static_cast(kint32max)); + intermediate = std::max(intermediate, static_cast(kint32min)); + return static_cast(intermediate); +} + +jvalue CoerceJavaScriptNumberToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string) { + // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES. + + // For conversion to numeric types, we need to replicate Java's type + // conversion rules. This requires that for integer values, we simply discard + // all but the lowest n buts, where n is the number of bits in the target + // type. For double values, the logic is more involved. + jvalue result; + DCHECK(variant.type == NPVariantType_Int32 || + variant.type == NPVariantType_Double); + bool is_double = variant.type == NPVariantType_Double; + switch (target_type.type) { + case JavaType::TypeByte: + result.b = is_double ? + static_cast(RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant))) : + static_cast(NPVARIANT_TO_INT32(variant)); + break; + case JavaType::TypeChar: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0. + // Spec requires converting doubles similarly to how we convert doubles to + // other numeric types. + result.c = is_double ? 0 : + static_cast(NPVARIANT_TO_INT32(variant)); + break; + case JavaType::TypeShort: + result.s = is_double ? + static_cast(RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant))) : + static_cast(NPVARIANT_TO_INT32(variant)); + break; + case JavaType::TypeInt: + result.i = is_double ? RoundDoubleToInt(NPVARIANT_TO_DOUBLE(variant)) : + NPVARIANT_TO_INT32(variant); + break; + case JavaType::TypeLong: + result.j = is_double ? RoundDoubleToLong(NPVARIANT_TO_DOUBLE(variant)) : + NPVARIANT_TO_INT32(variant); + break; + case JavaType::TypeFloat: + result.f = is_double ? static_cast(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: Existing behavior is to convert to null. Spec + // requires handling object equivalents of primitive types. + result.l = NULL; + break; + case JavaType::TypeString: + result.l = coerce_to_string ? + ConvertUTF8ToJavaString( + AttachCurrentThread(), + is_double ? + base::StringPrintf("%.6lg", NPVARIANT_TO_DOUBLE(variant)) : + base::Int64ToString(NPVARIANT_TO_INT32(variant))).Release() : + NULL; + break; + case JavaType::TypeBoolean: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec + // requires converting to false for 0 or NaN, true otherwise. + result.z = JNI_FALSE; + break; + case JavaType::TypeArray: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec + // requires raising a JavaScript exception. + result.l = NULL; + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + NOTREACHED(); + break; + } + return result; +} + +jvalue CoerceJavaScriptBooleanToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string) { + // 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.type) { + case JavaType::TypeBoolean: + result.z = boolean_value ? JNI_TRUE : JNI_FALSE; + break; + case JavaType::TypeObject: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires handling java.lang.Boolean and java.lang.Object. + result.l = NULL; + break; + case JavaType::TypeString: + result.l = coerce_to_string ? + ConvertUTF8ToJavaString(AttachCurrentThread(), + boolean_value ? "true" : "false").Release() : + NULL; + break; + case JavaType::TypeByte: + case JavaType::TypeChar: + case JavaType::TypeShort: + case JavaType::TypeInt: + case JavaType::TypeLong: + case JavaType::TypeFloat: + case JavaType::TypeDouble: { + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec + // requires converting to 0 or 1. + jvalue null_value = {0}; + result = null_value; + break; + } + case JavaType::TypeArray: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires raising a JavaScript exception. + result.l = NULL; + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + NOTREACHED(); + break; + } + return result; +} + +jvalue CoerceJavaScriptStringToJavaValue(const NPVariant& variant, + const JavaType& target_type) { + // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES. + DCHECK_EQ(NPVariantType_String, variant.type); + jvalue result; + switch (target_type.type) { + case JavaType::TypeString: + result.l = ConvertUTF8ToJavaString( + AttachCurrentThread(), + base::StringPiece(NPVARIANT_TO_STRING(variant).UTF8Characters, + NPVARIANT_TO_STRING(variant).UTF8Length)).Release(); + break; + case JavaType::TypeObject: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires handling java.lang.Object. + result.l = NULL; + break; + case JavaType::TypeByte: + case JavaType::TypeShort: + case JavaType::TypeInt: + case JavaType::TypeLong: + case JavaType::TypeFloat: + case JavaType::TypeDouble: { + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec + // requires using valueOf() method of corresponding object type. + jvalue null_value = {0}; + result = null_value; + break; + } + case JavaType::TypeChar: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec + // requires using java.lang.Short.decode(). + result.c = 0; + break; + case JavaType::TypeBoolean: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec + // requires converting the empty string to false, otherwise true. + result.z = JNI_FALSE; + break; + case JavaType::TypeArray: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires raising a JavaScript exception. + result.l = NULL; + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + NOTREACHED(); + break; + } + return result; +} + +// Note that this only handles primitive types and strings. +jobject CreateJavaArray(const JavaType& type, jsize length) { + JNIEnv* env = AttachCurrentThread(); + switch (type.type) { + case JavaType::TypeBoolean: + return env->NewBooleanArray(length); + case JavaType::TypeByte: + return env->NewByteArray(length); + case JavaType::TypeChar: + return env->NewCharArray(length); + case JavaType::TypeShort: + return env->NewShortArray(length); + case JavaType::TypeInt: + return env->NewIntArray(length); + case JavaType::TypeLong: + return env->NewLongArray(length); + case JavaType::TypeFloat: + return env->NewFloatArray(length); + case JavaType::TypeDouble: + return env->NewDoubleArray(length); + case JavaType::TypeString: { + ScopedJavaLocalRef clazz(GetClass(env, "java/lang/String")); + return env->NewObjectArray(length, clazz.obj(), NULL); + } + case JavaType::TypeVoid: + // Conversion to void must never happen. + case JavaType::TypeArray: + case JavaType::TypeObject: + // Not handled. + NOTREACHED(); + } + return NULL; +} + +// Sets the specified element of the supplied array to the value of the +// supplied jvalue. Requires that the type of the array matches that of the +// jvalue. Handles only primitive types and strings. Note that in the case of a +// string, the array takes a new reference to the string object. +void SetArrayElement(jobject array, + const JavaType& type, + jsize index, + const jvalue& value) { + JNIEnv* env = AttachCurrentThread(); + switch (type.type) { + case JavaType::TypeBoolean: + env->SetBooleanArrayRegion(static_cast(array), index, 1, + &value.z); + break; + case JavaType::TypeByte: + env->SetByteArrayRegion(static_cast(array), index, 1, + &value.b); + break; + case JavaType::TypeChar: + env->SetCharArrayRegion(static_cast(array), index, 1, + &value.c); + break; + case JavaType::TypeShort: + env->SetShortArrayRegion(static_cast(array), index, 1, + &value.s); + break; + case JavaType::TypeInt: + env->SetIntArrayRegion(static_cast(array), index, 1, + &value.i); + break; + case JavaType::TypeLong: + env->SetLongArrayRegion(static_cast(array), index, 1, + &value.j); + break; + case JavaType::TypeFloat: + env->SetFloatArrayRegion(static_cast(array), index, 1, + &value.f); + break; + case JavaType::TypeDouble: + env->SetDoubleArrayRegion(static_cast(array), index, 1, + &value.d); + break; + case JavaType::TypeString: + env->SetObjectArrayElement(static_cast(array), index, + value.l); + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + case JavaType::TypeArray: + case JavaType::TypeObject: + // Not handled. + NOTREACHED(); + } + base::android::CheckException(env); +} + +void ReleaseJavaValueIfRequired(JNIEnv* env, + jvalue* value, + const JavaType& type) { + if (type.type == JavaType::TypeString || + type.type == JavaType::TypeObject || + type.type == JavaType::TypeArray) { + env->DeleteLocalRef(value->l); + value->l = NULL; + } +} + +jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string); + +// Returns a new local reference to a Java array. +jobject CoerceJavaScriptObjectToArray(const NPVariant& variant, + const JavaType& target_type) { + DCHECK_EQ(JavaType::TypeArray, target_type.type); + NPObject* object = NPVARIANT_TO_OBJECT(variant); + DCHECK_NE(&JavaNPObject::kNPClass, object->_class); + + const JavaType& target_inner_type = *target_type.inner_type.get(); + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for + // multi-dimensional arrays. Spec requires handling multi-demensional arrays. + if (target_inner_type.type == JavaType::TypeArray) { + return NULL; + } + + // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object + // arrays. Spec requires handling object arrays. + if (target_inner_type.type == JavaType::TypeObject) { + return NULL; + } + + // If the object does not have a length property, return null. + NPVariant length_variant; + if (!WebBindings::getProperty(0, object, + WebBindings::getStringIdentifier("length"), + &length_variant)) { + WebBindings::releaseVariantValue(&length_variant); + return NULL; + } + + // If the length property does not have numeric type, or is outside the valid + // range for a Java array length, return null. + jsize length = -1; + if (NPVARIANT_IS_INT32(length_variant) + && NPVARIANT_TO_INT32(length_variant) >= 0) { + length = NPVARIANT_TO_INT32(length_variant); + } else if (NPVARIANT_IS_DOUBLE(length_variant) + && NPVARIANT_TO_DOUBLE(length_variant) >= 0.0 + && NPVARIANT_TO_DOUBLE(length_variant) <= kint32max) { + length = static_cast(NPVARIANT_TO_DOUBLE(length_variant)); + } + WebBindings::releaseVariantValue(&length_variant); + if (length == -1) { + return NULL; + } + + // Create the Java array. + // TODO(steveblock): Handle failure to create the array. + jobject result = CreateJavaArray(target_inner_type, length); + NPVariant value_variant; + JNIEnv* env = AttachCurrentThread(); + for (jsize i = 0; i < length; ++i) { + // It seems that getProperty() will set the variant to type void on failure, + // but this doesn't seem to be documented, so do it explicitly here for + // safety. + VOID_TO_NPVARIANT(value_variant); + // If this fails, for example due to a missing element, we simply treat the + // value as JavaScript undefined. + WebBindings::getProperty(0, object, WebBindings::getIntIdentifier(i), + &value_variant); + jvalue element = CoerceJavaScriptValueToJavaValue(value_variant, + target_inner_type, + false); + SetArrayElement(result, target_inner_type, i, element); + // CoerceJavaScriptValueToJavaValue() creates new local references to + // strings, objects and arrays. Of these, only strings can occur here. + // SetArrayElement() causes the array to take its own reference to the + // string, so we can now release the local reference. + DCHECK_NE(JavaType::TypeObject, target_inner_type.type); + DCHECK_NE(JavaType::TypeArray, target_inner_type.type); + ReleaseJavaValueIfRequired(env, &element, target_inner_type); + WebBindings::releaseVariantValue(&value_variant); + } + + return result; +} + +jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string) { + // This covers both JavaScript objects (including arrays) and Java objects. + // See http://jdk6.java.net/plugin2/liveconnect/#JS_OTHER_OBJECTS, + // http://jdk6.java.net/plugin2/liveconnect/#JS_ARRAY_VALUES and + // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS + DCHECK_EQ(NPVariantType_Object, variant.type); + + NPObject* object = NPVARIANT_TO_OBJECT(variant); + bool is_java_object = &JavaNPObject::kNPClass == object->_class; + + jvalue result; + switch (target_type.type) { + case JavaType::TypeObject: + if (is_java_object) { + // LIVECONNECT_COMPLIANCE: Existing behavior is to pass all Java + // objects. Spec requires passing only Java objects which are + // assignment-compatibile. + result.l = AttachCurrentThread()->NewLocalRef( + JavaBoundObject::GetJavaObject(object).obj()); + } else { + // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec + // requires converting if the target type is + // netscape.javascript.JSObject, otherwise raising a JavaScript + // exception. + result.l = NULL; + } + break; + case JavaType::TypeString: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to + // "undefined". Spec requires calling toString() on the Java object. + result.l = coerce_to_string ? + ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined"). + Release() : + NULL; + break; + case JavaType::TypeByte: + case JavaType::TypeShort: + case JavaType::TypeInt: + case JavaType::TypeLong: + case JavaType::TypeFloat: + case JavaType::TypeDouble: + case JavaType::TypeChar: { + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec + // requires raising a JavaScript exception. + jvalue null_value = {0}; + result = null_value; + break; + } + case JavaType::TypeBoolean: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec + // requires raising a JavaScript exception. + result.z = JNI_FALSE; + break; + case JavaType::TypeArray: + if (is_java_object) { + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires raising a JavaScript exception. + result.l = NULL; + } else { + result.l = CoerceJavaScriptObjectToArray(variant, target_type); + } + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + NOTREACHED(); + break; + } + return result; +} + +jvalue CoerceJavaScriptNullOrUndefinedToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string) { + // See http://jdk6.java.net/plugin2/liveconnect/#JS_NULL. + DCHECK(variant.type == NPVariantType_Null || + variant.type == NPVariantType_Void); + jvalue result; + switch (target_type.type) { + case JavaType::TypeObject: + result.l = NULL; + break; + case JavaType::TypeString: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to + // "undefined". Spec requires converting undefined to NULL. + result.l = (coerce_to_string && variant.type == NPVariantType_Void) ? + ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined"). + Release() : + NULL; + break; + case JavaType::TypeByte: + case JavaType::TypeChar: + case JavaType::TypeShort: + case JavaType::TypeInt: + case JavaType::TypeLong: + case JavaType::TypeFloat: + case JavaType::TypeDouble: { + jvalue null_value = {0}; + result = null_value; + break; + } + case JavaType::TypeBoolean: + result.z = JNI_FALSE; + break; + case JavaType::TypeArray: + // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec + // requires raising a JavaScript exception. + result.l = NULL; + break; + case JavaType::TypeVoid: + // Conversion to void must never happen. + NOTREACHED(); + break; + } + return result; +} + +// coerce_to_string means that we should try to coerce all JavaScript values to +// strings when required, rather than simply converting to NULL. This is used +// to maintain current behaviour, which differs slightly depending upon whether +// or not the coercion in question is for an array element. +// +// Note that the jvalue returned by this method may contain a new local +// reference to an object (string, object or array). This must be released by +// the caller. +jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant, + const JavaType& target_type, + bool coerce_to_string) { + // Note that in all these conversions, the relevant field of the jvalue must + // always be explicitly set, as jvalue does not initialize its fields. + + switch (variant.type) { + case NPVariantType_Int32: + case NPVariantType_Double: + return CoerceJavaScriptNumberToJavaValue(variant, target_type, + coerce_to_string); + case NPVariantType_Bool: + return CoerceJavaScriptBooleanToJavaValue(variant, target_type, + coerce_to_string); + case NPVariantType_String: + return CoerceJavaScriptStringToJavaValue(variant, target_type); + case NPVariantType_Object: + return CoerceJavaScriptObjectToJavaValue(variant, target_type, + coerce_to_string); + case NPVariantType_Null: + case NPVariantType_Void: + return CoerceJavaScriptNullOrUndefinedToJavaValue(variant, target_type, + coerce_to_string); + } + NOTREACHED(); + return jvalue(); +} + +} // namespace + +NPObject* JavaBoundObject::Create( + const JavaRef& object, + const JavaRef& safe_annotation_clazz, + const base::WeakPtr& manager) { + // 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( + &JavaNPObject::kNPClass)); + // The NPObject takes ownership of the JavaBoundObject. + reinterpret_cast(np_object)->bound_object = + new JavaBoundObject(object, safe_annotation_clazz, manager); + return np_object; +} + +JavaBoundObject::JavaBoundObject( + const JavaRef& object, + const JavaRef& safe_annotation_clazz, + const base::WeakPtr& manager) + : java_object_(AttachCurrentThread(), object.obj()), + manager_(manager), + are_methods_set_up_(false), + safe_annotation_clazz_(safe_annotation_clazz) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectCreated, + manager_, + base::android::ScopedJavaGlobalRef(object))); + // Other than informing the JavaBridgeDispatcherHostManager that a java bound + // object has been created (above), we don't do anything else with our Java + // object when first created. We do it all lazily when a method is first + // invoked. +} + +JavaBoundObject::~JavaBoundObject() { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed, + manager_, + base::android::ScopedJavaGlobalRef( + java_object_.get(AttachCurrentThread())))); +} + +ScopedJavaLocalRef JavaBoundObject::GetJavaObject(NPObject* object) { + DCHECK_EQ(&JavaNPObject::kNPClass, object->_class); + JavaBoundObject* jbo = reinterpret_cast(object)->bound_object; + return jbo->java_object_.get(AttachCurrentThread()); +} + +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 + 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 parameters(arg_count); + for (size_t i = 0; i < arg_count; ++i) { + parameters[i] = CoerceJavaScriptValueToJavaValue(args[i], + method->parameter_type(i), + true); + } + + ScopedJavaLocalRef obj = java_object_.get(AttachCurrentThread()); + + bool ok = false; + if (!obj.is_null()) { + // Call + ok = CallJNIMethod(obj.obj(), method->return_type(), + method->id(), ¶meters[0], result, + safe_annotation_clazz_, + manager_); + } + + // Now that we're done with the jvalue, release any local references created + // by CoerceJavaScriptValueToJavaValue(). + JNIEnv* env = AttachCurrentThread(); + for (size_t i = 0; i < arg_count; ++i) { + ReleaseJavaValueIfRequired(env, ¶meters[i], method->parameter_type(i)); + } + + return ok; +} + +void JavaBoundObject::EnsureMethodsAreSetUp() const { + if (are_methods_set_up_) + return; + are_methods_set_up_ = true; + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = java_object_.get(env); + + if (obj.is_null()) { + return; + } + + ScopedJavaLocalRef clazz(env, static_cast( + env->CallObjectMethod(obj.obj(), GetMethodIDFromClassName( + env, + kJavaLangObject, + kGetClass, + kReturningJavaLangClass)))); + + ScopedJavaLocalRef methods(env, static_cast( + env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName( + env, + kJavaLangClass, + kGetMethods, + kReturningJavaLangReflectMethodArray)))); + + size_t num_methods = env->GetArrayLength(methods.obj()); + // Java objects always have public methods. + DCHECK(num_methods); + + for (size_t i = 0; i < num_methods; ++i) { + ScopedJavaLocalRef java_method( + env, + env->GetObjectArrayElement(methods.obj(), i)); + + if (!safe_annotation_clazz_.is_null()) { + jboolean safe = env->CallBooleanMethod(java_method.obj(), + GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kIsAnnotationPresent, + kTakesJavaLangClassReturningBoolean), + safe_annotation_clazz_.obj()); + + if (!safe) + continue; + } + + JavaMethod* method = new JavaMethod(java_method); + methods_.insert(std::make_pair(method->name(), method)); + } +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_bound_object.h b/chromium/content/browser/renderer_host/java/java_bound_object.h new file mode 100644 index 00000000000..ff97fdb2b6c --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bound_object.h @@ -0,0 +1,89 @@ +// Copyright (c) 2012 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 +#include +#include + +#include "base/android/jni_helper.h" +#include "base/android/scoped_java_ref.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/weak_ptr.h" +#include "content/browser/renderer_host/java/java_method.h" +#include "third_party/npapi/bindings/npruntime.h" + +namespace content { + +class JavaBridgeDispatcherHostManager; + +// 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. The + // |require_annotation| flag specifies whether or not only methods with the + // JavascriptInterface annotation are exposed to JavaScript. This property + // propagates to all Objects that get implicitly exposed as return values as + // well. Returns an NPObject with a ref count of one which owns the + // JavaBoundObject. + // See also comment below for |manager_|. + static NPObject* Create( + const base::android::JavaRef& object, + const base::android::JavaRef& safe_annotation_clazz, + const base::WeakPtr& manager); + + virtual ~JavaBoundObject(); + + // Gets a local ref to the underlying JavaObject from a JavaBoundObject + // wrapped as an NPObject. May return null if the underlying object has + // been garbage collected. + static base::android::ScopedJavaLocalRef 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& object, + const base::android::JavaRef& safe_annotation_clazz, + const base::WeakPtr& manager_); + + void EnsureMethodsAreSetUp() const; + + // The weak ref to the underlying Java object that this JavaBoundObject + // instance represents. + JavaObjectWeakGlobalRef java_object_; + + // Keep a pointer back to the JavaBridgeDispatcherHostManager so that we + // can notify it when this JavaBoundObject is destroyed. JavaBoundObjects + // may outlive the manager so keep a WeakPtr. Note the WeakPtr may only be + // dereferenced on the UI thread. + base::WeakPtr manager_; + + // 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 > JavaMethodMap; + mutable JavaMethodMap methods_; + mutable bool are_methods_set_up_; + + base::android::ScopedJavaGlobalRef safe_annotation_clazz_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(JavaBoundObject); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BOUND_OBJECT_H_ diff --git a/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc new file mode 100644 index 00000000000..8f53b9d99a8 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.cc @@ -0,0 +1,95 @@ +// 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_bridge_channel_host.h" + +#include "base/atomicops.h" +#include "base/lazy_instance.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/waitable_event.h" +#include "content/common/java_bridge_messages.h" + +using base::WaitableEvent; + +namespace content { +namespace { +struct WaitableEventLazyInstanceTraits + : public base::DefaultLazyInstanceTraits { + static WaitableEvent* New(void* instance) { + // Use placement new to initialize our instance in our preallocated space. + // The parenthesis is very important here to force POD type initialization. + return new (instance) WaitableEvent(false, false); + } +}; +base::LazyInstance dummy_event = + LAZY_INSTANCE_INITIALIZER; + +base::subtle::AtomicWord g_last_id = 0; +} + +JavaBridgeChannelHost::~JavaBridgeChannelHost() { +#if defined(OS_POSIX) + if (channel_handle_.socket.fd > 0) { + close(channel_handle_.socket.fd); + } +#endif +} + +JavaBridgeChannelHost* JavaBridgeChannelHost::GetJavaBridgeChannelHost( + int renderer_id, + base::MessageLoopProxy* ipc_message_loop) { + std::string channel_name(base::StringPrintf("r%d.javabridge", renderer_id)); + // There's no need for a shutdown event here. If the browser is terminated + // while the JavaBridgeChannelHost is blocked on a synchronous IPC call, the + // renderer's shutdown event will cause the underlying channel to shut down, + // thus terminating the IPC call. + return static_cast(NPChannelBase::GetChannel( + channel_name, + IPC::Channel::MODE_SERVER, + ClassFactory, + ipc_message_loop, + true, + dummy_event.Pointer())); +} + +int JavaBridgeChannelHost::ThreadsafeGenerateRouteID() { + return base::subtle::NoBarrier_AtomicIncrement(&g_last_id, 1); +} + +int JavaBridgeChannelHost::GenerateRouteID() { + return ThreadsafeGenerateRouteID(); +} + +bool JavaBridgeChannelHost::Init(base::MessageLoopProxy* ipc_message_loop, + bool create_pipe_now, + WaitableEvent* shutdown_event) { + if (!NPChannelBase::Init(ipc_message_loop, create_pipe_now, shutdown_event)) { + return false; + } + + // Finish populating our ChannelHandle. +#if defined(OS_POSIX) + // We take control of the FD for all session between this host and + // the corresponding renderers. We keep it open until this object + // is deleted. + channel_handle_.socket.fd = channel_->TakeClientFileDescriptor(); +#endif + + return true; +} + +bool JavaBridgeChannelHost::OnControlMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(JavaBridgeChannelHost, message) + IPC_MESSAGE_HANDLER(JavaBridgeMsg_GenerateRouteID, OnGenerateRouteID) + IPC_END_MESSAGE_MAP() + return handled; +} + +void JavaBridgeChannelHost::OnGenerateRouteID(int* route_id) { + *route_id = GenerateRouteID(); +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h new file mode 100644 index 00000000000..3f3f1468e9b --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_channel_host.h @@ -0,0 +1,52 @@ +// Copyright (c) 2012 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_BRIDGE_CHANNEL_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_CHANNEL_HOST_H_ + +#include "content/child/npapi/np_channel_base.h" + +namespace content { + +class JavaBridgeChannelHost : public NPChannelBase { + public: + static JavaBridgeChannelHost* GetJavaBridgeChannelHost( + int renderer_id, + base::MessageLoopProxy* ipc_message_loop); + + // A threadsafe function to generate a unique route ID. Used by the + // JavaBridgeDispatcherHost on the UI thread and this class on the Java + // Bridge's background thread. + static int ThreadsafeGenerateRouteID(); + + // NPChannelBase implementation: + virtual int GenerateRouteID() OVERRIDE; + + // NPChannelBase override: + virtual bool Init(base::MessageLoopProxy* ipc_message_loop, + bool create_pipe_now, + base::WaitableEvent* shutdown_event) OVERRIDE; + + protected: + // NPChannelBase override: + virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + JavaBridgeChannelHost() {} + friend class base::RefCountedThreadSafe; + virtual ~JavaBridgeChannelHost(); + + static NPChannelBase* ClassFactory() { + return new JavaBridgeChannelHost(); + } + + // Message handlers + void OnGenerateRouteID(int* route_id); + + DISALLOW_COPY_AND_ASSIGN(JavaBridgeChannelHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_CHANNEL_HOST_H_ diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc new file mode 100644 index 00000000000..95a1c6aefe4 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.cc @@ -0,0 +1,173 @@ +// Copyright (c) 2012 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_bridge_dispatcher_host.h" + +#include "base/android/java_handler_thread.h" +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "content/browser/renderer_host/java/java_bridge_channel_host.h" +#include "content/browser/renderer_host/render_view_host_impl.h" +#include "content/child/child_process.h" +#include "content/child/npapi/npobject_stub.h" +#include "content/child/npapi/npobject_util.h" // For CreateNPVariantParam() +#include "content/common/java_bridge_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "third_party/WebKit/public/web/WebBindings.h" + +#if !defined(OS_ANDROID) +#error "JavaBridge currently only supports OS_ANDROID" +#endif + +namespace content { + +namespace { +// The JavaBridge needs to use a Java thread so the callback +// will happen on a thread with a prepared Looper. +class JavaBridgeThread : public base::android::JavaHandlerThread { + public: + JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") { + Start(); + } + virtual ~JavaBridgeThread() { + Stop(); + } +}; + +void CleanUpStubs(const std::vector > & stubs) { + for (size_t i = 0; i < stubs.size(); ++i) { + if (stubs[i]) { + stubs[i]->DeleteSoon(); + } + } +} + +base::LazyInstance g_background_thread = + LAZY_INSTANCE_INITIALIZER; +} // namespace + +JavaBridgeDispatcherHost::JavaBridgeDispatcherHost( + RenderViewHost* render_view_host) + : RenderViewHostObserver(render_view_host), + is_renderer_initialized_(false) { +} + +JavaBridgeDispatcherHost::~JavaBridgeDispatcherHost() { + g_background_thread.Get().message_loop()->PostTask( + FROM_HERE, + base::Bind(&CleanUpStubs, stubs_)); +} + +void JavaBridgeDispatcherHost::AddNamedObject(const string16& name, + NPObject* object) { + NPVariant_Param variant_param; + CreateNPVariantParam(object, &variant_param); + + if (!is_renderer_initialized_) { + is_renderer_initialized_ = true; + Send(new JavaBridgeMsg_Init(routing_id())); + } + Send(new JavaBridgeMsg_AddNamedObject(routing_id(), name, variant_param)); +} + +void JavaBridgeDispatcherHost::RemoveNamedObject(const string16& name) { + // On receipt of this message, the JavaBridgeDispatcher will drop its + // reference to the corresponding proxy object. When the last reference is + // removed, the proxy object will delete its NPObjectProxy, which will cause + // the NPObjectStub to be deleted, which will drop its reference to the + // original NPObject. + Send(new JavaBridgeMsg_RemoveNamedObject(routing_id(), name)); +} + +bool JavaBridgeDispatcherHost::Send(IPC::Message* msg) { + return RenderViewHostObserver::Send(msg); +} + +void JavaBridgeDispatcherHost::RenderViewHostDestroyed( + RenderViewHost* render_view_host) { + // Base implementation deletes the object. This class is ref counted, with + // refs held by the JavaBridgeDispatcherHostManager and base::Bind, so that + // behavior is unwanted. +} + +bool JavaBridgeDispatcherHost::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(JavaBridgeDispatcherHost, msg) + IPC_MESSAGE_HANDLER_DELAY_REPLY(JavaBridgeHostMsg_GetChannelHandle, + OnGetChannelHandle) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void JavaBridgeDispatcherHost::OnGetChannelHandle(IPC::Message* reply_msg) { + g_background_thread.Get().message_loop()->PostTask( + FROM_HERE, + base::Bind(&JavaBridgeDispatcherHost::GetChannelHandle, this, reply_msg)); +} + +void JavaBridgeDispatcherHost::GetChannelHandle(IPC::Message* reply_msg) { + // The channel creates the channel handle based on the renderer ID we passed + // to GetJavaBridgeChannelHost() and, on POSIX, the file descriptor used by + // the underlying channel. + JavaBridgeHostMsg_GetChannelHandle::WriteReplyParams( + reply_msg, + channel_->channel_handle()); + Send(reply_msg); +} + +void JavaBridgeDispatcherHost::CreateNPVariantParam(NPObject* object, + NPVariant_Param* param) { + // The JavaBridgeChannelHost needs to be created on the background thread, as + // that is where Java objects will live, and CreateNPVariantParam() needs the + // channel to create the NPObjectStub. To avoid blocking here until the + // channel is ready, create the NPVariant_Param by hand, then post a message + // to the background thread to set up the channel and create the corresponding + // NPObjectStub. Post that message before doing any IPC, to make sure that + // the channel and object proxies are ready before responses are received + // from the renderer. + + // Create an NPVariantParam suitable for serialization over IPC from our + // NPVariant. See CreateNPVariantParam() in npobject_utils. + param->type = NPVARIANT_PARAM_SENDER_OBJECT_ROUTING_ID; + int route_id = JavaBridgeChannelHost::ThreadsafeGenerateRouteID(); + param->npobject_routing_id = route_id; + + WebKit::WebBindings::retainObject(object); + g_background_thread.Get().message_loop()->PostTask( + FROM_HERE, + base::Bind(&JavaBridgeDispatcherHost::CreateObjectStub, this, object, + route_id)); +} + +void JavaBridgeDispatcherHost::CreateObjectStub(NPObject* object, + int route_id) { + DCHECK_EQ(g_background_thread.Get().message_loop(), + base::MessageLoop::current()); + if (!channel_.get()) { + channel_ = JavaBridgeChannelHost::GetJavaBridgeChannelHost( + render_view_host()->GetProcess()->GetID(), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)); + } + + // In a typical scenario, the lifetime of each NPObjectStub is governed by + // that of the NPObjectProxy in the renderer, via the channel. However, + // we cannot guaranteed that the renderer always terminates cleanly + // (crashes / sometimes just unavoidable). We keep a weak reference to + // it now and schedule a delete on it when this host is getting deleted. + + // Pass 0 for the containing window, as it's only used by plugins to pump the + // window message queue when a method on a renderer-side object causes a + // dialog to be displayed, and the Java Bridge does not need this + // functionality. The page URL is also not required. + stubs_.push_back((new NPObjectStub( + object, channel_.get(), route_id, 0, GURL()))->AsWeakPtr()); + + // The NPObjectStub takes a reference to the NPObject. Release the ref added + // in CreateNPVariantParam(). + WebKit::WebBindings::releaseObject(object); +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h new file mode 100644 index 00000000000..6d44c39d22a --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012 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_BRIDGE_DISPATCHER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_H_ + +#include +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "content/child/npapi/npobject_stub.h" +#include "content/public/browser/render_view_host_observer.h" + +class RouteIDGenerator; +struct NPObject; + +namespace content { +class NPChannelBase; +class RenderViewHost; +struct NPVariant_Param; + +// This class handles injecting Java objects into a single RenderView. The Java +// object itself lives in the browser process on a background thread, while a +// proxy object is created in the renderer. An instance of this class exists +// for each RenderViewHost. +class JavaBridgeDispatcherHost + : public base::RefCountedThreadSafe, + public RenderViewHostObserver { + public: + // We hold a weak pointer to the RenderViewhost. It must outlive this object. + JavaBridgeDispatcherHost(RenderViewHost* render_view_host); + + // Injects |object| into the main frame of the corresponding RenderView. A + // proxy object is created in the renderer and when the main frame's window + // object is next cleared, this proxy object is bound to the window object + // using |name|. The proxy object remains bound until the next time the + // window object is cleared after a call to RemoveNamedObject() or + // AddNamedObject() with the same name. The proxy object proxies calls back + // to |object|, which is manipulated on the background thread. This class + // holds a reference to |object| for the time that the proxy object is bound + // to the window object. + void AddNamedObject(const string16& name, NPObject* object); + void RemoveNamedObject(const string16& name); + + // RenderViewHostObserver overrides: + // The IPC macros require this to be public. + virtual bool Send(IPC::Message* msg) OVERRIDE; + virtual void RenderViewHostDestroyed( + RenderViewHost* render_view_host) OVERRIDE; + + private: + friend class base::RefCountedThreadSafe; + virtual ~JavaBridgeDispatcherHost(); + + // RenderViewHostObserver override: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // Message handlers + void OnGetChannelHandle(IPC::Message* reply_msg); + + void GetChannelHandle(IPC::Message* reply_msg); + void CreateNPVariantParam(NPObject* object, NPVariant_Param* param); + void CreateObjectStub(NPObject* object, int route_id); + + scoped_refptr channel_; + bool is_renderer_initialized_; + std::vector > stubs_; + + DISALLOW_COPY_AND_ASSIGN(JavaBridgeDispatcherHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_H_ diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc new file mode 100644 index 00000000000..bac38564e79 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2012 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_bridge_dispatcher_host_manager.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_helper.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "content/browser/renderer_host/java/java_bound_object.h" +#include "content/browser/renderer_host/java/java_bridge_dispatcher_host.h" +#include "content/common/android/hash_set.h" +#include "content/public/browser/browser_thread.h" +#include "third_party/WebKit/public/web/WebBindings.h" + +namespace content { + +JavaBridgeDispatcherHostManager::JavaBridgeDispatcherHostManager( + WebContents* web_contents) + : WebContentsObserver(web_contents) { +} + +JavaBridgeDispatcherHostManager::~JavaBridgeDispatcherHostManager() { + for (ObjectMap::iterator iter = objects_.begin(); iter != objects_.end(); + ++iter) { + WebKit::WebBindings::releaseObject(iter->second); + } + DCHECK_EQ(0U, instances_.size()); +} + +void JavaBridgeDispatcherHostManager::AddNamedObject(const string16& name, + NPObject* object) { + // Record this object in a map so that we can add it into RenderViewHosts + // created later. The JavaBridgeDispatcherHost instances will take a + // reference to the object, but we take one too, because this method can be + // called before there are any such instances. + WebKit::WebBindings::retainObject(object); + objects_[name] = object; + + for (InstanceMap::iterator iter = instances_.begin(); + iter != instances_.end(); ++iter) { + iter->second->AddNamedObject(name, object); + } +} + +void JavaBridgeDispatcherHostManager::SetRetainedObjectSet( + const JavaObjectWeakGlobalRef& retained_object_set) { + // It's an error to replace the retained_object_set_ after it's been set, + // so we check that it hasn't already been here. + // TODO(benm): It'd be better to pass the set in the constructor to avoid + // the chance of this happening; but that's tricky as this get's constructed + // before ContentViewCore (which owns the set). Best solution may be to move + // ownership of the JavaBridgerDispatchHostManager from WebContents to + // ContentViewCore? + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef new_retained_object_set = + retained_object_set.get(env); + base::android::ScopedJavaLocalRef current_retained_object_set = + retained_object_set_.get(env); + if (!env->IsSameObject(new_retained_object_set.obj(), + current_retained_object_set.obj())) { + DCHECK(current_retained_object_set.is_null()); + retained_object_set_ = retained_object_set; + } +} + +void JavaBridgeDispatcherHostManager::RemoveNamedObject(const string16& name) { + ObjectMap::iterator iter = objects_.find(name); + if (iter == objects_.end()) { + return; + } + + WebKit::WebBindings::releaseObject(iter->second); + objects_.erase(iter); + + for (InstanceMap::iterator iter = instances_.begin(); + iter != instances_.end(); ++iter) { + iter->second->RemoveNamedObject(name); + } +} + +void JavaBridgeDispatcherHostManager::RenderViewCreated( + RenderViewHost* render_view_host) { + // Creates a JavaBridgeDispatcherHost for the specified RenderViewHost and + // adds all currently registered named objects to the new instance. + scoped_refptr instance = + new JavaBridgeDispatcherHost(render_view_host); + + for (ObjectMap::const_iterator iter = objects_.begin(); + iter != objects_.end(); ++iter) { + instance->AddNamedObject(iter->first, iter->second); + } + + instances_[render_view_host] = instance; +} + +void JavaBridgeDispatcherHostManager::RenderViewDeleted( + RenderViewHost* render_view_host) { + instances_.erase(render_view_host); +} + +void JavaBridgeDispatcherHostManager::WebContentsDestroyed( + WebContents* web_contents) { + // When a WebContents is shutting down, it clears its observers before + // it kills all of its RenderViewHosts, so we won't get a call to + // RenderViewDeleted() for all RenderViewHosts. + instances_.clear(); +} + +void JavaBridgeDispatcherHostManager::DocumentAvailableInMainFrame() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // Called when the window object has been cleared in the main frame. + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_clear(env, retained_object_set); + + // We also need to add back the named objects we have so far as they + // should survive navigations. + ObjectMap::iterator it = objects_.begin(); + for (; it != objects_.end(); ++it) { + JNI_Java_HashSet_add(env, retained_object_set, + JavaBoundObject::GetJavaObject(it->second)); + } + } +} + +void JavaBridgeDispatcherHostManager::JavaBoundObjectCreated( + const base::android::JavaRef& object) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_add(env, retained_object_set, object); + } +} + +void JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed( + const base::android::JavaRef& object) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef retained_object_set = + retained_object_set_.get(env); + if (!retained_object_set.is_null()) { + JNI_Java_HashSet_remove(env, retained_object_set, object); + } +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h new file mode 100644 index 00000000000..6fb7aab7cfb --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 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_BRIDGE_DISPATCHER_HOST_MANAGER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_MANAGER_H_ + +#include + +#include "base/android/jni_helper.h" +#include "base/android/scoped_java_ref.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "content/public/browser/web_contents_observer.h" + +struct NPObject; + +namespace content { +class JavaBridgeDispatcherHost; +class RenderViewHost; + +// This class handles injecting Java objects into all of the RenderViews +// associated with a WebContents. It manages a set of JavaBridgeDispatcherHost +// objects, one per RenderViewHost. +class JavaBridgeDispatcherHostManager + : public WebContentsObserver, + public base::SupportsWeakPtr { + public: + explicit JavaBridgeDispatcherHostManager(WebContents* web_contents); + virtual ~JavaBridgeDispatcherHostManager(); + + // These methods add or remove the object to each JavaBridgeDispatcherHost. + // Each one holds a reference to the NPObject while the object is bound to + // the corresponding RenderView. See JavaBridgeDispatcherHost for details. + void AddNamedObject(const string16& name, NPObject* object); + void RemoveNamedObject(const string16& name); + + // Every time a JavaBoundObject backed by a real Java object is + // created/destroyed, we insert/remove a strong ref to that Java object into + // this set so that it doesn't get garbage collected while it's still + // potentially in use. Although the set is managed native side, it's owned + // and defined in Java so that pushing refs into it does not create new GC + // roots that would prevent ContentViewCore from being garbage collected. + void SetRetainedObjectSet(const JavaObjectWeakGlobalRef& retained_object_set); + + // WebContentsObserver overrides + virtual void RenderViewCreated(RenderViewHost* render_view_host) OVERRIDE; + virtual void RenderViewDeleted(RenderViewHost* render_view_host) OVERRIDE; + virtual void WebContentsDestroyed(WebContents* web_contents) OVERRIDE; + virtual void DocumentAvailableInMainFrame() OVERRIDE; + + void JavaBoundObjectCreated(const base::android::JavaRef& object); + void JavaBoundObjectDestroyed(const base::android::JavaRef& object); + + private: + typedef std::map > + InstanceMap; + InstanceMap instances_; + typedef std::map ObjectMap; + ObjectMap objects_; + JavaObjectWeakGlobalRef retained_object_set_; + + DISALLOW_COPY_AND_ASSIGN(JavaBridgeDispatcherHostManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_BRIDGE_DISPATCHER_HOST_MANAGER_H_ diff --git a/chromium/content/browser/renderer_host/java/java_method.cc b/chromium/content/browser/renderer_host/java/java_method.cc new file mode 100644 index 00000000000..3cc569fbcae --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_method.cc @@ -0,0 +1,237 @@ +// Copyright (c) 2012 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/lazy_instance.h" +#include "base/memory/singleton.h" +#include "base/strings/string_util.h" // For ReplaceSubstringsAfterOffset + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF8; +using base::android::GetClass; +using base::android::GetMethodIDFromClassName; +using base::android::MethodID; +using base::android::ScopedJavaGlobalRef; +using base::android::ScopedJavaLocalRef; + +namespace content { +namespace { + +const char kGetName[] = "getName"; +const char kGetDeclaringClass[] = "getDeclaringClass"; +const char kGetModifiers[] = "getModifiers"; +const char kGetParameterTypes[] = "getParameterTypes"; +const char kGetReturnType[] = "getReturnType"; +const char kIntegerReturningBoolean[] = "(I)Z"; +const char kIsStatic[] = "isStatic"; +const char kJavaLangClass[] = "java/lang/Class"; +const char kJavaLangReflectMethod[] = "java/lang/reflect/Method"; +const char kJavaLangReflectModifier[] = "java/lang/reflect/Modifier"; +const char kReturningInteger[] = "()I"; +const char kReturningJavaLangClass[] = "()Ljava/lang/Class;"; +const char kReturningJavaLangClassArray[] = "()[Ljava/lang/Class;"; +const char kReturningJavaLangString[] = "()Ljava/lang/String;"; + +struct ModifierClassTraits : + public base::internal::LeakyLazyInstanceTraits > { + static ScopedJavaGlobalRef* New(void* instance) { + JNIEnv* env = AttachCurrentThread(); + // Use placement new to initialize our instance in our preallocated space. + return new (instance) ScopedJavaGlobalRef( + GetClass(env, kJavaLangReflectModifier)); + } +}; + +base::LazyInstance, ModifierClassTraits> + g_java_lang_reflect_modifier_class = LAZY_INSTANCE_INITIALIZER; + +std::string BinaryNameToJNIName(const std::string& binary_name, + JavaType* type) { + DCHECK(type); + *type = JavaType::CreateFromBinaryName(binary_name); + switch (type->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: { + // For array types, the binary name uses the JNI name encodings. + std::string jni_name = binary_name; + ReplaceSubstringsAfterOffset(&jni_name, 0, ".", "/"); + return jni_name; + } + case JavaType::TypeString: + case JavaType::TypeObject: + std::string jni_name = "L" + binary_name + ";"; + ReplaceSubstringsAfterOffset(&jni_name, 0, ".", "/"); + return jni_name; + } + NOTREACHED(); + return EmptyString(); +} + +} // namespace + +JavaMethod::JavaMethod(const base::android::JavaRef& method) + : java_method_(method), + have_calculated_num_parameters_(false), + id_(NULL) { + JNIEnv* env = AttachCurrentThread(); + // On construction, we do nothing except get the name. Everything else is + // done lazily. + ScopedJavaLocalRef name(env, static_cast( + env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kGetName, + kReturningJavaLangString)))); + name_ = ConvertJavaStringToUTF8(name); +} + +JavaMethod::~JavaMethod() { +} + +size_t JavaMethod::num_parameters() const { + EnsureNumParametersIsSetUp(); + return num_parameters_; +} + +const JavaType& JavaMethod::parameter_type(size_t index) const { + EnsureTypesAndIDAreSetUp(); + return parameter_types_[index]; +} + +const JavaType& 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 = AttachCurrentThread(); + ScopedJavaLocalRef parameters(env, static_cast( + env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kGetParameterTypes, + kReturningJavaLangClassArray)))); + num_parameters_ = env->GetArrayLength(parameters.obj()); +} + +void JavaMethod::EnsureTypesAndIDAreSetUp() const { + if (id_) { + return; + } + + // Get the parameters + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef parameters(env, static_cast( + env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kGetParameterTypes, + kReturningJavaLangClassArray)))); + // Usually, this will already have been called. + EnsureNumParametersIsSetUp(); + DCHECK_EQ(num_parameters_, + static_cast(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 parameter(env, env->GetObjectArrayElement( + parameters.obj(), i)); + ScopedJavaLocalRef name(env, static_cast( + env->CallObjectMethod(parameter.obj(), GetMethodIDFromClassName( + env, + kJavaLangClass, + kGetName, + kReturningJavaLangString)))); + std::string name_utf8 = ConvertJavaStringToUTF8(name); + signature += BinaryNameToJNIName(name_utf8, ¶meter_types_[i]); + } + signature += ")"; + + // Get the return type + ScopedJavaLocalRef clazz(env, static_cast( + env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kGetReturnType, + kReturningJavaLangClass)))); + ScopedJavaLocalRef name(env, static_cast( + env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName( + env, + kJavaLangClass, + kGetName, + kReturningJavaLangString)))); + signature += BinaryNameToJNIName(ConvertJavaStringToUTF8(name), + &return_type_); + + // Determine whether the method is static. + jint modifiers = env->CallIntMethod( + java_method_.obj(), GetMethodIDFromClassName(env, + kJavaLangReflectMethod, + kGetModifiers, + kReturningInteger)); + bool is_static = env->CallStaticBooleanMethod( + g_java_lang_reflect_modifier_class.Get().obj(), + MethodID::Get( + env, g_java_lang_reflect_modifier_class.Get().obj(), kIsStatic, + kIntegerReturningBoolean), + modifiers); + + // Get the ID for this method. + ScopedJavaLocalRef declaring_class(env, static_cast( + env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName( + env, + kJavaLangReflectMethod, + kGetDeclaringClass, + kReturningJavaLangClass)))); + id_ = is_static ? + MethodID::Get( + env, declaring_class.obj(), name_.c_str(), signature.c_str()) : + MethodID::Get( + env, declaring_class.obj(), name_.c_str(), signature.c_str()); + java_method_.Reset(); +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_method.h b/chromium/content/browser/renderer_host/java/java_method.h new file mode 100644 index 00000000000..6356f70069d --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_method.h @@ -0,0 +1,47 @@ +// 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 +#include +#include + +#include "base/android/scoped_java_ref.h" +#include "content/browser/renderer_host/java/java_type.h" + +namespace content { + +// 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& method); + ~JavaMethod(); + + const std::string& name() const { return name_; } + size_t num_parameters() const; + const JavaType& parameter_type(size_t index) const; + const JavaType& return_type() const; + jmethodID id() const; + + private: + void EnsureNumParametersIsSetUp() const; + void EnsureTypesAndIDAreSetUp() const; + + std::string name_; + mutable base::android::ScopedJavaGlobalRef java_method_; + mutable bool have_calculated_num_parameters_; + mutable size_t num_parameters_; + mutable std::vector parameter_types_; + mutable JavaType return_type_; + mutable jmethodID id_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(JavaMethod); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_METHOD_H_ diff --git a/chromium/content/browser/renderer_host/java/java_type.cc b/chromium/content/browser/renderer_host/java/java_type.cc new file mode 100644 index 00000000000..b590e7733c0 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_type.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2012 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_type.h" + +#include "base/logging.h" + +namespace content { +namespace { + +JavaType JavaTypeFromJNIName(const std::string& jni_name) { + JavaType result; + DCHECK(!jni_name.empty()); + switch (jni_name[0]) { + case 'Z': + result.type = JavaType::TypeBoolean; + break; + case 'B': + result.type = JavaType::TypeByte; + break; + case 'C': + result.type = JavaType::TypeChar; + break; + case 'S': + result.type = JavaType::TypeShort; + break; + case 'I': + result.type = JavaType::TypeInt; + break; + case 'J': + result.type = JavaType::TypeLong; + break; + case 'F': + result.type = JavaType::TypeFloat; + break; + case 'D': + result.type = JavaType::TypeDouble; + break; + case '[': + result.type = JavaType::TypeArray; + // LIVECONNECT_COMPLIANCE: We don't support multi-dimensional arrays, so + // there's no need to populate the inner types. + break; + case 'L': + result.type = jni_name == "Ljava.lang.String;" ? + JavaType::TypeString : + JavaType::TypeObject; + break; + default: + // Includes void (V). + NOTREACHED(); + } + return result; +} + +} // namespace + +JavaType::JavaType() { +} + +JavaType::JavaType(const JavaType& other) { + *this = other; +} + +JavaType::~JavaType() { +} + +JavaType& JavaType::operator=(const JavaType& other) { + type = other.type; + if (other.inner_type) { + DCHECK_EQ(JavaType::TypeArray, type); + inner_type.reset(new JavaType(*other.inner_type)); + } else { + inner_type.reset(); + } + return *this; +} + +JavaType JavaType::CreateFromBinaryName(const std::string& binary_name) { + JavaType result; + DCHECK(!binary_name.empty()); + if (binary_name == "boolean") { + result.type = JavaType::TypeBoolean; + } else if (binary_name == "byte") { + result.type = JavaType::TypeByte; + } else if (binary_name == "char") { + result.type = JavaType::TypeChar; + } else if (binary_name == "short") { + result.type = JavaType::TypeShort; + } else if (binary_name == "int") { + result.type = JavaType::TypeInt; + } else if (binary_name == "long") { + result.type = JavaType::TypeLong; + } else if (binary_name == "float") { + result.type = JavaType::TypeFloat; + } else if (binary_name == "double") { + result.type = JavaType::TypeDouble; + } else if (binary_name == "void") { + result.type = JavaType::TypeVoid; + } else if (binary_name[0] == '[') { + result.type = JavaType::TypeArray; + // The inner type of an array is represented in JNI format. + result.inner_type.reset(new JavaType(JavaTypeFromJNIName( + binary_name.substr(1)))); + } else if (binary_name == "java.lang.String") { + result.type = JavaType::TypeString; + } else { + result.type = JavaType::TypeObject; + } + return result; +} + +} // namespace content diff --git a/chromium/content/browser/renderer_host/java/java_type.h b/chromium/content/browser/renderer_host/java/java_type.h new file mode 100644 index 00000000000..fe7845eeaa6 --- /dev/null +++ b/chromium/content/browser/renderer_host/java/java_type.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 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_TYPE_H_ +#define CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_TYPE_H_ + +#include + +#include "base/memory/scoped_ptr.h" + +namespace content { + +// The type of a Java value. A light-weight enum-like structure intended for +// use by value and in STL containers. +struct JavaType { + JavaType(); + JavaType(const JavaType& other); + ~JavaType(); + JavaType& operator=(const JavaType& other); + + // Java's reflection API represents types as a string using an extended + // 'binary name'. + static JavaType CreateFromBinaryName(const std::string& binary_name); + + 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, + }; + + Type type; + scoped_ptr inner_type; // Used for TypeArray only. +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_JAVA_JAVA_TYPE_H_ -- cgit v1.2.1