chromium/content/browser/android/java/gin_java_method_invocation_helper_unittest.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/android/java/gin_java_method_invocation_helper.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/android/jni_android.h"
#include "base/values.h"
#include "content/common/android/gin_java_bridge_value.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

class NullObjectDelegate
    : public GinJavaMethodInvocationHelper::ObjectDelegate {
 public:
  NullObjectDelegate() = default;

  NullObjectDelegate(const NullObjectDelegate&) = delete;
  NullObjectDelegate& operator=(const NullObjectDelegate&) = delete;

  ~NullObjectDelegate() override = default;

  base::android::ScopedJavaLocalRef<jobject> GetLocalRef(JNIEnv* env) override {
    return base::android::ScopedJavaLocalRef<jobject>();
  }

  base::android::ScopedJavaLocalRef<jclass> GetLocalClassRef(
      JNIEnv* env) override {
    return base::android::ScopedJavaLocalRef<jclass>();
  }

  const JavaMethod* FindMethod(const std::string& method_name,
                               size_t num_parameters) override {
    return nullptr;
  }

  bool IsObjectGetClassMethod(const JavaMethod* method) override {
    return false;
  }

  const base::android::JavaRef<jclass>& GetSafeAnnotationClass() override {
    return safe_annotation_class_;
  }

 private:
  base::android::ScopedJavaGlobalRef<jclass> safe_annotation_class_;
};

class NullDispatcherDelegate
    : public GinJavaMethodInvocationHelper::DispatcherDelegate {
 public:
  NullDispatcherDelegate() = default;

  NullDispatcherDelegate(const NullDispatcherDelegate&) = delete;
  NullDispatcherDelegate& operator=(const NullDispatcherDelegate&) = delete;

  ~NullDispatcherDelegate() override = default;

  JavaObjectWeakGlobalRef GetObjectWeakRef(
      GinJavaBoundObject::ObjectID object_id) override {
    return JavaObjectWeakGlobalRef();
  }
};

}  // namespace

class GinJavaMethodInvocationHelperTest : public testing::Test {
};

namespace {

class CountingDispatcherDelegate
    : public GinJavaMethodInvocationHelper::DispatcherDelegate {
 public:
  CountingDispatcherDelegate() = default;

  CountingDispatcherDelegate(const CountingDispatcherDelegate&) = delete;
  CountingDispatcherDelegate& operator=(const CountingDispatcherDelegate&) =
      delete;

  ~CountingDispatcherDelegate() override = default;

  JavaObjectWeakGlobalRef GetObjectWeakRef(
      GinJavaBoundObject::ObjectID object_id) override {
    counters_[object_id]++;
    return JavaObjectWeakGlobalRef();
  }

  void AssertInvocationsCount(GinJavaBoundObject::ObjectID begin_object_id,
                              GinJavaBoundObject::ObjectID end_object_id) {
    EXPECT_EQ(end_object_id - begin_object_id,
              static_cast<int>(counters_.size()));
    for (GinJavaBoundObject::ObjectID i = begin_object_id;
         i < end_object_id; ++i) {
      EXPECT_LT(0, counters_[i]) << "ObjectID: " << i;
    }
  }

 private:
  typedef std::map<GinJavaBoundObject::ObjectID, int> Counters;
  Counters counters_;
};

}  // namespace

TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsNoObjects) {
  base::Value::List no_objects;
  for (int i = 0; i < 10; ++i) {
    no_objects.Append(i);
  }

  auto helper = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::make_unique<NullObjectDelegate>(), "foo", no_objects);
  CountingDispatcherDelegate counter;
  helper->Init(&counter);
  counter.AssertInvocationsCount(0, 0);
}

