blob: 8336b0dcaf3fb3391f78e83c809e66a3a4180774 [file] [log] [blame]
// Copyright 2022 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_manager.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/single_thread_task_runner_thread_mode.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "gin/arguments.h"
#include "gin/array_buffer.h"
#include "gin/converter.h"
#include "gin/function_template.h"
#include "gin/public/context_holder.h"
#include "gin/public/isolate_holder.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "services/accessibility/assistive_technology_controller_impl.h"
#include "services/accessibility/features/automation_internal_bindings.h"
#include "services/accessibility/features/mojo/mojo.h"
#include "services/accessibility/features/v8_bindings_utils.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-template.h"
namespace ax {
namespace {
// The index into the global template's internal fields that
// stores a pointer to this V8Manager. This allows any class with
// a v8::Context to access this V8Manager.
static const int kV8ContextWrapperIndex = 0;
} // namespace
// static
scoped_refptr<V8Manager> V8Manager::Create() {
// Create task runner for running V8. The Isolate should only ever be accessed
// on this thread.
auto v8_runner = base::ThreadPool::CreateSingleThreadTaskRunner(
{base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, base::MayBlock()},
base::SingleThreadTaskRunnerThreadMode::DEDICATED);
// Get a reference to the current SequencedTaskRunner for posting tasks back
// to the constructor and current thread.
auto main_runner = base::SequencedTaskRunner::GetCurrentDefault();
scoped_refptr<V8Manager> result(new V8Manager(v8_runner, main_runner));
v8_runner->PostTask(
FROM_HERE, base::BindOnce(&V8Manager::ConstructIsolateOnThread, result));
return result;
}
// static
V8Manager* V8Manager::GetFromContext(v8::Local<v8::Context> context) {
v8::Local<v8::Object> global_proxy = context->Global();
CHECK_LT(kV8ContextWrapperIndex, global_proxy->InternalFieldCount());
V8Manager* v8_manager = reinterpret_cast<V8Manager*>(
global_proxy->GetAlignedPointerFromInternalField(kV8ContextWrapperIndex));
CHECK(v8_manager);
return v8_manager;
}
V8Manager::V8Manager(scoped_refptr<base::SingleThreadTaskRunner> v8_runner,
scoped_refptr<base::SequencedTaskRunner> main_runner)
: base::RefCountedDeleteOnSequence<V8Manager>(v8_runner),
v8_runner_(v8_runner),
main_runner_(main_runner) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
V8Manager::~V8Manager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!isolate_holder_)
return;
NotifyIsolateWillDestroy();
isolate_holder_->isolate()->TerminateExecution();
context_holder_.reset();
isolate_holder_.reset();
}
void V8Manager::InstallAutomation(
base::WeakPtr<AssistiveTechnologyControllerImpl> at_controller) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
v8_runner_->PostTask(
FROM_HERE, base::BindOnce(&V8Manager::BindAutomationOnThread,
weak_ptr_factory_.GetWeakPtr(), at_controller));
}
void V8Manager::AddV8Bindings() {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
v8_runner_->PostTask(FROM_HERE,
base::BindOnce(&V8Manager::AddV8BindingsOnThread,
weak_ptr_factory_.GetWeakPtr()));
}
void V8Manager::ExecuteScript(const std::string& script,
base::OnceCallback<void()> on_complete) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
v8_runner_->PostTask(FROM_HERE,
base::BindOnce(&V8Manager::ExecuteScriptOnThread,
weak_ptr_factory_.GetWeakPtr(), script,
std::move(on_complete)));
}
v8::Isolate* V8Manager::GetIsolate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return isolate_holder_ ? isolate_holder_->isolate() : nullptr;
}
v8::Local<v8::Context> V8Manager::GetContext() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return context_holder_->context();
}
void V8Manager::BindInterface(const std::string& interface_name,
mojo::GenericPendingReceiver pending_receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(b:262637071): Add mappings for bindings for other C++/JS mojom APIs.
if (test_mojo_interface_ &&
test_mojo_interface_->MatchesInterface(interface_name)) {
test_mojo_interface_->BindReceiver(std::move(pending_receiver));
return;
}
LOG(ERROR) << "Couldn't find remote implementation for interface "
<< interface_name;
}
void V8Manager::SetTestMojoInterface(
std::unique_ptr<InterfaceBinder> test_interface) {
DETACH_FROM_SEQUENCE(sequence_checker_);
DCHECK(main_runner_ == base::SequencedTaskRunner::GetCurrentDefault());
v8_runner_->PostTask(FROM_HERE,
base::BindOnce(&V8Manager::SetTestMojoInterfaceOnThread,
weak_ptr_factory_.GetWeakPtr(),
std::move(test_interface)));
}
void V8Manager::ConstructIsolateOnThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (isolate_holder_ && context_holder_)
return;
std::unique_ptr<v8::Isolate::CreateParams> params =
gin::IsolateHolder::getDefaultIsolateParams();
isolate_holder_ = std::make_unique<gin::IsolateHolder>(
v8_runner_, gin::IsolateHolder::kSingleThread,
gin::IsolateHolder::IsolateType::kUtility, std::move(params));
}
void V8Manager::AddV8BindingsOnThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(isolate_holder_) << "V8 has not been started, cannot bind.";
v8::Isolate* isolate = isolate_holder_->isolate();
// Enter isolate scope.
v8::Isolate::Scope isolate_scope(isolate);
// Creates and enters stack-allocated handle scope.
// All the Local handles (Local<>) in this function will belong to this
// HandleScope and will be garbage collected when it goes out of scope in this
// C++ function.
v8::HandleScope handle_scope(isolate);
// Create a template for the global object where we set the
// built-in global functions.
v8::Local<v8::ObjectTemplate> global_template =
v8::ObjectTemplate::New(isolate);
global_template->SetInternalFieldCount(kV8ContextWrapperIndex + 1);
// Create a template for the global "chrome" object.
// We add this because APIs are found in the chrome namespace in JS,
// like chrome.automation, etc.
v8::Local<v8::ObjectTemplate> chrome_template =
v8::ObjectTemplate::New(isolate);
global_template->Set(isolate, "chrome", chrome_template);
// Add automation bindings if needed.
if (automation_bindings_) {
v8::Local<v8::ObjectTemplate> automation_template =
v8::ObjectTemplate::New(isolate);
automation_bindings_->AddAutomationRoutesToTemplate(&automation_template);
chrome_template->Set(isolate, "automation", automation_template);
v8::Local<v8::ObjectTemplate> automation_internal_template =
v8::ObjectTemplate::New(isolate);
automation_bindings_->AddAutomationInternalRoutesToTemplate(
&automation_internal_template);
chrome_template->Set(isolate, "automationInternal",
automation_internal_template);
}
// Adds atpconsole.log/warn/error.
// TODO(crbug.com/1355633): Deprecate and use console.log/warn/error instead.
BindingsUtils::AddAtpConsoleTemplate(isolate, global_template);
// Add TextEncoder and TextDecoder, which are used by Mojo to pass strings,
// as well as some Accessibility features.
BindingsUtils::AddCallHandlerToTemplate(
isolate, global_template, "TextEncoder",
BindingsUtils::CreateTextEncoderCallback);
BindingsUtils::AddCallHandlerToTemplate(
isolate, global_template, "TextDecoder",
BindingsUtils::CreateTextDecoderCallback);
// TODO(crbug.com/1355633): Add other API bindings to the global template.
// Add the global template to the current context.
v8::Local<v8::Context> context =
v8::Context::New(isolate, /*extensions=*/nullptr, global_template);
context_holder_ = std::make_unique<gin::ContextHolder>(isolate);
context_holder_->SetContext(context);
// Make a pointer to `this` in the current context.
// This allows Mojo to use the context to find `this` and bind interfaces
// on it.
v8::Local<v8::Object> global_proxy = context->Global();
DCHECK_EQ(kV8ContextWrapperIndex + 1, global_proxy->InternalFieldCount());
global_proxy->SetAlignedPointerInInternalField(kV8ContextWrapperIndex, this);
// Create a template for the "Mojo" object in the context scope.
v8::Context::Scope context_scope(context);
gin::Handle<Mojo> mojo = Mojo::Create(context);
global_proxy->Set(context, gin::StringToV8(isolate, "Mojo"), mojo.ToV8())
.Check();
// TODO(crbug.com/1355633): At this point we could load in API Javascript
// using ExecuteScript.
}
void V8Manager::BindAutomationOnThread(
base::WeakPtr<AssistiveTechnologyControllerImpl> at_controller) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Construct the AutomationInternalBindings and its routes.
automation_bindings_ = std::make_unique<AutomationInternalBindings>(
weak_ptr_factory_.GetWeakPtr(), at_controller, main_runner_);
}
void V8Manager::SetTestMojoInterfaceOnThread(
std::unique_ptr<InterfaceBinder> test_interface) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
test_mojo_interface_ = std::move(test_interface);
}
void V8Manager::ExecuteScriptOnThread(const std::string& script,
base::OnceCallback<void()> on_complete) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool result = BindingsIsolateHolder::ExecuteScriptInContext(script);
DCHECK(result);
std::move(on_complete).Run();
}
} // namespace ax