// 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/browser/renderer_host/java/jni_helper.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::JavaRef; using base::android::ScopedJavaGlobalRef; using base::android::ScopedJavaLocalRef; using blink::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