TEST_F(GinJavaMethodInvocationHelperTest, RetrievalOfObjectsHaveObjects) {
  base::Value::List objects;
  objects.Append(100);
  objects.Append(base::Value::FromUniquePtrValue(
      GinJavaBridgeValue::CreateObjectIDValue(1)));
  base::Value::List sub_list;
  sub_list.Append(200);
  sub_list.Append(base::Value::FromUniquePtrValue(
      GinJavaBridgeValue::CreateObjectIDValue(2)));
  objects.Append(std::move(sub_list));
  base::Value::Dict sub_dict;
  sub_dict.Set("1", 300);
  sub_dict.Set("2", base::Value::FromUniquePtrValue(
                        GinJavaBridgeValue::CreateObjectIDValue(3)));
  objects.Append(std::move(sub_dict));
  base::Value::List sub_list_with_dict;
  base::Value::Dict sub_sub_dict;
  sub_sub_dict.Set("1", base::Value::FromUniquePtrValue(
                            GinJavaBridgeValue::CreateObjectIDValue(4)));
  sub_list_with_dict.Append(std::move(sub_sub_dict));
  objects.Append(std::move(sub_list_with_dict));
  base::Value::Dict sub_dict_with_list;
  base::Value::List sub_sub_list;
  sub_sub_list.Append(base::Value::FromUniquePtrValue(
      GinJavaBridgeValue::CreateObjectIDValue(5)));
  sub_dict_with_list.Set("1", std::move(sub_sub_list));
  objects.Append(std::move(sub_dict_with_list));

  auto helper = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::make_unique<NullObjectDelegate>(), "foo", objects);
  CountingDispatcherDelegate counter;
  helper->Init(&counter);
  counter.AssertInvocationsCount(1, 6);
}

namespace {

class ObjectIsGoneObjectDelegate : public NullObjectDelegate {
 public:
  ObjectIsGoneObjectDelegate() :
      get_local_ref_called_(false) {
    // We need a Java Method object to create a valid JavaMethod instance.
    JNIEnv* env = base::android::AttachCurrentThread();
    base::android::ScopedJavaLocalRef<jclass> clazz(
        base::android::GetClass(env, "java/lang/Object"));
    jmethodID method_id =
        base::android::MethodID::Get<base::android::MethodID::TYPE_INSTANCE>(
            env, clazz.obj(), "hashCode", "()I");
    EXPECT_TRUE(method_id);
    base::android::ScopedJavaLocalRef<jobject> method_obj(
        env, env->ToReflectedMethod(clazz.obj(), method_id, false));
    EXPECT_TRUE(method_obj.obj());
    method_ = std::make_unique<JavaMethod>(method_obj);
  }

  ObjectIsGoneObjectDelegate(const ObjectIsGoneObjectDelegate&) = delete;
  ObjectIsGoneObjectDelegate& operator=(const ObjectIsGoneObjectDelegate&) =
      delete;

  ~ObjectIsGoneObjectDelegate() override = default;

  base::android::ScopedJavaLocalRef<jobject> GetLocalRef(JNIEnv* env) override {
    get_local_ref_called_ = true;
    return NullObjectDelegate::GetLocalRef(env);
  }

  const JavaMethod* FindMethod(const std::string& method_name,
                               size_t num_parameters) override {
    return method_.get();
  }

  bool get_local_ref_called() { return get_local_ref_called_; }

  const std::string& get_method_name() { return method_->name(); }

 protected:
  std::unique_ptr<JavaMethod> method_;
  bool get_local_ref_called_;
};

}  // namespace

TEST_F(GinJavaMethodInvocationHelperTest, HandleObjectIsGone) {
  base::Value::List no_objects;
  auto object_delegate_unique = std::make_unique<ObjectIsGoneObjectDelegate>();
  ObjectIsGoneObjectDelegate* object_delegate = object_delegate_unique.get();
  auto helper = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::move(object_delegate_unique), object_delegate->get_method_name(),
      no_objects);
  NullDispatcherDelegate dispatcher;
  helper->Init(&dispatcher);
  EXPECT_FALSE(object_delegate->get_local_ref_called());
  EXPECT_EQ(mojom::GinJavaBridgeError::kGinJavaBridgeNoError,
            helper->GetInvocationError());
  helper->Invoke();
  EXPECT_TRUE(object_delegate->get_local_ref_called());
  EXPECT_TRUE(helper->HoldsPrimitiveResult());
  EXPECT_TRUE(helper->GetPrimitiveResult().empty());
  EXPECT_EQ(mojom::GinJavaBridgeError::kGinJavaBridgeObjectIsGone,
            helper->GetInvocationError());
}

namespace {

class MethodNotFoundObjectDelegate : public NullObjectDelegate {
 public:
  MethodNotFoundObjectDelegate() : find_method_called_(false) {}

