chromium/services/accessibility/features/v8_utils_unittest.cc

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

#include "services/accessibility/features/v8_utils.h"

#include <memory>

#include "base/logging.h"
#include "base/test/task_environment.h"
#include "gin/array_buffer.h"
#include "gin/public/isolate_holder.h"
#include "services/accessibility/features/bindings_isolate_holder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "v8-array-buffer.h"
#include "v8-container.h"
#include "v8-isolate.h"
#include "v8-local-handle.h"
#include "v8-object.h"
#include "v8-primitive.h"
#include "v8-template.h"

namespace ax {

class V8ValueConverterTest : public testing::Test,
                             public BindingsIsolateHolder {
 public:
  V8ValueConverterTest() {
    BindingsIsolateHolder::InitializeV8();

    isolate_holder_ = std::make_unique<gin::IsolateHolder>(
        base::SingleThreadTaskRunner::GetCurrentDefault(),
        gin::IsolateHolder::kSingleThread,
        gin::IsolateHolder::IsolateType::kTest);
    isolate_ = isolate_holder_->isolate();
  }

  // BindingsIsolateHolder:
  v8::Isolate* GetIsolate() const override { return isolate_; }
  v8::Local<v8::Context> GetContext() const override {
    v8::EscapableHandleScope handle_scope(isolate_);
    v8::Local<v8::Context> context =
        v8::Local<v8::Context>::New(isolate_, context_);
    return handle_scope.Escape(context);
  }
  void HandleError(const std::string& message) override {
    LOG(ERROR) << message;
  }

 protected:
  void SetUp() override {
    v8::HandleScope handle_scope(isolate_);
    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
    context_.Reset(isolate_, v8::Context::New(isolate_, nullptr, global));
  }

  void TearDown() override { context_.Reset(); }

  base::test::TaskEnvironment task_environment_;
  std::unique_ptr<gin::IsolateHolder> isolate_holder_;
  raw_ptr<v8::Isolate> isolate_ = nullptr;

  // Context for the JavaScript in the test.
  v8::Persistent<v8::Context> context_;
};

// This test covers all data types that accessibility uses to dispatch events.
// They are all added to a dictionary (which is also a type used), and makes
// sure the final conversion has the right types.
TEST_F(V8ValueConverterTest, DictWithBasicTypes) {
  base::Value::Dict dict;
  dict.Set("boolean", true);
  dict.Set("integer", 2);
  dict.Set("double", 2.0f);
  dict.Set("string", "hello world");

  v8::HandleScope handle_scope(isolate_);
  v8::Local<v8::Context> context = GetContext();
  v8::Context::Scope context_scope(context);

  v8::Local<v8::Object> v8_object = V8ValueConverter::GetInstance()
                                        ->ConvertToV8Value(dict, context)
                                        .As<v8::Object>();
  ASSERT_FALSE(v8_object.IsEmpty());

  EXPECT_EQ(dict.size(),
            v8_object->GetPropertyNames(context).ToLocalChecked()->Length());

  EXPECT_TRUE(v8_object
                  ->Get(context, v8::String::NewFromUtf8(
                                     isolate_, "boolean",
                                     v8::NewStringType::kInternalized)
                                     .ToLocalChecked())
                  .ToLocalChecked()
                  ->IsTrue());
  EXPECT_TRUE(v8_object
                  ->Get(context, v8::String::NewFromUtf8(
                                     isolate_, "integer",
                                     v8::NewStringType::kInternalized)
                                     .ToLocalChecked())
                  .ToLocalChecked()
                  ->IsInt32());
  EXPECT_TRUE(v8_object
                  ->Get(context, v8::String::NewFromUtf8(
                                     isolate_, "double",
                                     v8::NewStringType::kInternalized)
                                     .ToLocalChecked())
                  .ToLocalChecked()
                  ->IsNumber());
  EXPECT_TRUE(v8_object
                  ->Get(context, v8::String::NewFromUtf8(
                                     isolate_, "string",
                                     v8::NewStringType::kInternalized)
                                     .ToLocalChecked())
                  .ToLocalChecked()
                  ->IsString());
}

// Tests the conversion of the list type, used as the arguments list to function
// calls.
TEST_F(V8ValueConverterTest, ConvertsList) {
  base::Value::List list;
  list.Append(true);
  list.Append(2);

  v8::HandleScope handle_scope(isolate_);
  v8::Local<v8::Context> context = GetContext();
  v8::Context::Scope context_scope(context);

  v8::Local<v8::Array> v8_array = V8ValueConverter::GetInstance()
                                      ->ConvertToV8Value(list, context)
                                      .As<v8::Array>();

  EXPECT_EQ(list.size(), v8_array->Length());
  EXPECT_TRUE(v8_array->Get(context, v8::Integer::New(isolate_, 0))
                  .ToLocalChecked()
                  ->IsTrue());
  EXPECT_TRUE(v8_array->Get(context, v8::Integer::New(isolate_, 1))
                  .ToLocalChecked()
                  ->IsInt32());
}

TEST_F(V8ValueConverterTest, ConvertsBlogStorage) {
  base::Value::BlobStorage storage({1u, 2u});

  v8::HandleScope handle_scope(isolate_);
  v8::Local<v8::Context> context = GetContext();
  v8::Context::Scope context_scope(context);

  v8::Local<v8::ArrayBuffer> v8_array = V8ValueConverter::GetInstance()
                                            ->ConvertToV8Value(storage, context)
                                            .As<v8::ArrayBuffer>();

  EXPECT_EQ(storage.size(), v8_array->ByteLength());
}

}  // namespace ax