blob: 191848ba3d6cfa57041e46176c54641fe8523725 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/accessibility/ax_window_obj_wrapper.h"
#include <stddef.h>
#include <string>
#include <vector>
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "ui/accessibility/aura/aura_window_properties.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window_tree_host.h"
#include "ui/aura/window_tree_host_platform.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/compositor/layer.h"
#include "ui/views/accessibility/ax_aura_obj_cache.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h"
#endif
namespace views {
namespace {
Widget* GetWidgetForWindow(aura::Window* window) {
Widget* widget = Widget::GetWidgetForNativeView(window);
if (!widget)
return nullptr;
// Under mus/mash both the WindowTreeHost's root aura::Window and the content
// aura::Window will return the same Widget for GetWidgetForNativeView(). Only
// return the Widget for the content window, not the root, since otherwise
// we'll end up with two children in the AX node tree that have the same
// parent.
if (widget->GetNativeWindow() != window) {
DCHECK(window->IsRootWindow());
return nullptr;
}
return widget;
}
// Fires |event| on the |window|, and the Widget and RootView associated with
// |window|.
void FireEventOnWindowChildWidgetAndRootView(aura::Window* window,
ax::mojom::Event event,
AXAuraObjCache* cache) {
cache->FireEvent(cache->GetOrCreate(window), event);
Widget* widget = GetWidgetForWindow(window);
if (widget) {
cache->FireEvent(cache->GetOrCreate(widget), event);
views::View* root_view = widget->GetRootView();
if (root_view)
root_view->NotifyAccessibilityEvent(event, true);
}
}
// Fires location change events on a window, taking into account its
// associated widget, that widget's root view, and descendant windows.
void FireLocationChangesRecursively(aura::Window* window,
AXAuraObjCache* cache) {
FireEventOnWindowChildWidgetAndRootView(
window, ax::mojom::Event::kLocationChanged, cache);
for (auto* child : window->children())
FireLocationChangesRecursively(child, cache);
}
std::string GetWindowName(aura::Window* window) {
std::string class_name = window->GetName();
if (class_name.empty())
class_name = "aura::Window";
return class_name;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
std::string GetPlatformWindowId(aura::Window* window) {
// Ignore non-top level windows.
if (!window->IsRootWindow() || window->parent())
return std::string();
// On desktop aura there is one WindowTreeHost per top-level window.
aura::WindowTreeHost* window_tree_host = window->GetHost();
if (!window_tree_host)
return std::string();
// Prefer the DesktopWindowTreeHostPlatform if it exists.
DesktopWindowTreeHostPlatform* desktop_window_tree_host_platform =
DesktopWindowTreeHostPlatform::GetHostForWidget(
window_tree_host->GetAcceleratedWidget());
if (!desktop_window_tree_host_platform)
return window_tree_host->GetUniqueId();
while (desktop_window_tree_host_platform->window_parent()) {
desktop_window_tree_host_platform =
desktop_window_tree_host_platform->window_parent();
}
return desktop_window_tree_host_platform->GetUniqueId();
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
} // namespace
AXWindowObjWrapper::AXWindowObjWrapper(AXAuraObjCache* aura_obj_cache,
aura::Window* window)
: AXAuraObjWrapper(aura_obj_cache),
window_(window),
is_root_window_(window->IsRootWindow()) {
observation_.Observe(window);
if (is_root_window_)
aura_obj_cache_->OnRootWindowObjCreated(window);
// This is a top level root window.
if (window->IsRootWindow() && !window->parent()) {
// On desktop aura there is one WindowTreeHost per top-level window.
aura::WindowTreeHost* window_tree_host = window->GetHost();
if (window_tree_host)
ime_observation_.Observe(window_tree_host->GetInputMethod());
}
}
AXWindowObjWrapper::~AXWindowObjWrapper() = default;
bool AXWindowObjWrapper::HandleAccessibleAction(
const ui::AXActionData& action) {
if (action.action == ax::mojom::Action::kFocus) {
window_->Focus();
return true;
}
return false;
}
AXAuraObjWrapper* AXWindowObjWrapper::GetParent() {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
const std::string& window_id = GetPlatformWindowId(window_);
// In Lacros, the presence of a platform window id means it is parented to the
// Ash tree via the app id.
if (!window_id.empty())
return nullptr;
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
aura::Window* parent = window_->parent();
if (!parent)
return nullptr;
if (parent->GetProperty(ui::kChildAXTreeID) &&
ui::AXTreeID::FromString(*(parent->GetProperty(ui::kChildAXTreeID))) !=
ui::AXTreeIDUnknown()) {
return nullptr;
}
return aura_obj_cache_->GetOrCreate(parent);
}
void AXWindowObjWrapper::GetChildren(
std::vector<AXAuraObjWrapper*>* out_children) {
// Ignore this window's descendants if it has a child tree.
if (window_->GetProperty(ui::kChildAXTreeID) &&
ui::AXTreeID::FromString(*(window_->GetProperty(ui::kChildAXTreeID))) !=
ui::AXTreeIDUnknown()) {
return;
}
// Ignore this window's children if it is forced to be invisible.
if (window_->GetProperty(ui::kAXConsiderInvisibleAndIgnoreChildren))
return;
for (auto* child : window_->children())
out_children->push_back(aura_obj_cache_->GetOrCreate(child));
// Also consider any associated widgets as children.
Widget* widget = GetWidgetForWindow(window_);
if (widget && widget->IsVisible())
out_children->push_back(aura_obj_cache_->GetOrCreate(widget));
}
void AXWindowObjWrapper::Serialize(ui::AXNodeData* out_node_data) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// This app id connects this node with a node in the Ash tree (an
// components/exo shell surface).
const std::string& window_id = GetPlatformWindowId(window_);
if (!window_id.empty())
out_node_data->AddStringAttribute(ax::mojom::StringAttribute::kAppId,
window_id);
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
if (window_->IsRootWindow() && !window_->parent() && window_->GetHost()) {
ui::TextInputClient* client =
window_->GetHost()->GetInputMethod()->GetTextInputClient();
// Only set caret bounds if input caret is in an editable node.
if (client && client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE) {
gfx::Rect caret_bounds_in_screen = client->GetCaretBounds();
out_node_data->AddIntListAttribute(
ax::mojom::IntListAttribute::kCaretBounds,
{caret_bounds_in_screen.x(), caret_bounds_in_screen.y(),
caret_bounds_in_screen.width(), caret_bounds_in_screen.height()});
}
}
out_node_data->id = GetUniqueId();
ax::mojom::Role role = window_->GetProperty(ui::kAXRoleOverride);
if (role != ax::mojom::Role::kNone)
out_node_data->role = role;
else
out_node_data->role = ax::mojom::Role::kWindow;
out_node_data->AddStringAttribute(ax::mojom::StringAttribute::kName,
base::UTF16ToUTF8(window_->GetTitle()));
if (!window_->IsVisible() ||
window_->GetProperty(ui::kAXConsiderInvisibleAndIgnoreChildren)) {
out_node_data->AddState(ax::mojom::State::kInvisible);
}
out_node_data->relative_bounds.bounds =
gfx::RectF(window_->GetBoundsInScreen());
out_node_data->AddStringAttribute(ax::mojom::StringAttribute::kClassName,
GetWindowName(window_));
std::string* child_ax_tree_id_ptr = window_->GetProperty(ui::kChildAXTreeID);
if (child_ax_tree_id_ptr) {
ui::AXTreeID child_ax_tree_id =
ui::AXTreeID::FromString(*child_ax_tree_id_ptr);
if (child_ax_tree_id != ui::AXTreeIDUnknown()) {
// Most often, child AX trees are parented to Views. We need to handle
// the case where they're not here, but we don't want the same AX tree
// to be a child of two different parents.
//
// To avoid this double-parenting, only add the child tree ID of this
// window if the top-level window doesn't have an associated Widget.
//
// Also, if this window is not visible, its child tree should also be
// non-visible so prune it.
if (!window_->GetToplevelWindow() ||
GetWidgetForWindow(window_->GetToplevelWindow()) ||
!window_->IsVisible() ||
window_->GetProperty(ui::kAXConsiderInvisibleAndIgnoreChildren)) {
return;
}
out_node_data->AddChildTreeId(child_ax_tree_id);
const float scale_factor =
window_->GetToplevelWindow()->layer()->device_scale_factor();
out_node_data->AddFloatAttribute(
ax::mojom::FloatAttribute::kChildTreeScale, scale_factor);
}
}
}
ui::AXNodeID AXWindowObjWrapper::GetUniqueId() const {
return unique_id_.Get();
}
std::string AXWindowObjWrapper::ToString() const {
return GetWindowName(window_);
}
void AXWindowObjWrapper::OnCaretBoundsChanged(
const ui::TextInputClient* client) {
FireEvent(ax::mojom::Event::kTreeChanged);
}
void AXWindowObjWrapper::OnWindowDestroyed(aura::Window* window) {
if (is_root_window_)
aura_obj_cache_->OnRootWindowObjDestroyed(window_);
aura_obj_cache_->Remove(window, nullptr);
}
void AXWindowObjWrapper::OnWindowDestroying(aura::Window* window) {
DCHECK_EQ(window, window_);
window_destroying_ = true;
}
void AXWindowObjWrapper::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
if (params.phase == WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED)
aura_obj_cache_->Remove(params.target, params.old_parent);
}
void AXWindowObjWrapper::OnWindowBoundsChanged(
aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
if (window_destroying_)
return;
if (window == window_)
FireLocationChangesRecursively(window_, aura_obj_cache_);
}
void AXWindowObjWrapper::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
if (window_destroying_ || window != window_)
return;
if (key == ui::kChildAXTreeID ||
key == ui::kAXConsiderInvisibleAndIgnoreChildren) {
FireEvent(ax::mojom::Event::kChildrenChanged);
}
}
void AXWindowObjWrapper::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
if (window_destroying_)
return;
FireEvent(ax::mojom::Event::kStateChanged);
}
void AXWindowObjWrapper::OnWindowTransformed(aura::Window* window,
ui::PropertyChangeReason reason) {
if (window_destroying_)
return;
if (window == window_)
FireLocationChangesRecursively(window_, aura_obj_cache_);
}
void AXWindowObjWrapper::OnWindowTitleChanged(aura::Window* window) {
if (window_destroying_)
return;
FireEventOnWindowChildWidgetAndRootView(
window_, ax::mojom::Event::kTreeChanged, aura_obj_cache_);
}
void AXWindowObjWrapper::FireEvent(ax::mojom::Event event_type) {
aura_obj_cache_->FireEvent(aura_obj_cache_->GetOrCreate(window_), event_type);
}
} // namespace views