blob: 8c6eb24c412662e9c3bd20f7bdc59c95d68940c6 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/mus/ax_remote_host.h"
#include <stddef.h>
#include "base/no_destructor.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_event.h"
#include "ui/accessibility/platform/aura_window_properties.h"
#include "ui/accessibility/platform/ax_unique_id.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/accessibility/ax_aura_obj_wrapper.h"
#include "ui/views/accessibility/ax_event_manager.h"
#include "ui/views/mus/ax_tree_source_mus.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
using display::Display;
using display::Screen;
namespace views {
AXRemoteHost::AXRemoteHost() {
AXEventManager::Get()->AddObserver(this);
AXAuraObjCache::GetInstance()->SetDelegate(this);
}
AXRemoteHost::~AXRemoteHost() {
if (widget_)
StopMonitoringWidget();
AXAuraObjCache::GetInstance()->SetDelegate(nullptr);
AXEventManager::Get()->RemoveObserver(this);
}
void AXRemoteHost::Init(service_manager::Connector* connector) {
connector->BindInterface(ax::mojom::kAXHostServiceName, &ax_host_ptr_);
BindAndRegisterRemote();
}
void AXRemoteHost::InitForTesting(ax::mojom::AXHostPtr host_ptr) {
ax_host_ptr_ = std::move(host_ptr);
BindAndRegisterRemote();
}
void AXRemoteHost::StartMonitoringWidget(Widget* widget) {
if (!enabled_)
return;
// Check if we're already tracking a widget.
// TODO(jamescook): Support multiple widgets.
if (widget_)
StopMonitoringWidget();
widget_ = widget;
widget_->AddObserver(this);
DCHECK_NE(tree_id_, ui::AXTreeIDUnknown());
widget_->GetNativeWindow()->SetProperty(ui::kChildAXTreeID,
new std::string(tree_id_.ToString()));
// The cache needs to track the root window to follow focus changes.
AXAuraObjCache* cache = AXAuraObjCache::GetInstance();
cache->OnRootWindowObjCreated(widget_->GetNativeWindow());
// Start the AX tree with the contents view because the window frame is
// handled by the window manager in another process.
View* contents_view = widget_->widget_delegate()->GetContentsView();
AXAuraObjWrapper* contents_wrapper = cache->GetOrCreate(contents_view);
tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper, tree_id_);
tree_serializer_ = std::make_unique<AuraAXTreeSerializer>(tree_source_.get());
// Inform the serializer of the display device scale factor.
UpdateDeviceScaleFactor();
Screen::GetScreen()->AddObserver(this);
SendEvent(contents_wrapper, ax::mojom::Event::kLoadComplete);
}
void AXRemoteHost::StopMonitoringWidget() {
DCHECK(widget_);
DCHECK(widget_->HasObserver(this));
Screen::GetScreen()->RemoveObserver(this);
widget_->RemoveObserver(this);
AXAuraObjCache* cache = AXAuraObjCache::GetInstance();
cache->OnRootWindowObjDestroyed(widget_->GetNativeWindow());
cache->Remove(widget_->widget_delegate()->GetContentsView());
widget_ = nullptr;
// Delete source and serializers to save memory.
tree_serializer_.reset();
tree_source_.reset();
}
void AXRemoteHost::OnAutomationEnabled(bool enabled) {
if (enabled)
Enable();
else
Disable();
}
void AXRemoteHost::PerformAction(const ui::AXActionData& action_data) {
if (!enabled_)
return;
// A hit test requires determining the node to perform the action on first.
if (action_data.action == ax::mojom::Action::kHitTest) {
PerformHitTest(action_data);
return;
}
tree_source_->HandleAccessibleAction(action_data);
}
void AXRemoteHost::OnWidgetClosing(Widget* widget) {
// In the typical case, clean up early during widget close. Waiting until
// widget destroy can sometimes lead to crashes due to AX window visibility
// events being triggered during close. https://crbug.com/869608
DCHECK_EQ(widget_, widget);
StopMonitoringWidget();
}
void AXRemoteHost::OnWidgetDestroying(Widget* widget) {
// Widgets can be deleted without being closed, which is common in tests
// and possible in production.
DCHECK_EQ(widget_, widget);
StopMonitoringWidget();
}
void AXRemoteHost::OnDisplayMetricsChanged(const Display& display,
uint32_t metrics) {
if (metrics & display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR) {
UpdateDeviceScaleFactor();
SendEvent(tree_source_->GetRoot(), ax::mojom::Event::kLocationChanged);
}
}
void AXRemoteHost::OnChildWindowRemoved(AXAuraObjWrapper* parent) {
if (!enabled_ || !widget_)
return;
DCHECK(tree_source_);
if (!parent)
parent = tree_source_->GetRoot();
SendEvent(parent, ax::mojom::Event::kChildrenChanged);
}
void AXRemoteHost::OnEvent(AXAuraObjWrapper* aura_obj,
ax::mojom::Event event_type) {
SendEvent(aura_obj, event_type);
}
void AXRemoteHost::OnViewEvent(View* view, ax::mojom::Event event_type) {
CHECK(view);
if (!enabled_)
return;
// Can return null for views without a widget.
AXAuraObjWrapper* aura_obj = AXAuraObjCache::GetInstance()->GetOrCreate(view);
if (!aura_obj)
return;
SendEvent(aura_obj, event_type);
}
void AXRemoteHost::FlushForTesting() {
ax_host_ptr_.FlushForTesting();
}
void AXRemoteHost::BindAndRegisterRemote() {
ax::mojom::AXRemoteHostPtr remote;
binding_.Bind(mojo::MakeRequest(&remote));
ax_host_ptr_->RegisterRemoteHost(
std::move(remote),
base::BindOnce(&AXRemoteHost::RegisterRemoteHostCallback,
base::Unretained(this)));
}
void AXRemoteHost::RegisterRemoteHostCallback(const ui::AXTreeID& tree_id,
bool enabled) {
tree_id_ = tree_id;
// Set the initial enabled state and send the AX tree if necessary.
OnAutomationEnabled(enabled);
}
void AXRemoteHost::Enable() {
// Don't early-exit if already enabled. AXRemoteHost can start up in the
// "enabled" state even if ChromeVox is off at the moment the app launches.
// Turning on ChromeVox later will generate another OnAutomationEnabled()
// call and we need to serialize the node tree again. This is similar to
// AutomationManagerAura's behavior. https://crbug.com/876407
enabled_ = true;
std::set<aura::Window*> roots =
MusClient::Get()->window_tree_client()->GetRoots();
for (aura::Window* root : roots) {
Widget* widget = Widget::GetWidgetForNativeWindow(root);
// Typically it's only tests that create Windows (the WindowTreeHost
// backing the root Window) in such a way that there is no associated
// widget.
if (widget) {
// TODO(jamescook): Support multiple roots.
StartMonitoringWidget(widget);
}
}
}
void AXRemoteHost::Disable() {
if (!enabled_)
return;
enabled_ = false;
StopMonitoringWidget();
}
void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj,
ax::mojom::Event event_type) {
DCHECK(aura_obj);
// Early return when this host is disabled or only partially initialized.
// This roughly matches the behavior in AutomationManagerAura::SendEvent.
// Toggling ChromeVox off does not disable the host, etc: crbug.com/910224
if (!enabled_ || !widget_ || !tree_serializer_ || !tree_source_)
return;
ui::AXTreeUpdate update;
if (!tree_serializer_->SerializeChanges(aura_obj, &update)) {
LOG(ERROR) << "Unable to serialize accessibility tree.";
return;
}
std::vector<ui::AXTreeUpdate> updates;
updates.push_back(update);
// Make sure the focused node is serialized.
AXAuraObjWrapper* focus = AXAuraObjCache::GetInstance()->GetFocus();
if (focus) {
ui::AXTreeUpdate focused_node_update;
tree_serializer_->SerializeChanges(focus, &focused_node_update);
updates.push_back(focused_node_update);
}
ui::AXEvent event;
event.id = aura_obj->GetUniqueId();
event.event_type = event_type;
// Other fields are not used.
ax_host_ptr_->HandleAccessibilityEvent(tree_id_, updates, event);
}
void AXRemoteHost::PerformHitTest(const ui::AXActionData& action) {
DCHECK(enabled_);
DCHECK_EQ(action.action, ax::mojom::Action::kHitTest);
// If the widget isn't open yet there's nothing to hit.
if (!widget_)
return;
views::View* hit_view =
widget_->GetRootView()->GetEventHandlerForPoint(action.target_point);
if (hit_view)
hit_view->NotifyAccessibilityEvent(action.hit_test_event_to_fire, true);
}
void AXRemoteHost::UpdateDeviceScaleFactor() {
DCHECK(widget_);
DCHECK(tree_source_);
// Use the scale factor for the widget's window's current display.
Display display =
Screen::GetScreen()->GetDisplayNearestWindow(widget_->GetNativeWindow());
tree_source_->set_device_scale_factor(display.device_scale_factor());
}
} // namespace views