blob: 74967443d05aa2d7526bdeb44e57508da8147b3b [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.
#ifndef SERVICES_ACCESSIBILITY_FEATURES_V8_MANAGER_H_
#define SERVICES_ACCESSIBILITY_FEATURES_V8_MANAGER_H_
#include <memory>
#include <queue>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/sequenced_task_runner_helpers.h"
#include "base/threading/sequence_bound.h"
#include "mojo/public/cpp/bindings/generic_pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/accessibility/features/bindings_isolate_holder.h"
#include "services/accessibility/public/mojom/accessibility_service.mojom-forward.h"
#include "services/accessibility/public/mojom/automation.mojom-forward.h"
#include "services/accessibility/public/mojom/file_loader.mojom-forward.h"
#include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h"
#include "v8-persistent-handle.h"
#include "v8-script.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-local-handle.h"
namespace v8 {
class Isolate;
} // namespace v8
namespace gin {
class ContextHolder;
class IsolateHolder;
} // namespace gin
namespace ax {
class AutomationInternalBindings;
class InterfaceBinder;
class V8Manager;
class OSDevToolsAgent;
// A V8Environment owns a V8 context within the Accessibility Service, the
// bindings that belong to that context, as well as loading the Javascript that
// will run in that context.
//
// It lives on an implementation-defined task runner (typically a background
// task runner dedicated to this isolate+context) and should primarily be used
// through its owning class, V8Manager.
//
// TODO(dcheng): Move this into v8_environment.h.
class V8Environment : public BindingsIsolateHolder {
public:
// The default Context ID to use. We currently will have one context per
// isolate. In the future we may need to switch this to an incrementing
// system.
static const int kDefaultContextId = 1;
using OnFileLoadedCallback = base::OnceCallback<void(base::File)>;
// Creates a new V8Environment with its own isolate and context.
static base::SequenceBound<V8Environment> Create(
base::WeakPtr<V8Manager> manager);
// Gets a pointer to the V8 manager that belongs to this `context`.
static V8Environment* GetFromContext(v8::Local<v8::Context> context);
// Resolves `relative_path` to `base_dir` and performs some normalizations:
// * Removes references to current directory;
// * Replaces '..'with the directory name.
//
// Notes:
// * This function will fail if `relative_path` is an absolute path;
// * `relative_path` must be a relative path of `base_dir` and does not
// reference any parents of `base_dir`;
// * `base_dir`is not empty.
static std::string NormalizeRelativePath(const std::string& relative_path,
const std::string& base_dir);
V8Environment(const V8Environment&) = delete;
V8Environment& operator=(const V8Environment&) = delete;
// Creates a devtools agent to debug javascript running in this environment.
void ConnectDevToolsAgent(
mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent);
// All of the APIs needed for this V8Manager (based on the AT type) should be
// installed before adding V8 bindings.
void InstallAutomation(
mojo::PendingAssociatedReceiver<mojom::Automation> automation);
void InstallOSState();
void AddV8Bindings();
// Executes the given string as a Javascript script, and calls the
// callback when execution is complete.
void ExecuteScript(const std::string& script,
base::OnceCallback<void()> on_complete);
void ExecuteModule(base::FilePath file_path, base::OnceClosure on_complete);
// BindingsIsolateHolder overrides:
v8::Isolate* GetIsolate() const override;
v8::Local<v8::Context> GetContext() const override;
explicit V8Environment(scoped_refptr<base::SequencedTaskRunner> main_runner,
base::WeakPtr<V8Manager> manager);
virtual ~V8Environment();
// Called by the Mojo JS API when ready to bind an interface.
void BindInterface(const std::string& interface_name,
mojo::GenericPendingReceiver pending_receiver);
// `identifier` that represents a module. The identifier is composed of the
// normalized resolved path of the directory name of the root module relative
// path concatenated with the module specifier (which may be a file name or a
// relative path of the root module).
v8::MaybeLocal<v8::Module> GetModuleFromIdentifier(
const std::string& identifier);
std::optional<std::string> GetIdentifierFromModule(
v8::Global<v8::Module> module);
private:
// Provides a hash function for a Global Module object.
class ModuleGlobalHash {
public:
explicit ModuleGlobalHash(v8::Isolate* isolate) : isolate_(isolate) {}
size_t operator()(const v8::Global<v8::Module>& module) const {
return module.Get(isolate_)->GetIdentityHash();
}
private:
raw_ptr<v8::Isolate> isolate_;
};
void CreateIsolate();
// Loads the file contents of the module referenced by `file_pat`, invoking
// `OnFileLoaded()` when the operation is done.
// `file_path` must be a relative path and for now accepts only files in the
// current directory.
void RequestModuleContents(base::FilePath file_path);
// Callback function invoked when the file contents of the module identified
// by `module_identifier` is finished loading.
void OnFileLoaded(std::string module_identifier, base::File file);
// Evaluates the module identified by `root_module_identifier_` if all module
// dependencies have been loaded and compiled into modules.
void EvaluateModule();
// Resets module evaluation to not in progress and handles the error thrown by
// the v8 isolate or by the environment.
void HandleModuleError(const std::string& message);
// Thread runner for communicating with object which constructed this
// class using V8Environment::Create. This may be the main service thread,
// but that is not required.
const scoped_refptr<base::SequencedTaskRunner> main_runner_;
const base::WeakPtr<V8Manager> manager_;
// Sync API bindings need to be installed during AddV8Bindings(), because the
// IsolateScope and HandleScope are limited to that function.
// Track which APIs need to be installed.
bool os_state_needed_ = false;
// Bindings wrappers for V8 APIs.
// TODO(crbug.com/1355633): Add more APIs including TTS, SST, etc.
std::unique_ptr<AutomationInternalBindings> automation_bindings_;
// Holders for isolate and context.
std::unique_ptr<gin::IsolateHolder> isolate_holder_;
std::unique_ptr<gin::ContextHolder> context_holder_;
// Whether a module is being evaluated by this object.
bool module_evaluation_in_progress_ = false;
// If a module is being evaluated, contains the identifier of the root module.
std::optional<std::string> root_module_identifier_;
// Callback to be invoked when the module is finished evaluating.
base::OnceClosure on_complete_;
// Number of modules that have not been compiled into a v8::Module object.
// Once there are not remaining modules to be loaded, the root module can be
// evaluated since all its dependencies are compiled and ready to be consumed.
unsigned int num_unloaded_modules_ = 0;
// Module identifier to Module object.
std::map<std::string, v8::Global<v8::Module>> identifier_to_module_map_;
using ModuleToIdentifierMap =
std::unordered_map<v8::Global<v8::Module>, std::string, ModuleGlobalHash>;
std::unique_ptr<ModuleToIdentifierMap> module_to_identifier_map_;
std::unique_ptr<OSDevToolsAgent> devtools_agent_;
};
// Owns the V8Environment and any Mojo interfaces exposed to that V8Environment.
// Lives on the main service thread; any use of the internally-owned
// V8Environment will be proxied to the v8 task runner.
//
// There may be one V8Manager per Assistive Technology feature or features
// may share V8Managers.
class V8Manager {
public:
V8Manager();
~V8Manager();
// Various optional features that can be configured. All configuration must be
// done before calling `FinishContextSetUp()`.
void ConfigureAutoclick(mojom::AccessibilityServiceClient* ax_service_client);
void ConfigureAutomation(
mojom::AccessibilityServiceClient* ax_service_client,
mojo::PendingAssociatedReceiver<mojom::Automation> automation);
void ConfigureOSState();
void ConfigureSpeechRecognition(
mojom::AccessibilityServiceClient* ax_service_client);
void ConfigureTts(mojom::AccessibilityServiceClient* ax_service_client);
void ConfigureUserInput(mojom::AccessibilityServiceClient* ax_service_client);
void ConfigureUserInterface(
mojom::AccessibilityServiceClient* ax_service_client);
// |file_loader_remote| must outlive this object.
void ConfigureFileLoader(
mojo::Remote<mojom::AccessibilityFileLoader>* file_loader_remote);
void FinishContextSetUp();
// Instructs V8Environment to create a devtools agent.
void ConnectDevToolsAgent(
mojo::PendingAssociatedReceiver<blink::mojom::DevToolsAgent> agent);
// Called by V8Environment when JS wants to bind a Mojo interface.
void BindInterface(const std::string& interface_name,
mojo::GenericPendingReceiver pending_receiver);
// Executes the module at |file_path|, invoking |callback| when the operation
// is done.
void ExecuteModule(base::FilePath file_path, base::OnceClosure on_complete);
// Loads the file at |path|, and invokes |callback| once that is done. Note
// that |callback| is wrapped with a base::SequenceBoundCallback, which causes
// the callback to be invoked in the sequence in which the
// base::SequenceBoundCallback was constructed.
// Caller is responsible for checking if the resulting base::File is valid.
void LoadFile(base::FilePath path,
base::SequenceBound<V8Environment::OnFileLoadedCallback>
sequence_bound_callback);
// Allow tests to expose additional Mojo interfaces to JS.
void AddInterfaceForTest(std::unique_ptr<InterfaceBinder> interface_binder);
void RunScriptForTest(const std::string& script,
base::OnceClosure on_complete);
// The files added here will be returned in fifo order in response to calls to
// |LoadFile()|.
void AddFileForTest(base::File file) {
files_for_test_.push(std::move(file));
}
private:
SEQUENCE_CHECKER(sequence_checker_);
base::SequenceBound<V8Environment> v8_env_;
// The Mojo interfaces that are exposed to JS. When JS wants to bind a Mojo
// interface, the first matching InterfaceBinder will be used.
std::vector<std::unique_ptr<InterfaceBinder>> interface_binders_;
// Interface used to load files.
raw_ptr<mojo::Remote<mojom::AccessibilityFileLoader>> file_loader_remote_;
std::queue<base::File> files_for_test_;
base::WeakPtrFactory<V8Manager> weak_factory_{this};
};
} // namespace ax
#endif // SERVICES_ACCESSIBILITY_FEATURES_V8_MANAGER_H_