java_bound_object.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/browser/renderer_host/java/java_bound_object.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_string.h"
9#include "base/memory/singleton.h"
10#include "base/string_number_conversions.h"
11#include "base/stringprintf.h"
12#include "content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.h"
13#include "content/browser/renderer_host/java/java_type.h"
14#include "content/public/browser/browser_thread.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebBindings.h"
16
17using base::StringPrintf;
18using base::android::AttachCurrentThread;
19using base::android::ConvertUTF8ToJavaString;
20using base::android::GetClass;
21using base::android::GetMethodIDFromClassName;
22using base::android::JavaRef;
23using base::android::ScopedJavaGlobalRef;
24using base::android::ScopedJavaLocalRef;
25using WebKit::WebBindings;
26
27// The conversion between JavaScript and Java types is based on the Live
28// Connect 2 spec. See
29// http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_CONVERSIONS.
30
31// Note that in some cases, we differ from from the spec in order to maintain
32// existing behavior. These areas are marked LIVECONNECT_COMPLIANCE. We may
33// revisit this decision in the future.
34
35namespace content {
36namespace {
37
38const char kJavaLangClass[] = "java/lang/Class";
39const char kJavaLangObject[] = "java/lang/Object";
40const char kJavaLangReflectMethod[] = "java/lang/reflect/Method";
41const char kGetClass[] = "getClass";
42const char kGetMethods[] = "getMethods";
43const char kIsAnnotationPresent[] = "isAnnotationPresent";
44const char kReturningJavaLangClass[] = "()Ljava/lang/Class;";
45const char kReturningJavaLangReflectMethodArray[] =
46    "()[Ljava/lang/reflect/Method;";
47const char kTakesJavaLangClassReturningBoolean[] = "(Ljava/lang/Class;)Z";
48
49// Our special NPObject type.  We extend an NPObject with a pointer to a
50// JavaBoundObject.  We also add static methods for each of the NPObject
51// callbacks, which are registered by our NPClass. These methods simply
52// delegate to the private implementation methods of JavaBoundObject.
53struct JavaNPObject : public NPObject {
54  JavaBoundObject* bound_object;
55
56  static const NPClass kNPClass;
57
58  static NPObject* Allocate(NPP npp, NPClass* np_class);
59  static void Deallocate(NPObject* np_object);
60  static bool HasMethod(NPObject* np_object, NPIdentifier np_identifier);
61  static bool Invoke(NPObject* np_object, NPIdentifier np_identifier,
62                     const NPVariant *args, uint32_t arg_count,
63                     NPVariant *result);
64  static bool HasProperty(NPObject* np_object, NPIdentifier np_identifier);
65  static bool GetProperty(NPObject* np_object, NPIdentifier np_identifier,
66                          NPVariant *result);
67};
68
69const NPClass JavaNPObject::kNPClass = {
70  NP_CLASS_STRUCT_VERSION,
71  JavaNPObject::Allocate,
72  JavaNPObject::Deallocate,
73  NULL,  // NPInvalidate
74  JavaNPObject::HasMethod,
75  JavaNPObject::Invoke,
76  NULL,  // NPInvokeDefault
77  JavaNPObject::HasProperty,
78  JavaNPObject::GetProperty,
79  NULL,  // NPSetProperty,
80  NULL,  // NPRemoveProperty
81};
82
83NPObject* JavaNPObject::Allocate(NPP npp, NPClass* np_class) {
84  JavaNPObject* obj = new JavaNPObject();
85  return obj;
86}
87
88void JavaNPObject::Deallocate(NPObject* np_object) {
89  JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
90  delete obj->bound_object;
91  delete obj;
92}
93
94bool JavaNPObject::HasMethod(NPObject* np_object, NPIdentifier np_identifier) {
95  std::string name(WebBindings::utf8FromIdentifier(np_identifier));
96  JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
97  return obj->bound_object->HasMethod(name);
98}
99
100bool JavaNPObject::Invoke(NPObject* np_object, NPIdentifier np_identifier,
101                          const NPVariant* args, uint32_t arg_count,
102                          NPVariant* result) {
103  std::string name(WebBindings::utf8FromIdentifier(np_identifier));
104  JavaNPObject* obj = reinterpret_cast<JavaNPObject*>(np_object);
105  return obj->bound_object->Invoke(name, args, arg_count, result);
106}
107
108bool JavaNPObject::HasProperty(NPObject* np_object,
109                               NPIdentifier np_identifier) {
110  // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate
111  // that the property is not present. Spec requires supporting this correctly.
112  return false;
113}
114
115bool JavaNPObject::GetProperty(NPObject* np_object,
116                               NPIdentifier np_identifier,
117                               NPVariant* result) {
118  // LIVECONNECT_COMPLIANCE: Existing behavior is to return false to indicate
119  // that the property is undefined. Spec requires supporting this correctly.
120  return false;
121}
122
123// Calls a Java method through JNI. If the Java method raises an uncaught
124// exception, it is cleared and this method returns false. Otherwise, this
125// method returns true and the Java method's return value is provided as an
126// NPVariant. Note that this method does not do any type coercion. The Java
127// return value is simply converted to the corresponding NPAPI type.
128bool CallJNIMethod(
129    jobject object,
130    const JavaType& return_type,
131    jmethodID id,
132    jvalue* parameters,
133    NPVariant* result,
134    const JavaRef<jclass>& safe_annotation_clazz,
135    const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager) {
136  JNIEnv* env = AttachCurrentThread();
137  switch (return_type.type) {
138    case JavaType::TypeBoolean:
139      BOOLEAN_TO_NPVARIANT(env->CallBooleanMethodA(object, id, parameters),
140                           *result);
141      break;
142    case JavaType::TypeByte:
143      INT32_TO_NPVARIANT(env->CallByteMethodA(object, id, parameters), *result);
144      break;
145    case JavaType::TypeChar:
146      INT32_TO_NPVARIANT(env->CallCharMethodA(object, id, parameters), *result);
147      break;
148    case JavaType::TypeShort:
149      INT32_TO_NPVARIANT(env->CallShortMethodA(object, id, parameters),
150                         *result);
151      break;
152    case JavaType::TypeInt:
153      INT32_TO_NPVARIANT(env->CallIntMethodA(object, id, parameters), *result);
154      break;
155    case JavaType::TypeLong:
156      DOUBLE_TO_NPVARIANT(env->CallLongMethodA(object, id, parameters),
157                          *result);
158      break;
159    case JavaType::TypeFloat:
160      DOUBLE_TO_NPVARIANT(env->CallFloatMethodA(object, id, parameters),
161                          *result);
162      break;
163    case JavaType::TypeDouble:
164      DOUBLE_TO_NPVARIANT(env->CallDoubleMethodA(object, id, parameters),
165                          *result);
166      break;
167    case JavaType::TypeVoid:
168      env->CallVoidMethodA(object, id, parameters);
169      VOID_TO_NPVARIANT(*result);
170      break;
171    case JavaType::TypeArray:
172      // LIVECONNECT_COMPLIANCE: Existing behavior is to not call methods that
173      // return arrays. Spec requires calling the method and converting the
174      // result to a JavaScript array.
175      VOID_TO_NPVARIANT(*result);
176      break;
177    case JavaType::TypeString: {
178      jstring java_string = static_cast<jstring>(
179          env->CallObjectMethodA(object, id, parameters));
180      // If an exception was raised, we must clear it before calling most JNI
181      // methods. ScopedJavaLocalRef is liable to make such calls, so we test
182      // first.
183      if (base::android::ClearException(env)) {
184        return false;
185      }
186      ScopedJavaLocalRef<jstring> scoped_java_string(env, java_string);
187      if (!scoped_java_string.obj()) {
188        // LIVECONNECT_COMPLIANCE: Existing behavior is to return undefined.
189        // Spec requires returning a null string.
190        VOID_TO_NPVARIANT(*result);
191        break;
192      }
193      std::string str =
194          base::android::ConvertJavaStringToUTF8(scoped_java_string);
195      size_t length = str.length();
196      // This pointer is freed in _NPN_ReleaseVariantValue in
197      // third_party/WebKit/Source/WebCore/bindings/v8/npruntime.cpp.
198      char* buffer = static_cast<char*>(malloc(length));
199      str.copy(buffer, length, 0);
200      STRINGN_TO_NPVARIANT(buffer, length, *result);
201      break;
202    }
203    case JavaType::TypeObject: {
204      // If an exception was raised, we must clear it before calling most JNI
205      // methods. ScopedJavaLocalRef is liable to make such calls, so we test
206      // first.
207      jobject java_object = env->CallObjectMethodA(object, id, parameters);
208      if (base::android::ClearException(env)) {
209        return false;
210      }
211      ScopedJavaLocalRef<jobject> scoped_java_object(env, java_object);
212      if (!scoped_java_object.obj()) {
213        NULL_TO_NPVARIANT(*result);
214        break;
215      }
216      OBJECT_TO_NPVARIANT(JavaBoundObject::Create(scoped_java_object,
217                                                  safe_annotation_clazz,
218                                                  manager),
219                          *result);
220      break;
221    }
222  }
223  return !base::android::ClearException(env);
224}
225
226jvalue CoerceJavaScriptNumberToJavaValue(const NPVariant& variant,
227                                         const JavaType& target_type,
228                                         bool coerce_to_string) {
229  // See http://jdk6.java.net/plugin2/liveconnect/#JS_NUMBER_VALUES.
230  jvalue result;
231  DCHECK(variant.type == NPVariantType_Int32 ||
232         variant.type == NPVariantType_Double);
233  bool is_double = variant.type == NPVariantType_Double;
234  switch (target_type.type) {
235    case JavaType::TypeByte:
236      result.b = is_double ? static_cast<jbyte>(NPVARIANT_TO_DOUBLE(variant)) :
237                             static_cast<jbyte>(NPVARIANT_TO_INT32(variant));
238      break;
239    case JavaType::TypeChar:
240      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert double to 0.
241      // Spec requires converting doubles the same as int32.
242      result.c = is_double ? 0 :
243                             static_cast<jchar>(NPVARIANT_TO_INT32(variant));
244      break;
245    case JavaType::TypeShort:
246      result.s = is_double ? static_cast<jshort>(NPVARIANT_TO_DOUBLE(variant)) :
247                             static_cast<jshort>(NPVARIANT_TO_INT32(variant));
248      break;
249    case JavaType::TypeInt:
250      result.i = is_double ? static_cast<jint>(NPVARIANT_TO_DOUBLE(variant)) :
251                             NPVARIANT_TO_INT32(variant);
252      break;
253    case JavaType::TypeLong:
254      result.j = is_double ? static_cast<jlong>(NPVARIANT_TO_DOUBLE(variant)) :
255                             NPVARIANT_TO_INT32(variant);
256      break;
257    case JavaType::TypeFloat:
258      result.f = is_double ? static_cast<jfloat>(NPVARIANT_TO_DOUBLE(variant)) :
259                             NPVARIANT_TO_INT32(variant);
260      break;
261    case JavaType::TypeDouble:
262      result.d = is_double ? NPVARIANT_TO_DOUBLE(variant) :
263                             NPVARIANT_TO_INT32(variant);
264      break;
265    case JavaType::TypeObject:
266      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
267      // requires handling object equivalents of primitive types.
268      result.l = NULL;
269      break;
270    case JavaType::TypeString:
271      result.l = coerce_to_string ?
272          ConvertUTF8ToJavaString(
273              AttachCurrentThread(),
274              is_double ?
275                  base::StringPrintf("%.6lg", NPVARIANT_TO_DOUBLE(variant)) :
276                  base::Int64ToString(NPVARIANT_TO_INT32(variant))).Release() :
277          NULL;
278      break;
279    case JavaType::TypeBoolean:
280      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
281      // requires converting to false for 0 or NaN, true otherwise.
282      result.z = JNI_FALSE;
283      break;
284    case JavaType::TypeArray:
285      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to null. Spec
286      // requires raising a JavaScript exception.
287      result.l = NULL;
288      break;
289    case JavaType::TypeVoid:
290      // Conversion to void must never happen.
291      NOTREACHED();
292      break;
293  }
294  return result;
295}
296
297jvalue CoerceJavaScriptBooleanToJavaValue(const NPVariant& variant,
298                                          const JavaType& target_type,
299                                          bool coerce_to_string) {
300  // See http://jdk6.java.net/plugin2/liveconnect/#JS_BOOLEAN_VALUES.
301  DCHECK_EQ(NPVariantType_Bool, variant.type);
302  bool boolean_value = NPVARIANT_TO_BOOLEAN(variant);
303  jvalue result;
304  switch (target_type.type) {
305    case JavaType::TypeBoolean:
306      result.z = boolean_value ? JNI_TRUE : JNI_FALSE;
307      break;
308    case JavaType::TypeObject:
309      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
310      // requires handling java.lang.Boolean and java.lang.Object.
311      result.l = NULL;
312      break;
313    case JavaType::TypeString:
314      result.l = coerce_to_string ?
315          ConvertUTF8ToJavaString(AttachCurrentThread(),
316                                  boolean_value ? "true" : "false").Release() :
317          NULL;
318      break;
319    case JavaType::TypeByte:
320    case JavaType::TypeChar:
321    case JavaType::TypeShort:
322    case JavaType::TypeInt:
323    case JavaType::TypeLong:
324    case JavaType::TypeFloat:
325    case JavaType::TypeDouble: {
326      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
327      // requires converting to 0 or 1.
328      jvalue null_value = {0};
329      result = null_value;
330      break;
331    }
332    case JavaType::TypeArray:
333      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
334      // requires raising a JavaScript exception.
335      result.l = NULL;
336      break;
337    case JavaType::TypeVoid:
338      // Conversion to void must never happen.
339      NOTREACHED();
340      break;
341  }
342  return result;
343}
344
345jvalue CoerceJavaScriptStringToJavaValue(const NPVariant& variant,
346                                         const JavaType& target_type) {
347  // See http://jdk6.java.net/plugin2/liveconnect/#JS_STRING_VALUES.
348  DCHECK_EQ(NPVariantType_String, variant.type);
349  jvalue result;
350  switch (target_type.type) {
351    case JavaType::TypeString:
352      result.l = ConvertUTF8ToJavaString(
353          AttachCurrentThread(),
354          base::StringPiece(NPVARIANT_TO_STRING(variant).UTF8Characters,
355                            NPVARIANT_TO_STRING(variant).UTF8Length)).Release();
356      break;
357    case JavaType::TypeObject:
358      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
359      // requires handling java.lang.Object.
360      result.l = NULL;
361      break;
362    case JavaType::TypeByte:
363    case JavaType::TypeShort:
364    case JavaType::TypeInt:
365    case JavaType::TypeLong:
366    case JavaType::TypeFloat:
367    case JavaType::TypeDouble: {
368      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
369      // requires using valueOf() method of corresponding object type.
370      jvalue null_value = {0};
371      result = null_value;
372      break;
373    }
374    case JavaType::TypeChar:
375      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
376      // requires using java.lang.Short.decode().
377      result.c = 0;
378      break;
379    case JavaType::TypeBoolean:
380      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
381      // requires converting the empty string to false, otherwise true.
382      result.z = JNI_FALSE;
383      break;
384    case JavaType::TypeArray:
385      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
386      // requires raising a JavaScript exception.
387      result.l = NULL;
388      break;
389    case JavaType::TypeVoid:
390      // Conversion to void must never happen.
391      NOTREACHED();
392      break;
393  }
394  return result;
395}
396
397// Note that this only handles primitive types and strings.
398jobject CreateJavaArray(const JavaType& type, jsize length) {
399  JNIEnv* env = AttachCurrentThread();
400  switch (type.type) {
401    case JavaType::TypeBoolean:
402      return env->NewBooleanArray(length);
403    case JavaType::TypeByte:
404      return env->NewByteArray(length);
405    case JavaType::TypeChar:
406      return env->NewCharArray(length);
407    case JavaType::TypeShort:
408      return env->NewShortArray(length);
409    case JavaType::TypeInt:
410      return env->NewIntArray(length);
411    case JavaType::TypeLong:
412      return env->NewLongArray(length);
413    case JavaType::TypeFloat:
414      return env->NewFloatArray(length);
415    case JavaType::TypeDouble:
416      return env->NewDoubleArray(length);
417    case JavaType::TypeString: {
418      ScopedJavaLocalRef<jclass> clazz(GetClass(env, "java/lang/String"));
419      return env->NewObjectArray(length, clazz.obj(), NULL);
420    }
421    case JavaType::TypeVoid:
422      // Conversion to void must never happen.
423    case JavaType::TypeArray:
424    case JavaType::TypeObject:
425      // Not handled.
426      NOTREACHED();
427  }
428  return NULL;
429}
430
431// Sets the specified element of the supplied array to the value of the
432// supplied jvalue. Requires that the type of the array matches that of the
433// jvalue. Handles only primitive types and strings. Note that in the case of a
434// string, the array takes a new reference to the string object.
435void SetArrayElement(jobject array,
436                     const JavaType& type,
437                     jsize index,
438                     const jvalue& value) {
439  JNIEnv* env = AttachCurrentThread();
440  switch (type.type) {
441    case JavaType::TypeBoolean:
442      env->SetBooleanArrayRegion(static_cast<jbooleanArray>(array), index, 1,
443                                 &value.z);
444      break;
445    case JavaType::TypeByte:
446      env->SetByteArrayRegion(static_cast<jbyteArray>(array), index, 1,
447                              &value.b);
448      break;
449    case JavaType::TypeChar:
450      env->SetCharArrayRegion(static_cast<jcharArray>(array), index, 1,
451                              &value.c);
452      break;
453    case JavaType::TypeShort:
454      env->SetShortArrayRegion(static_cast<jshortArray>(array), index, 1,
455                               &value.s);
456      break;
457    case JavaType::TypeInt:
458      env->SetIntArrayRegion(static_cast<jintArray>(array), index, 1,
459                             &value.i);
460      break;
461    case JavaType::TypeLong:
462      env->SetLongArrayRegion(static_cast<jlongArray>(array), index, 1,
463                              &value.j);
464      break;
465    case JavaType::TypeFloat:
466      env->SetFloatArrayRegion(static_cast<jfloatArray>(array), index, 1,
467                               &value.f);
468      break;
469    case JavaType::TypeDouble:
470      env->SetDoubleArrayRegion(static_cast<jdoubleArray>(array), index, 1,
471                                &value.d);
472      break;
473    case JavaType::TypeString:
474      env->SetObjectArrayElement(static_cast<jobjectArray>(array), index,
475                                 value.l);
476      break;
477    case JavaType::TypeVoid:
478      // Conversion to void must never happen.
479    case JavaType::TypeArray:
480    case JavaType::TypeObject:
481      // Not handled.
482      NOTREACHED();
483  }
484  base::android::CheckException(env);
485}
486
487void ReleaseJavaValueIfRequired(JNIEnv* env,
488                                jvalue* value,
489                                const JavaType& type) {
490  if (type.type == JavaType::TypeString ||
491      type.type == JavaType::TypeObject ||
492      type.type == JavaType::TypeArray) {
493    env->DeleteLocalRef(value->l);
494    value->l = NULL;
495  }
496}
497
498jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant,
499                                        const JavaType& target_type,
500                                        bool coerce_to_string);
501
502// Returns a new local reference to a Java array.
503jobject CoerceJavaScriptObjectToArray(const NPVariant& variant,
504                                      const JavaType& target_type) {
505  DCHECK_EQ(JavaType::TypeArray, target_type.type);
506  NPObject* object = NPVARIANT_TO_OBJECT(variant);
507  DCHECK_NE(&JavaNPObject::kNPClass, object->_class);
508
509  const JavaType& target_inner_type = *target_type.inner_type.get();
510  // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for
511  // multi-dimensional arrays. Spec requires handling multi-demensional arrays.
512  if (target_inner_type.type == JavaType::TypeArray) {
513    return NULL;
514  }
515
516  // LIVECONNECT_COMPLIANCE: Existing behavior is to return null for object
517  // arrays. Spec requires handling object arrays.
518  if (target_inner_type.type == JavaType::TypeObject) {
519    return NULL;
520  }
521
522  // If the object does not have a length property, return null.
523  NPVariant length_variant;
524  if (!WebBindings::getProperty(0, object,
525                                WebBindings::getStringIdentifier("length"),
526                                &length_variant)) {
527    WebBindings::releaseVariantValue(&length_variant);
528    return NULL;
529  }
530
531  // If the length property does not have numeric type, or is outside the valid
532  // range for a Java array length, return null.
533  jsize length = -1;
534  if (NPVARIANT_IS_INT32(length_variant)
535      && NPVARIANT_TO_INT32(length_variant) >= 0) {
536    length = NPVARIANT_TO_INT32(length_variant);
537  } else if (NPVARIANT_IS_DOUBLE(length_variant)
538             && NPVARIANT_TO_DOUBLE(length_variant) >= 0.0
539             && NPVARIANT_TO_DOUBLE(length_variant) <= kint32max) {
540    length = static_cast<jsize>(NPVARIANT_TO_DOUBLE(length_variant));
541  }
542  WebBindings::releaseVariantValue(&length_variant);
543  if (length == -1) {
544    return NULL;
545  }
546
547  // Create the Java array.
548  // TODO(steveblock): Handle failure to create the array.
549  jobject result = CreateJavaArray(target_inner_type, length);
550  NPVariant value_variant;
551  JNIEnv* env = AttachCurrentThread();
552  for (jsize i = 0; i < length; ++i) {
553    // It seems that getProperty() will set the variant to type void on failure,
554    // but this doesn't seem to be documented, so do it explicitly here for
555    // safety.
556    VOID_TO_NPVARIANT(value_variant);
557    // If this fails, for example due to a missing element, we simply treat the
558    // value as JavaScript undefined.
559    WebBindings::getProperty(0, object, WebBindings::getIntIdentifier(i),
560                             &value_variant);
561    jvalue element = CoerceJavaScriptValueToJavaValue(value_variant,
562                                                      target_inner_type,
563                                                      false);
564    SetArrayElement(result, target_inner_type, i, element);
565    // CoerceJavaScriptValueToJavaValue() creates new local references to
566    // strings, objects and arrays. Of these, only strings can occur here.
567    // SetArrayElement() causes the array to take its own reference to the
568    // string, so we can now release the local reference.
569    DCHECK_NE(JavaType::TypeObject, target_inner_type.type);
570    DCHECK_NE(JavaType::TypeArray, target_inner_type.type);
571    ReleaseJavaValueIfRequired(env, &element, target_inner_type);
572    WebBindings::releaseVariantValue(&value_variant);
573  }
574
575  return result;
576}
577
578jvalue CoerceJavaScriptObjectToJavaValue(const NPVariant& variant,
579                                         const JavaType& target_type,
580                                         bool coerce_to_string) {
581  // This covers both JavaScript objects (including arrays) and Java objects.
582  // See http://jdk6.java.net/plugin2/liveconnect/#JS_OTHER_OBJECTS,
583  // http://jdk6.java.net/plugin2/liveconnect/#JS_ARRAY_VALUES and
584  // http://jdk6.java.net/plugin2/liveconnect/#JS_JAVA_OBJECTS
585  DCHECK_EQ(NPVariantType_Object, variant.type);
586
587  NPObject* object = NPVARIANT_TO_OBJECT(variant);
588  bool is_java_object = &JavaNPObject::kNPClass == object->_class;
589
590  jvalue result;
591  switch (target_type.type) {
592    case JavaType::TypeObject:
593      if (is_java_object) {
594        // LIVECONNECT_COMPLIANCE: Existing behavior is to pass all Java
595        // objects. Spec requires passing only Java objects which are
596        // assignment-compatibile.
597        result.l = AttachCurrentThread()->NewLocalRef(
598            JavaBoundObject::GetJavaObject(object).obj());
599      } else {
600        // LIVECONNECT_COMPLIANCE: Existing behavior is to pass null. Spec
601        // requires converting if the target type is
602        // netscape.javascript.JSObject, otherwise raising a JavaScript
603        // exception.
604        result.l = NULL;
605      }
606      break;
607    case JavaType::TypeString:
608      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to
609      // "undefined". Spec requires calling toString() on the Java object.
610      result.l = coerce_to_string ?
611          ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined").
612              Release() :
613          NULL;
614      break;
615    case JavaType::TypeByte:
616    case JavaType::TypeShort:
617    case JavaType::TypeInt:
618    case JavaType::TypeLong:
619    case JavaType::TypeFloat:
620    case JavaType::TypeDouble:
621    case JavaType::TypeChar: {
622      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to 0. Spec
623      // requires raising a JavaScript exception.
624      jvalue null_value = {0};
625      result = null_value;
626      break;
627    }
628    case JavaType::TypeBoolean:
629      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to false. Spec
630      // requires raising a JavaScript exception.
631      result.z = JNI_FALSE;
632      break;
633    case JavaType::TypeArray:
634      if (is_java_object) {
635        // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
636        // requires raising a JavaScript exception.
637        result.l = NULL;
638      } else {
639        result.l = CoerceJavaScriptObjectToArray(variant, target_type);
640      }
641      break;
642    case JavaType::TypeVoid:
643      // Conversion to void must never happen.
644      NOTREACHED();
645      break;
646  }
647  return result;
648}
649
650jvalue CoerceJavaScriptNullOrUndefinedToJavaValue(const NPVariant& variant,
651                                                  const JavaType& target_type,
652                                                  bool coerce_to_string) {
653  // See http://jdk6.java.net/plugin2/liveconnect/#JS_NULL.
654  DCHECK(variant.type == NPVariantType_Null ||
655         variant.type == NPVariantType_Void);
656  jvalue result;
657  switch (target_type.type) {
658    case JavaType::TypeObject:
659      result.l = NULL;
660      break;
661    case JavaType::TypeString:
662      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert undefined to
663      // "undefined". Spec requires converting undefined to NULL.
664      result.l = (coerce_to_string && variant.type == NPVariantType_Void) ?
665          ConvertUTF8ToJavaString(AttachCurrentThread(), "undefined").
666              Release() :
667          NULL;
668      break;
669    case JavaType::TypeByte:
670    case JavaType::TypeChar:
671    case JavaType::TypeShort:
672    case JavaType::TypeInt:
673    case JavaType::TypeLong:
674    case JavaType::TypeFloat:
675    case JavaType::TypeDouble: {
676      jvalue null_value = {0};
677      result = null_value;
678      break;
679    }
680    case JavaType::TypeBoolean:
681      result.z = JNI_FALSE;
682      break;
683    case JavaType::TypeArray:
684      // LIVECONNECT_COMPLIANCE: Existing behavior is to convert to NULL. Spec
685      // requires raising a JavaScript exception.
686      result.l = NULL;
687      break;
688    case JavaType::TypeVoid:
689      // Conversion to void must never happen.
690      NOTREACHED();
691      break;
692  }
693  return result;
694}
695
696// coerce_to_string means that we should try to coerce all JavaScript values to
697// strings when required, rather than simply converting to NULL. This is used
698// to maintain current behaviour, which differs slightly depending upon whether
699// or not the coercion in question is for an array element.
700//
701// Note that the jvalue returned by this method may contain a new local
702// reference to an object (string, object or array). This must be released by
703// the caller.
704jvalue CoerceJavaScriptValueToJavaValue(const NPVariant& variant,
705                                        const JavaType& target_type,
706                                        bool coerce_to_string) {
707  // Note that in all these conversions, the relevant field of the jvalue must
708  // always be explicitly set, as jvalue does not initialize its fields.
709
710  switch (variant.type) {
711    case NPVariantType_Int32:
712    case NPVariantType_Double:
713      return CoerceJavaScriptNumberToJavaValue(variant, target_type,
714                                               coerce_to_string);
715    case NPVariantType_Bool:
716      return CoerceJavaScriptBooleanToJavaValue(variant, target_type,
717                                                coerce_to_string);
718    case NPVariantType_String:
719      return CoerceJavaScriptStringToJavaValue(variant, target_type);
720    case NPVariantType_Object:
721      return CoerceJavaScriptObjectToJavaValue(variant, target_type,
722                                               coerce_to_string);
723    case NPVariantType_Null:
724    case NPVariantType_Void:
725      return CoerceJavaScriptNullOrUndefinedToJavaValue(variant, target_type,
726                                                        coerce_to_string);
727  }
728  NOTREACHED();
729  return jvalue();
730}
731
732}  // namespace
733
734NPObject* JavaBoundObject::Create(
735    const JavaRef<jobject>& object,
736    const JavaRef<jclass>& safe_annotation_clazz,
737    const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager) {
738  // The first argument (a plugin's instance handle) is passed through to the
739  // allocate function directly, and we don't use it, so it's ok to be 0.
740  // The object is created with a ref count of one.
741  NPObject* np_object = WebBindings::createObject(0, const_cast<NPClass*>(
742      &JavaNPObject::kNPClass));
743  // The NPObject takes ownership of the JavaBoundObject.
744  reinterpret_cast<JavaNPObject*>(np_object)->bound_object =
745      new JavaBoundObject(object, safe_annotation_clazz, manager);
746  return np_object;
747}
748
749JavaBoundObject::JavaBoundObject(
750    const JavaRef<jobject>& object,
751    const JavaRef<jclass>& safe_annotation_clazz,
752    const base::WeakPtr<JavaBridgeDispatcherHostManager>& manager)
753    : java_object_(AttachCurrentThread(), object.obj()),
754      manager_(manager),
755      are_methods_set_up_(false),
756      safe_annotation_clazz_(safe_annotation_clazz) {
757  BrowserThread::PostTask(
758        BrowserThread::UI, FROM_HERE,
759        base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectCreated,
760                   manager_,
761                   base::android::ScopedJavaGlobalRef<jobject>(object)));
762  // Other than informing the JavaBridgeDispatcherHostManager that a java bound
763  // object has been created (above), we don't do anything else with our Java
764  // object when first created. We do it all lazily when a method is first
765  // invoked.
766}
767
768JavaBoundObject::~JavaBoundObject() {
769  BrowserThread::PostTask(
770      BrowserThread::UI, FROM_HERE,
771      base::Bind(&JavaBridgeDispatcherHostManager::JavaBoundObjectDestroyed,
772                 manager_,
773                 base::android::ScopedJavaGlobalRef<jobject>(
774                     java_object_.get(AttachCurrentThread()))));
775}
776
777ScopedJavaLocalRef<jobject> JavaBoundObject::GetJavaObject(NPObject* object) {
778  DCHECK_EQ(&JavaNPObject::kNPClass, object->_class);
779  JavaBoundObject* jbo = reinterpret_cast<JavaNPObject*>(object)->bound_object;
780  return jbo->java_object_.get(AttachCurrentThread());
781}
782
783bool JavaBoundObject::HasMethod(const std::string& name) const {
784  EnsureMethodsAreSetUp();
785  return methods_.find(name) != methods_.end();
786}
787
788bool JavaBoundObject::Invoke(const std::string& name, const NPVariant* args,
789                             size_t arg_count, NPVariant* result) {
790  EnsureMethodsAreSetUp();
791
792  // Get all methods with the correct name.
793  std::pair<JavaMethodMap::const_iterator, JavaMethodMap::const_iterator>
794      iters = methods_.equal_range(name);
795  if (iters.first == iters.second) {
796    return false;
797  }
798
799  // Take the first method with the correct number of arguments.
800  JavaMethod* method = NULL;
801  for (JavaMethodMap::const_iterator iter = iters.first; iter != iters.second;
802       ++iter) {
803    if (iter->second->num_parameters() == arg_count) {
804      method = iter->second.get();
805      break;
806    }
807  }
808  if (!method) {
809    return false;
810  }
811
812  // Coerce
813  std::vector<jvalue> parameters(arg_count);
814  for (size_t i = 0; i < arg_count; ++i) {
815    parameters[i] = CoerceJavaScriptValueToJavaValue(args[i],
816                                                     method->parameter_type(i),
817                                                     true);
818  }
819
820  ScopedJavaLocalRef<jobject> obj = java_object_.get(AttachCurrentThread());
821
822  bool ok = false;
823  if (!obj.is_null()) {
824    // Call
825    ok = CallJNIMethod(obj.obj(), method->return_type(),
826                       method->id(), &parameters[0], result,
827                       safe_annotation_clazz_,
828                       manager_);
829  }
830
831  // Now that we're done with the jvalue, release any local references created
832  // by CoerceJavaScriptValueToJavaValue().
833  JNIEnv* env = AttachCurrentThread();
834  for (size_t i = 0; i < arg_count; ++i) {
835    ReleaseJavaValueIfRequired(env, &parameters[i], method->parameter_type(i));
836  }
837
838  return ok;
839}
840
841void JavaBoundObject::EnsureMethodsAreSetUp() const {
842  if (are_methods_set_up_)
843    return;
844  are_methods_set_up_ = true;
845
846  JNIEnv* env = AttachCurrentThread();
847  ScopedJavaLocalRef<jobject> obj = java_object_.get(env);
848
849  if (obj.is_null()) {
850    return;
851  }
852
853  ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
854      env->CallObjectMethod(obj.obj(),  GetMethodIDFromClassName(
855          env,
856          kJavaLangObject,
857          kGetClass,
858          kReturningJavaLangClass))));
859
860  ScopedJavaLocalRef<jobjectArray> methods(env, static_cast<jobjectArray>(
861      env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName(
862          env,
863          kJavaLangClass,
864          kGetMethods,
865          kReturningJavaLangReflectMethodArray))));
866
867  size_t num_methods = env->GetArrayLength(methods.obj());
868  // Java objects always have public methods.
869  DCHECK(num_methods);
870
871  for (size_t i = 0; i < num_methods; ++i) {
872    ScopedJavaLocalRef<jobject> java_method(
873        env,
874        env->GetObjectArrayElement(methods.obj(), i));
875
876    if (!safe_annotation_clazz_.is_null()) {
877      jboolean safe = env->CallBooleanMethod(java_method.obj(),
878          GetMethodIDFromClassName(
879              env,
880              kJavaLangReflectMethod,
881              kIsAnnotationPresent,
882              kTakesJavaLangClassReturningBoolean),
883          safe_annotation_clazz_.obj());
884
885      if (!safe)
886        continue;
887    }
888
889    JavaMethod* method = new JavaMethod(java_method);
890    methods_.insert(std::make_pair(method->name(), method));
891  }
892}
893
894}  // namespace content
895