blob: c0d1aa54785b578382cc2652ffab74fb973f38e0 [file] [log] [blame]
// 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/string_number_conversions.h"
#include "base/stringprintf.h"
#include "content/browser/renderer_host/java/java_type.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
using base::StringPrintf;
using base::android::AttachCurrentThread;
using base::android::ConvertUTF8ToJavaString;
using base::android::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 {
const char kJavaLangClass[] = "java/lang/Class";
const char kJavaLangObject[] = "java/lang/Object";
const char kGetClass[] = "getClass";
const char kGetMethods[] = "getMethods";
const char kReturningJavaLangClass[] = "()Ljava/lang/Class;";
const char kReturningJavaLangReflectMethodArray[] =
"()[Ljava/lang/reflect/Method;";
// Our special NPObject type. We extend an NPObject with a pointer to a
// JavaBoundObject. We also add static methods for each of the NPObject
// callbacks, which are registered by our NPClass. These methods simply
// delegate to the private implementation methods of JavaBoundObject.
struct JavaNPObject : public NPObject {
JavaBoundObject* bound_object;
static const NPClass kNPClass;
static NPObject* Allocate(NPP npp, NPClass* np_class);
static void Deallocate(NPObject* np_object);
static bool HasMethod(NPObject* np_object, NPIdentifier np_identifier);
static bool Invoke(NPObject* np_object, NPIdentifier np_identifier,
const NPVariant *args, uint32_t arg_count,
NPVariant *result);
static bool HasProperty(NPObject* np_object, NPIdentifier np_identifier);
static bool GetProperty(NPObject* np_object, NPIdentifier np_identifier,
NPVariant *result);
};
const NPClass JavaNPObject::kNPClass = {
NP_CLASS_STRUCT_VERSION,
JavaNPObject::Allocate,
JavaNPObject::Deallocate,
NULL, // NPInvalidate
JavaNPObject::HasMethod,
JavaNPObject::Invoke,
NULL, // NPInvokeDefault
JavaNPObject::HasProperty,
JavaNPObject::GetProperty,
NULL, // NPSetProperty,
NULL, // NPRemoveProperty
};
NPObject* JavaNPObject::Allocate(NPP npp, NPClass* np_class) {
JavaNPObject* obj = new JavaNPObject();
return obj;
}
void JavaNPObject::Deallocate(NPObject* np_object) {
JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
delete obj->bound_object;
delete obj;
}
bool JavaNPObject::HasMethod(NPObject* np_object, NPIdentifier np_identifier) {
std::string name(WebBindings::utf8FromIdentifier(np_identifier));
JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
return obj->bound_object->HasMethod(name);
}
bool JavaNPObject::Invoke(NPObject* np_object, NPIdentifier np_identifier,
const NPVariant* args, uint32_t arg_count,
NPVariant* result) {
std::string name(WebBindings::utf8FromIdentifier(np_identifier));
JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
return obj->bound_object->Invoke(name, args, arg_count, result);
}
bool JavaNPObject::HasProperty(NPObject* np_object,
NPIdentifier np_identifier) {
// LIVECONNECT_COMPLIANCE: 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 and returns the result as an NPVariant. Note
// that this method does not do any type coercion. The Java return value is
// simply converted to the corresponding NPAPI type.
NPVariant CallJNIMethod(jobject object, const JavaType& return_type,
jmethodID id, jvalue* parameters) {
JNIEnv* env = AttachCurrentThread();
NPVariant result;
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: {
ScopedJavaLocalRef<jstring> java_string(env, static_cast<jstring>(
env->CallObjectMethodA(object, id, parameters)));
if (!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(java_string);
// Take a copy and pass ownership to the variant. We must allocate using
// NPN_MemAlloc, to match NPN_ReleaseVariant, which uses NPN_MemFree.
size_t length = str.length();
char* buffer = static_cast<char*>(NPN_MemAlloc(length));
str.copy(buffer, length, 0);
STRINGN_TO_NPVARIANT(buffer, length, result);
break;
}
case JavaType::TypeObject: {
ScopedJavaLocalRef<jobject> java_object(
env,
env->CallObjectMethodA(object, id, parameters));
if (!java_object.obj()) {
NULL_TO_NPVARIANT(result);
break;
}
OBJECT_TO_NPVARIANT(JavaBoundObject::Create(java_object), result);
break;
}
}
return result;
}
jvalue CoerceJavaScriptNumberToJavaValue(const NPVariant& variant,
const JavaType& target_type,
bool coerce_to_string) {
// See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES.
jvalue result;
DCHECK(variant.type == NPVariantType_Int32 ||
variant.type == NPVariantType_Double);
bool is_double = variant.type == NPVariantType_Double;
switch (target_type.type) {
case JavaType::TypeByte:
result.b = is_double ? static_cast<jbyte>(NPVARIANT_TO_DOUBLE(variant)) :
static_cast<jbyte>(NPVARIANT_TO_INT32(variant));
break;
case JavaType::TypeChar:
// LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0.
// Spec requires converting doubles the same as int32.
result.c = is_double ? 0 :
static_cast<jchar>(NPVARIANT_TO_INT32(variant));
break;
case JavaType::TypeShort:
result.s = is_double ? static_cast<jshort>(NPVARIANT_TO_DOUBLE(variant)) :
static_cast<jshort>(NPVARIANT_TO_INT32(variant));
break;
case JavaType::TypeInt:
result.i = is_double ? static_cast<jint>(NPVARIANT_TO_DOUBLE(variant)) :
NPVARIANT_TO_INT32(variant);
break;
case JavaType::TypeLong:
result.j = is_double ? static_cast<jlong>(NPVARIANT_TO_DOUBLE(variant)) :
NPVARIANT_TO_INT32(variant);
break;
case JavaType::TypeFloat:
result.f = is_double ? static_cast<jfloat>(NPVARIANT_TO_DOUBLE(variant)) :
NPVARIANT_TO_INT32(variant);
break;
case JavaType::TypeDouble:
result.d = is_double ? NPVARIANT_TO_DOUBLE(variant) :
NPVARIANT_TO_INT32(variant);
break;
case JavaType::TypeObject:
// LIVECONNECT_COMPLIANCE: 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 ? 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<jclass> 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<jbooleanArray>(array), index, 1,
&value.z);
break;
case JavaType::TypeByte:
env->SetByteArrayRegion(static_cast<jbyteArray>(array), index, 1,
&value.b);
break;
case JavaType::TypeChar:
env->SetCharArrayRegion(static_cast<jcharArray>(array), index, 1,
&value.c);
break;
case JavaType::TypeShort:
env->SetShortArrayRegion(static_cast<jshortArray>(array), index, 1,
&value.s);
break;
case JavaType::TypeInt:
env->SetIntArrayRegion(static_cast<jintArray>(array), index, 1,
&value.i);
break;
case JavaType::TypeLong:
env->SetLongArrayRegion(static_cast<jlongArray>(array), index, 1,
&value.j);
break;
case JavaType::TypeFloat:
env->SetFloatArrayRegion(static_cast<jfloatArray>(array), index, 1,
&value.f);
break;
case JavaType::TypeDouble:
env->SetDoubleArrayRegion(static_cast<jdoubleArray>(array), index, 1,
&value.d);
break;
case JavaType::TypeString:
env->SetObjectArrayElement(static_cast<jobjectArray>(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<jsize>(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));
} 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<jobject>& object) {
// The first argument (a plugin's instance handle) is passed through to the
// allocate function directly, and we don't use it, so it's ok to be 0.
// The object is created with a ref count of one.
NPObject* np_object = WebBindings::createObject(0, const_cast<NPClass*>(
&JavaNPObject::kNPClass));
// The NPObject takes ownership of the JavaBoundObject.
reinterpret_cast<JavaNPObject*>(np_object)->bound_object =
new JavaBoundObject(object);
return np_object;
}
JavaBoundObject::JavaBoundObject(const JavaRef<jobject>& object)
: java_object_(object) {
// We don't do anything with our Java object when first created. We do it all
// lazily when a method is first invoked.
}
JavaBoundObject::~JavaBoundObject() {
}
jobject JavaBoundObject::GetJavaObject(NPObject* object) {
DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object;
return jbo->java_object_.obj();
}
bool JavaBoundObject::HasMethod(const std::string& name) const {
EnsureMethodsAreSetUp();
return methods_.find(name) != methods_.end();
}
bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args,
size_t arg_count, NPVariant* result) {
EnsureMethodsAreSetUp();
// Get all methods with the correct name.
std::pair<JavaMethodMap::const_iterator, JavaMethodMap::const_iterator>
iters = methods_.equal_range(name);
if (iters.first == iters.second) {
return false;
}
// Take the first method with the correct number of arguments.
JavaMethod* method = NULL;
for (JavaMethodMap::const_iterator iter = iters.first; iter != iters.second;
++iter) {
if (iter->second->num_parameters() == arg_count) {
method = iter->second.get();
break;
}
}
if (!method) {
return false;
}
// Coerce
std::vector<jvalue> parameters(arg_count);
for (size_t i = 0; i < arg_count; ++i) {
parameters[i] = CoerceJavaScriptValueToJavaValue(args[i],
method->parameter_type(i),
true);
}
// Call
*result = CallJNIMethod(java_object_.obj(), method->return_type(),
method->id(), &parameters[0]);
// 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, &parameters[i], method->parameter_type(i));
}
return true;
}
void JavaBoundObject::EnsureMethodsAreSetUp() const {
if (!methods_.empty()) {
return;
}
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
env->CallObjectMethod(java_object_.obj(), GetMethodIDFromClassName(
env,
kJavaLangObject,
kGetClass,
kReturningJavaLangClass))));
ScopedJavaLocalRef<jobjectArray> methods(env, static_cast<jobjectArray>(
env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName(
env,
kJavaLangClass,
kGetMethods,
kReturningJavaLangReflectMethodArray))));
size_t num_methods = env->GetArrayLength(methods.obj());
DCHECK(num_methods) << "Java objects always have public methods";
for (size_t i = 0; i < num_methods; ++i) {
ScopedJavaLocalRef<jobject> java_method(
env,
env->GetObjectArrayElement(methods.obj(), i));
JavaMethod* method = new JavaMethod(java_method);
methods_.insert(std::make_pair(method->name(), method));
}
}