  MethodNotFoundObjectDelegate(const MethodNotFoundObjectDelegate&) = delete;
  MethodNotFoundObjectDelegate& operator=(const MethodNotFoundObjectDelegate&) =
      delete;

  ~MethodNotFoundObjectDelegate() override = default;

  base::android::ScopedJavaLocalRef<jobject> GetLocalRef(JNIEnv* env) override {
    return base::android::ScopedJavaLocalRef<jobject>(
        env, static_cast<jobject>(env->FindClass("java/lang/String")));
  }

  const JavaMethod* FindMethod(const std::string& method_name,
                               size_t num_parameters) override {
    find_method_called_ = true;
    return nullptr;
  }

  bool find_method_called() const { return find_method_called_; }

 protected:
  bool find_method_called_;
};

}  // namespace

TEST_F(GinJavaMethodInvocationHelperTest, HandleMethodNotFound) {
  base::Value::List no_objects;
  auto object_delegate_unique =
      std::make_unique<MethodNotFoundObjectDelegate>();
  MethodNotFoundObjectDelegate* object_delegate = object_delegate_unique.get();
  auto helper = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::move(object_delegate_unique), "foo", no_objects);
  NullDispatcherDelegate dispatcher;
  helper->Init(&dispatcher);
  EXPECT_FALSE(object_delegate->find_method_called());
  EXPECT_EQ(mojom::GinJavaBridgeError::kGinJavaBridgeNoError,
            helper->GetInvocationError());
  helper->Invoke();
  EXPECT_TRUE(object_delegate->find_method_called());
  EXPECT_TRUE(helper->HoldsPrimitiveResult());
  EXPECT_TRUE(helper->GetPrimitiveResult().empty());
  EXPECT_EQ(mojom::GinJavaBridgeError::kGinJavaBridgeMethodNotFound,
            helper->GetInvocationError());
}

namespace {

class GetClassObjectDelegate : public MethodNotFoundObjectDelegate {
 public:
  GetClassObjectDelegate() : get_class_called_(false) {}

  GetClassObjectDelegate(const GetClassObjectDelegate&) = delete;
  GetClassObjectDelegate& operator=(const GetClassObjectDelegate&) = delete;

  ~GetClassObjectDelegate() override = default;

  const JavaMethod* FindMethod(const std::string& method_name,
                               size_t num_parameters) override {
    find_method_called_ = true;
    return kFakeGetClass;
  }

  bool IsObjectGetClassMethod(const JavaMethod* method) override {
    get_class_called_ = true;
    return kFakeGetClass == method;
  }

  bool get_class_called() const { return get_class_called_; }

 private:
  static const JavaMethod* kFakeGetClass;
  bool get_class_called_;
};

// We don't expect GinJavaMethodInvocationHelper to actually invoke the
// method, since the point of the test is to verify whether calls to
// 'getClass' get blocked.
const JavaMethod* GetClassObjectDelegate::kFakeGetClass =
    (JavaMethod*)0xdeadbeef;

}  // namespace

TEST_F(GinJavaMethodInvocationHelperTest, HandleGetClassInvocation) {
  base::Value::List no_objects;
  auto object_delegate_unique = std::make_unique<GetClassObjectDelegate>();
  GetClassObjectDelegate* object_delegate = object_delegate_unique.get();
  auto helper = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::move(object_delegate_unique), "foo", no_objects);
  NullDispatcherDelegate dispatcher;
  helper->Init(&dispatcher);
  EXPECT_FALSE(object_delegate->find_method_called());
  EXPECT_FALSE(object_delegate->get_class_called());
  EXPECT_EQ(mojom::GinJavaBridgeError::kGinJavaBridgeNoError,
            helper->GetInvocationError());
  helper->Invoke();
  EXPECT_TRUE(object_delegate->find_method_called());
  EXPECT_TRUE(object_delegate->get_class_called());
  EXPECT_TRUE(helper->HoldsPrimitiveResult());
  EXPECT_TRUE(helper->GetPrimitiveResult().empty());
  EXPECT_EQ(
      mojom::GinJavaBridgeError::kGinJavaBridgeAccessToObjectGetClassIsBlocked,
      helper->GetInvocationError());
}

}  // namespace content