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