blob: 5ca5c43be54463853d0d9c4bcdd0164fcd49e8e6 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/text_encoder/text_encoder.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "gin/array_buffer.h"
#include "gin/public/isolate_holder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "v8/include/libplatform/libplatform.h"
#include "v8/include/v8.h"
namespace text_encoder {
namespace {
static const int kBindingListIndex = 0;
typedef std::vector<std::unique_ptr<TextEncoder>> BindingList;
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::NewStringType;
using v8::Script;
using v8::String;
using v8::V8;
using v8::Value;
void CreateTextEncoderCallback(
const v8::FunctionCallbackInfo<v8::Value>& info) {
// Check this is a constructor call: JS should always request
// `new TextEncoder()` rather than just `TextEncoder`.
DCHECK(info.IsConstructCall());
std::unique_ptr<TextEncoder> text_encoder;
Local<Context> context = info.GetIsolate()->GetCurrentContext();
gin::Handle<TextEncoder> handle = TextEncoder::Create(context, &text_encoder);
BindingList* bindings = static_cast<BindingList*>(
context->Global()->GetAlignedPointerFromInternalField(kBindingListIndex));
bindings->push_back(std::move(text_encoder));
info.GetReturnValue().Set(handle.ToV8());
}
void AddCallHandlerToTemplate(v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate>& object_template,
const std::string& name,
v8::FunctionCallback callback) {
v8::Local<v8::FunctionTemplate> fn_template =
v8::FunctionTemplate::New(isolate);
fn_template->SetCallHandler(callback);
object_template->Set(isolate, name.c_str(), fn_template);
}
class TestTextEncoder : public testing::Test {
public:
TestTextEncoder() {
gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode,
gin::ArrayBufferAllocator::SharedInstance(),
/*reference_table=*/nullptr, "");
isolate_holder_ = std::make_unique<gin::IsolateHolder>(
base::SingleThreadTaskRunner::GetCurrentDefault(),
gin::IsolateHolder::kSingleThread,
gin::IsolateHolder::IsolateType::kUtility);
}
~TestTextEncoder() override = default;
std::string Run(std::string test) {
Isolate::Scope isolate_scope(isolate_holder_->isolate());
// Create a stack-allocated handle scope.
HandleScope handle_scope(isolate_holder_->isolate());
v8::Handle<v8::ObjectTemplate> global_template =
v8::ObjectTemplate::New(isolate_holder_->isolate());
global_template->SetInternalFieldCount(kBindingListIndex + 1);
AddCallHandlerToTemplate(isolate_holder_->isolate(), global_template,
"TextEncoder", CreateTextEncoderCallback);
// Create a new context.
Local<Context> context = Context::New(
isolate_holder_->isolate(), /*extensions=*/nullptr, global_template);
BindingList binding_list;
context->Global()->SetAlignedPointerInInternalField(kBindingListIndex,
&binding_list);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(isolate_holder_->isolate(), test.c_str(),
NewStringType::kNormal)
.ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
Local<Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(isolate_holder_->isolate(), result);
return *utf8;
}
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<gin::IsolateHolder> isolate_holder_;
};
TEST_F(TestTextEncoder, BasicTest) {
// Verify our test infra is working properly.
EXPECT_EQ("97", Run("\"a\".charCodeAt(0)"));
// Verify we can create a TextEncoder.
EXPECT_EQ("97", Run("new TextEncoder();\"a\".charCodeAt(0)"));
// Verift we can encode a character.
EXPECT_EQ("97", Run("new TextEncoder().encode(\"a\")[0]"));
// Verify we can encode two character strings.
EXPECT_EQ("97", Run("new TextEncoder().encode(\"ab\")[0]"));
EXPECT_EQ("98", Run("new TextEncoder().encode(\"ab\")[1]"));
// Verify we can save a TextEncoder in a variable.
EXPECT_EQ("97", Run("let te = new TextEncoder(); \
te.encode(\"a\")[0]"));
// Verify we can encode two strings.
EXPECT_EQ("97", Run("let te = new TextEncoder(); \
te.encode(\"b\"); \
te.encode(\"a\")[0]"));
// Verify we can encode two strings without modifying first string.
EXPECT_EQ("98", Run("let te = new TextEncoder(); \
let x = te.encode(\"b\"); \
te.encode(\"a\"); \
x[0]"));
// Encode a 3 byte UTF character.
EXPECT_EQ("226,130,172", Run("new TextEncoder().encode('€')"));
// Encode a 4 byte UTF character.
EXPECT_EQ("240,157,159,152", Run("new TextEncoder().encode('𝟘')"));
// Try first 128 characters.
EXPECT_EQ("true", Run("var r = true; \
var te = new TextEncoder(); \
for (var i = 0; i < 128; i++) \
r &&= i == te.encode(String.fromCharCode(i))"));
// Verify stringification of args.
EXPECT_EQ("49", Run("new TextEncoder().encode(1)"));
EXPECT_EQ("49,44,97", Run("new TextEncoder().encode([1,'a'])"));
EXPECT_EQ("50", Run("new TextEncoder().encode(new Uint8Array([2]))"));
EXPECT_EQ("52,57",
Run("new TextEncoder().encode(new TextEncoder().encode(1))"));
// Verify no args produces empty array.
EXPECT_EQ("", Run("new TextEncoder().encode()"));
// Verify null gets stringified.
EXPECT_EQ("110,117,108,108", Run("new TextEncoder().encode(null)"));
// Verify extra args are ignored.
EXPECT_EQ("49", Run("new TextEncoder().encode(1, 2)"));
EXPECT_EQ("49", Run("new TextEncoder().encode(1, 'a')"));
EXPECT_EQ("97", Run("new TextEncoder().encode('a', 1)"));
EXPECT_EQ("97", Run("new TextEncoder().encode('a', 'b')"));
EXPECT_EQ("49", Run("new TextEncoder().encode(new Uint8Array([1]), 1)"));
EXPECT_EQ("49", Run("new TextEncoder().encode(new Uint8Array([1]), 'a')"));
EXPECT_EQ("97", Run("new TextEncoder().encode('a', new Uint8Array([1]))"));
EXPECT_EQ("49", Run("new TextEncoder().encode(new Uint8Array([1]), new "
"Uint8Array([2]))"));
}
} // namespace
} // namespace text_encoder