blob: 870840b3d6fc7f6a80973a60cc3ec045e4beb0a5 [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/automation_internal_bindings.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "gin/function_template.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "services/accessibility/assistive_technology_controller_impl.h"
#include "services/accessibility/automation_impl.h"
#include "services/accessibility/features/bindings_isolate_holder.h"
#include "services/accessibility/features/v8_utils.h"
#include "services/accessibility/public/mojom/accessibility_service.mojom.h"
#include "ui/accessibility/ax_event_generator.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/mojom/ax_event.mojom.h"
#include "ui/accessibility/platform/automation/automation_api_util.h"
#include "ui/accessibility/platform/automation/automation_v8_router.h"
#include "v8-function.h"
#include "v8-value.h"
#include "v8/include/v8-local-handle.h"
#include "v8/include/v8-template.h"
namespace ax {
namespace {
static const char kJSAutomationInternalV8Listeners[] =
"automationInternalV8Listeners";
static const char kJSCallListeners[] = "callListeners";
} // namespace
AutomationInternalBindings::AutomationInternalBindings(
BindingsIsolateHolder* isolate_holder,
mojo::PendingAssociatedReceiver<mojom::Automation> automation)
: isolate_holder_(isolate_holder),
automation_v8_bindings_(std::make_unique<ui::AutomationV8Bindings>(
/*AutomationTreeManagerOwner=*/this,
/*AutomationV8Router=*/this)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
receiver_.Bind(std::move(automation));
}
AutomationInternalBindings::~AutomationInternalBindings() = default;
void AutomationInternalBindings::AddAutomationRoutesToTemplate(
v8::Local<v8::ObjectTemplate>* object_template) {
template_ = object_template;
automation_v8_bindings_->AddV8Routes();
template_ = nullptr;
}
ui::AutomationV8Bindings* AutomationInternalBindings::GetAutomationV8Bindings()
const {
DCHECK(automation_v8_bindings_);
return automation_v8_bindings_.get();
}
void AutomationInternalBindings::NotifyTreeEventListenersChanged() {
// This task is posted because we need to wait for any pending mutations
// to be processed before sending the event.
CHECK(base::SequencedTaskRunner::HasCurrentDefault());
auto& main_runner = base::SequencedTaskRunner::GetCurrentDefault();
// `this` is safe here because this object outlives
// AutomationTreeManagerOwner, which in turn generates this kind of event.
auto task = base::BindOnce(&AutomationInternalBindings::
MaybeSendOnAllAutomationEventListenersRemoved,
base::Unretained(this));
main_runner->PostTask(FROM_HERE, std::move(task));
}
void AutomationInternalBindings::ThrowInvalidArgumentsException(
bool is_fatal) const {
GetIsolate()->ThrowException(v8::String::NewFromUtf8Literal(
GetIsolate(),
"Invalid arguments to AutomationInternalBindings function"));
if (!is_fatal)
return;
LOG(FATAL) << "Invalid arguments to AutomationInternalBindings function";
// TODO(crbug.com/1357889): Could print a stack trace from JS, paralleling
// AutomationInternalCustomBindings.
}
v8::Isolate* AutomationInternalBindings::GetIsolate() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(isolate_holder_);
return isolate_holder_->GetIsolate();
}
v8::Local<v8::Context> AutomationInternalBindings::GetContext() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(isolate_holder_);
return isolate_holder_->GetContext();
}
void AutomationInternalBindings::RouteHandlerFunction(
const std::string& name,
scoped_refptr<ui::V8HandlerFunctionWrapper> handler_function_wrapper) {
DCHECK(template_);
(*template_)
->Set(GetIsolate(), name.c_str(),
gin::CreateFunctionTemplate(
GetIsolate(),
base::BindRepeating(&ui::V8HandlerFunctionWrapper::Run,
handler_function_wrapper)));
}
ui::TreeChangeObserverFilter
AutomationInternalBindings::ParseTreeChangeObserverFilter(
const std::string& filter) const {
// TODO(b:327258691): Share const strings between c++ and js for event names.
if (filter == "none") {
return ui::TreeChangeObserverFilter::kNone;
}
if (filter == "noTreeChanges") {
return ui::TreeChangeObserverFilter::kNoTreeChanges;
}
if (filter == "liveRegionTreeChanges") {
return ui::TreeChangeObserverFilter::kLiveRegionTreeChanges;
}
if (filter == "textMarkerChanges") {
return ui::TreeChangeObserverFilter::kTextMarkerChanges;
}
if (filter == "allTreeChanges") {
return ui::TreeChangeObserverFilter::kAllTreeChanges;
}
return ui::TreeChangeObserverFilter::kAllTreeChanges;
}
std::string AutomationInternalBindings::GetMarkerTypeString(
ax::mojom::MarkerType type) const {
// TODO(crbug.com/1357889): Implement based on Automation API.
return ui::ToString(type);
}
std::string AutomationInternalBindings::GetFocusedStateString() const {
// TODO(crbug.com/1357889): Implement based on Automation API.
return "focused";
}
std::string AutomationInternalBindings::GetOffscreenStateString() const {
// TODO(crbug.com/1357889): Implement based on Automation API.
return "offscreen";
}
std::string
AutomationInternalBindings::GetLocalizedStringForImageAnnotationStatus(
ax::mojom::ImageAnnotationStatus status) const {
// TODO(crbug.com/1357889): Implement based on Automation API.
return ui::ToString(status);
}
std::string AutomationInternalBindings::GetTreeChangeTypeString(
ax::mojom::Mutation change_type) const {
// TODO(b:327258691): Share const strings between c++ and js for event names.
switch (change_type) {
case ax::mojom::Mutation::kNone:
return "none";
case ax::mojom::Mutation::kNodeCreated:
return "nodeCreated";
case ax::mojom::Mutation::kSubtreeCreated:
return "subtreeCreated";
case ax::mojom::Mutation::kNodeChanged:
return "nodeChanged";
case ax::mojom::Mutation::kTextChanged:
return "textChanged";
case ax::mojom::Mutation::kNodeRemoved:
return "nodeRemoved";
case ax::mojom::Mutation::kSubtreeUpdateEnd:
return "subtreeUpdateEnd";
}
}
std::string AutomationInternalBindings::GetEventTypeString(
const std::tuple<ax::mojom::Event, ui::AXEventGenerator::Event>& event_type)
const {
// TODO(b:327258691): Share const strings between c++ and js for event names.
const ui::AXEventGenerator::Event& generated_event = std::get<1>(event_type);
// Resolve the proper event based on generated or non-generated event sources.
if (generated_event != ui::AXEventGenerator::Event::NONE &&
!ui::ShouldIgnoreGeneratedEventForAutomation(generated_event)) {
return ui::ToString(generated_event);
}
const ax::mojom::Event& event = std::get<0>(event_type);
if (event != ax::mojom::Event::kNone &&
!ui::ShouldIgnoreAXEventForAutomation(event)) {
return ui::ToString(event);
}
return "";
}
void AutomationInternalBindings::DispatchEvent(
const std::string& event_name,
const base::Value::List& event_args) const {
v8::HandleScope handle_scope(GetIsolate());
v8::Local<v8::Context> context = GetContext();
v8::Context::Scope context_scope(context);
// Here, a helper function defined in js is invoked to retrieve the event
// object that contains all the listeners. Then, we dispatch the event to all
// listeners.
v8::Local<v8::String> func_name =
v8::String::NewFromUtf8(GetIsolate(), kJSAutomationInternalV8Listeners)
.ToLocalChecked();
v8::Local<v8::Value> func_value =
context->Global()->Get(context, func_name).ToLocalChecked();
v8::Local<v8::Function> get_event_listeners_from_name_function =
v8::Local<v8::Function>::Cast(func_value);
// Prepare the argument for the 'getEventListenersFromName' call
v8::Local<v8::Value> args[] = {
v8::String::NewFromUtf8(GetIsolate(), event_name.c_str())
.ToLocalChecked()};
// Call the 'getEventListenersFromName' function using the event that needs to
// be dispatched. The return value is an object containing all listeners to
// that event.
v8::Local<v8::Value> result = get_event_listeners_from_name_function
->Call(context, context->Global(), 1, args)
.ToLocalChecked();
CHECK(result->IsObject()) << "Unknown event type: " << event_name;
v8::Local<v8::Object> event_listeners_object =
v8::Local<v8::Object>::Cast(result);
v8::Local<v8::String> method_name =
v8::String::NewFromUtf8(GetIsolate(), kJSCallListeners).ToLocalChecked();
v8::Local<v8::Value> method_value =
event_listeners_object->Get(context, method_name).ToLocalChecked();
v8::Local<v8::Function> method = v8::Local<v8::Function>::Cast(method_value);
std::vector<v8::Local<v8::Value>> js_event_args;
for (const base::Value& value : event_args) {
v8::Local<v8::Value> js_event_arg =
V8ValueConverter ::GetInstance()->ConvertToV8Value(value, context);
js_event_args.push_back(js_event_arg);
}
v8::MaybeLocal<v8::Value> unused_result =
method->Call(context, event_listeners_object, js_event_args.size(),
js_event_args.data());
CHECK(!unused_result.IsEmpty());
}
} // namespace ax