#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/adapters.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/notimplemented.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/ax_selection.h"
#include "ui/accessibility/ax_tree.h"
#include "ui/accessibility/ax_tree_data.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/accessibility/ax_tree_update.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_base.h"
#include "ui/base/layout.h"
#include "ui/events/event_utils.h"
#include "ui/views/accessibility/atomic_view_ax_tree_manager.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/accessibility/view_accessibility_utils.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace views {
namespace {
struct QueuedEvent {
QueuedEvent(ax::mojom::Event type, int32_t node_id)
: type(type), node_id(node_id) {}
ax::mojom::Event type;
int32_t node_id;
};
std::vector<QueuedEvent>& GetEventQueue() {
static base::NoDestructor<std::vector<QueuedEvent>> event_queue;
return *event_queue;
}
bool g_is_queueing_events = false;
bool g_is_flushing = false;
ui::AXPlatformNode* FromNativeWindow(gfx::NativeWindow native_window) {
Widget* widget = Widget::GetWidgetForNativeWindow(native_window);
if (!widget) {
return nullptr;
}
View* view = widget->GetRootView();
if (!view) {
return nullptr;
}
gfx::NativeViewAccessible native_view_accessible =
view->GetNativeViewAccessible();
if (!native_view_accessible) {
return nullptr;
}
return ui::AXPlatformNode::FromNativeViewAccessible(native_view_accessible);
}
ui::AXPlatformNode* PlatformNodeFromNodeID(int32_t id) {
return ui::AXPlatformNodeBase::GetFromUniqueId(id);
}
void FireEvent(QueuedEvent event) {
ui::AXPlatformNode* node = PlatformNodeFromNodeID(event.node_id);
if (node) {
node->NotifyAccessibilityEvent(event.type);
}
}
void FlushQueue() {
DCHECK(g_is_queueing_events);
g_is_queueing_events = false;
g_is_flushing = true;
for (QueuedEvent event : GetEventQueue()) {
FireEvent(event);
}
g_is_flushing = false;
GetEventQueue().clear();
}
void PostFlushEventQueueTaskIfNecessary() {
if (!g_is_queueing_events) {
g_is_queueing_events = true;
base::OnceCallback<void()> cb = base::BindOnce(&FlushQueue);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(cb));
}
}
}
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult() = default;
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult(
std::vector<raw_ptr<Widget, VectorExperimental>> child_widgets,
bool is_tab_modal_showing)
: child_widgets(child_widgets),
is_tab_modal_showing(is_tab_modal_showing) {}
ViewAXPlatformNodeDelegate::ChildWidgetsResult::ChildWidgetsResult(
const ViewAXPlatformNodeDelegate::ChildWidgetsResult& other) = default;
ViewAXPlatformNodeDelegate::ChildWidgetsResult::~ChildWidgetsResult() = default;
ViewAXPlatformNodeDelegate::ChildWidgetsResult&
ViewAXPlatformNodeDelegate::ChildWidgetsResult::operator=(
const ViewAXPlatformNodeDelegate::ChildWidgetsResult& other) = default;
ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view)
: ViewAccessibility(view) {}
void ViewAXPlatformNodeDelegate::Init() {
ax_platform_node_ = ui::AXPlatformNode::Create(*this);
DCHECK(ax_platform_node_);
static bool first_time = true;
if (first_time) {
ui::AXPlatformNode::RegisterNativeWindowHandler(
base::BindRepeating(&FromNativeWindow));
first_time = false;
}
}
ViewAXPlatformNodeDelegate::~ViewAXPlatformNodeDelegate() {
if (ui::AXPlatformNode::GetPopupFocusOverride() ==
ax_platform_node_->GetNativeViewAccessible()) {
ui::AXPlatformNode::SetPopupFocusOverride(gfx::NativeViewAccessible());
}
}
void ViewAXPlatformNodeDelegate::EnsureAtomicViewAXTreeManager() {
if (atomic_view_ax_tree_manager_) {
return;
}
atomic_view_ax_tree_manager_ =
views::AtomicViewAXTreeManager::Create(this, GetData());
}
bool ViewAXPlatformNodeDelegate::IsAccessibilityFocusable() const {
return GetData().HasState(ax::mojom::State::kFocusable);
}
bool ViewAXPlatformNodeDelegate::IsFocusedForTesting() const {
if (ui::AXPlatformNode::GetPopupFocusOverride()) {
return ui::AXPlatformNode::GetPopupFocusOverride() ==
GetNativeViewAccessible();
}
return ViewAccessibility::IsFocusedForTesting();
}
void ViewAXPlatformNodeDelegate::SetPopupFocusOverride() {
ui::AXPlatformNode::SetPopupFocusOverride(GetNativeViewAccessible());
}
void ViewAXPlatformNodeDelegate::EndPopupFocusOverride() {
ui::AXPlatformNode::SetPopupFocusOverride(gfx::NativeViewAccessible());
}
void ViewAXPlatformNodeDelegate::FireFocusAfterMenuClose() {
ui::AXPlatformNodeBase* focused_node =
static_cast<ui::AXPlatformNodeBase*>(ax_platform_node_.get());
while (focused_node) {
ui::AXPlatformNodeBase* deeper_focus = static_cast<ui::AXPlatformNodeBase*>(
ui::AXPlatformNode::FromNativeViewAccessible(focused_node->GetFocus()));
if (!deeper_focus || deeper_focus == focused_node) {
break;
}
focused_node = deeper_focus;
}
if (focused_node) {
if (accessibility_events_callback_) {
accessibility_events_callback_.Run(
this, ax::mojom::Event::kFocusAfterMenuClose);
}
focused_node->NotifyAccessibilityEvent(
ax::mojom::Event::kFocusAfterMenuClose);
}
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() const {
DCHECK(ax_platform_node_);
return ax_platform_node_->GetNativeViewAccessible();
}
void ViewAXPlatformNodeDelegate::FireNativeEvent(ax::mojom::Event event_type) {
DCHECK(ax_platform_node_);
Widget* const widget = view()->GetWidget();
if (!widget || widget->IsClosed()) {
return;
}
if (g_is_flushing) {
return;
}
if (event_type == ax::mojom::Event::kAlert) {
ax_platform_node_->NotifyAccessibilityEvent(event_type);
return;
}
if (accessibility_events_callback_) {
accessibility_events_callback_.Run(this, event_type);
}
if (g_is_queueing_events) {
GetEventQueue().emplace_back(event_type, GetUniqueId());
return;
}
switch (event_type) {
case ax::mojom::Event::kFocusAfterMenuClose: {
DCHECK(!ui::AXPlatformNode::GetPopupFocusOverride())
<< "Must call ViewAccessibility::EndPopupFocusOverride() as menu "
"closes.";
break;
}
case ax::mojom::Event::kFocus: {
if (ui::AXPlatformNode::GetPopupFocusOverride()) {
DCHECK_EQ(ui::AXPlatformNode::GetPopupFocusOverride(),
GetNativeViewAccessible())
<< "If the popup focus override is on, then the kFocus event must "
"match it. Most likely the popup has closed, but did not call "
"ViewAccessibility::EndPopupFocusOverride(), and focus has "
"now moved on.";
}
break;
}
case ax::mojom::Event::kFocusContext: {
PostFlushEventQueueTaskIfNecessary();
break;
}
case ax::mojom::Event::kLiveRegionChanged: {
PostFlushEventQueueTaskIfNecessary();
GetEventQueue().emplace_back(event_type, GetUniqueId());
return;
}
default:
break;
}
ax_platform_node_->NotifyAccessibilityEvent(event_type);
}
#if BUILDFLAG(IS_MAC)
void ViewAXPlatformNodeDelegate::AnnounceTextAs(
const std::u16string& text,
ui::AXPlatformNode::AnnouncementType announcement_type) {
ax_platform_node_->AnnounceTextAs(text, announcement_type);
}
#endif
const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const {
if (atomic_view_ax_tree_manager_) {
atomic_view_ax_tree_manager_->ClearComputedRootData();
}
data_ = ui::AXNodeData();
if (!ignore_missing_widget_for_testing_ && !view()->GetWidget()) {
data_.role = ax::mojom::Role::kUnknown;
data_.SetRestriction(ax::mojom::Restriction::kDisabled);
return data_;
}
GetAccessibleNodeData(&data_);
return data_;
}
size_t ViewAXPlatformNodeDelegate::GetChildCount() const {
if (ViewAccessibility::IsLeaf()) {
return 0;
}
if (!virtual_children().empty()) {
size_t virtual_child_count = 0;
for (const std::unique_ptr<AXVirtualView>& virtual_child :
virtual_children()) {
if (virtual_child->IsIgnored()) {
virtual_child_count += virtual_child->GetChildCount();
} else {
++virtual_child_count;
}
}
return virtual_child_count;
}
const ChildWidgetsResult child_widgets_result = GetChildWidgets();
if (child_widgets_result.is_tab_modal_showing) {
DCHECK_EQ(child_widgets_result.child_widgets.size(), 1U);
return 1;
}
size_t view_child_count = 0;
for (View* child : view()->children()) {
const ViewAccessibility& view_accessibility = child->GetViewAccessibility();
if (view_accessibility.GetIsIgnored()) {
const auto* child_view_delegate =
static_cast<const ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(child_view_delegate);
view_child_count += child_view_delegate->GetChildCount();
} else {
++view_child_count;
}
}
return view_child_count + child_widgets_result.child_widgets.size();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(
size_t index) const {
DCHECK_LT(index, GetChildCount())
<< "|index| should be less than the unignored child count.";
if (IsLeaf()) {
return gfx::NativeViewAccessible();
}
if (!virtual_children().empty()) {
for (const std::unique_ptr<AXVirtualView>& virtual_child :
virtual_children()) {
if (virtual_child->IsIgnored()) {
size_t virtual_child_count = virtual_child->GetChildCount();
if (index < virtual_child_count) {
return virtual_child->ChildAtIndex(index);
}
index -= virtual_child_count;
} else {
if (index == 0) {
return virtual_child->GetNativeObject();
}
--index;
}
}
NOTREACHED() << "|index| should be less than the unignored child count.";
}
const ChildWidgetsResult child_widgets_result = GetChildWidgets();
const std::vector<raw_ptr<Widget, VectorExperimental>>& child_widgets =
child_widgets_result.child_widgets;
if (child_widgets_result.is_tab_modal_showing) {
DCHECK_EQ(index, 0u);
DCHECK_EQ(child_widgets.size(), 1U);
return child_widgets[0]->GetRootView()->GetNativeViewAccessible();
}
for (View* child : view()->children()) {
ViewAccessibility& view_accessibility = child->GetViewAccessibility();
if (view_accessibility.GetIsIgnored()) {
auto* child_view_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(child_view_delegate);
size_t child_count = child_view_delegate->GetChildCount();
if (index < child_count) {
return child_view_delegate->ChildAtIndex(index);
}
index -= child_count;
} else {
if (index == 0) {
return view_accessibility.view()->GetNativeViewAccessible();
}
--index;
}
}
CHECK_LT(index, child_widgets_result.child_widgets.size())
<< "|index| should be less than the unignored child count.";
return child_widgets[index]->GetRootView()->GetNativeViewAccessible();
}
bool ViewAXPlatformNodeDelegate::HasModalDialog() const {
return GetChildWidgets().is_tab_modal_showing;
}
std::wstring ViewAXPlatformNodeDelegate::ComputeListItemNameFromContent()
const {
DCHECK_EQ(GetData().role, ax::mojom::Role::kListItem);
std::string str = "";
for (size_t i = 0, child_count = GetChildCount(); i < child_count; ++i) {
auto* child = ui::AXPlatformNode::FromNativeViewAccessible(ChildAtIndex(i));
if (GetData().role != ax::mojom::Role::kListMarker) {
str += child->GetDelegate()->GetName();
}
}
return base::UTF8ToWide(str);
}
bool ViewAXPlatformNodeDelegate::IsChildOfLeaf() const {
return AXPlatformNodeDelegate::IsChildOfLeaf();
}
const ui::AXSelection ViewAXPlatformNodeDelegate::GetUnignoredSelection()
const {
const ui::AXNodeData& data = GetData();
ui::AXSelection selection;
selection.is_backward = false;
selection.anchor_object_id = GetUniqueId();
selection.anchor_offset =
data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelStart);
selection.anchor_affinity = ax::mojom::TextAffinity::kDownstream;
selection.focus_object_id = GetUniqueId();
selection.focus_offset =
data.GetIntAttribute(ax::mojom::IntAttribute::kTextSelEnd);
selection.focus_affinity = ax::mojom::TextAffinity::kDownstream;
return selection;
}
ui::AXNodePosition::AXPositionInstance
ViewAXPlatformNodeDelegate::CreatePositionAt(
int offset,
ax::mojom::TextAffinity affinity) const {
return CreateTextPositionAt(offset, affinity);
}
ui::AXNodePosition::AXPositionInstance
ViewAXPlatformNodeDelegate::CreateTextPositionAt(
int offset,
ax::mojom::TextAffinity affinity) const {
if (!atomic_view_ax_tree_manager_) {
return ui::AXNodePosition::CreateNullPosition();
}
return ui::AXNodePosition::CreateTextPosition(
*atomic_view_ax_tree_manager_->GetRoot(), offset, affinity);
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNSWindow() {
NOTIMPLEMENTED() << "Should only be called on Mac.";
return gfx::NativeViewAccessible();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeViewAccessible()
const {
return const_cast<ViewAXPlatformNodeDelegate*>(this)
->GetNativeViewAccessible();
}
gfx::NativeViewAccessible
ViewAXPlatformNodeDelegate::GetNativeViewAccessible() {
return view()->GetNativeViewAccessible();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetParent() const {
if (View* parent_view = view()->parent()) {
ViewAccessibility& view_accessibility = parent_view->GetViewAccessibility();
if (!view_accessibility.GetIsIgnored()) {
return parent_view->GetNativeViewAccessible();
}
auto* parent_view_delegate =
static_cast<ViewAXPlatformNodeDelegate*>(&view_accessibility);
DCHECK(parent_view_delegate);
return parent_view_delegate->GetParent();
}
if (Widget* widget = view()->GetWidget()) {
Widget* top_widget = widget->GetTopLevelWidget();
if (top_widget && widget != top_widget && top_widget->GetRootView()) {
return top_widget->GetRootView()->GetNativeViewAccessible();
}
}
return gfx::NativeViewAccessible();
}
bool ViewAXPlatformNodeDelegate::IsLeaf() const {
return ViewAccessibility::IsLeaf() || AXPlatformNodeDelegate::IsLeaf();
}
bool ViewAXPlatformNodeDelegate::IsInvisibleOrIgnored() const {
return GetIsIgnored() || GetData().IsInvisible();
}
bool ViewAXPlatformNodeDelegate::IsFocused() const {
return GetFocus() == GetNativeObject();
}
gfx::Rect ViewAXPlatformNodeDelegate::GetBoundsRect(
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
case ui::AXCoordinateSystem::kScreenDIPs:
return view()->GetBoundsInScreen();
case ui::AXCoordinateSystem::kScreenPhysicalPixels:
case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
return gfx::Rect();
}
}
gfx::Rect ViewAXPlatformNodeDelegate::GetInnerTextRangeBoundsRect(
const int start_offset,
const int end_offset,
const ui::AXCoordinateSystem coordinate_system,
const ui::AXClippingBehavior clipping_behavior,
ui::AXOffscreenResult* offscreen_result) const {
switch (coordinate_system) {
case ui::AXCoordinateSystem::kScreenDIPs: {
gfx::Rect content_bounds = view()->GetContentsBounds();
View::ConvertRectToScreen(view(), &content_bounds);
gfx::RectF bounds = GetInlineTextRect(start_offset, end_offset);
if (ui::IsTextField(data_.role)) {
bounds = RelativeToContainerBounds(bounds, offscreen_result);
if (bounds.IsEmpty() && offscreen_result &&
*offscreen_result == ui::AXOffscreenResult::kOnscreen) {
const int kMinimumWidth = 1;
bounds.set_width(kMinimumWidth);
bounds.set_height(content_bounds.height());
if (base::i18n::IsRTL()) {
bounds.set_x(content_bounds.width() - kMinimumWidth);
}
}
} else if (offscreen_result) {
*offscreen_result = ui::AXOffscreenResult::kOnscreen;
}
bounds.Offset(content_bounds.x(), content_bounds.y());
return gfx::ToEnclosingRect(bounds);
}
case ui::AXCoordinateSystem::kScreenPhysicalPixels:
case ui::AXCoordinateSystem::kRootFrame:
case ui::AXCoordinateSystem::kFrame:
NOTIMPLEMENTED();
return gfx::Rect();
}
}
gfx::RectF ViewAXPlatformNodeDelegate::GetInlineTextRect(
const int start_offset,
const int end_offset) const {
DCHECK_GE(start_offset, 0);
DCHECK_GE(end_offset, 0);
DCHECK_LE(start_offset, end_offset);
const std::vector<int32_t>& character_offsets =
data_.GetIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
if (character_offsets.empty()) {
return gfx::RectF();
}
const size_t adjusted_start =
std::min(static_cast<size_t>(start_offset), character_offsets.size() - 1);
const size_t adjusted_end =
std::min(static_cast<size_t>(end_offset), character_offsets.size() - 1);
const int left_most_offset = std::min(character_offsets[adjusted_start],
character_offsets[adjusted_end]);
const int right_most_offset = std::max(character_offsets[adjusted_start],
character_offsets[adjusted_end]);
if (left_most_offset == right_most_offset) {
return gfx::RectF();
}
return gfx::RectF(left_most_offset, 0, right_most_offset - left_most_offset,
view()->GetContentsBounds().height());
}
gfx::RectF ViewAXPlatformNodeDelegate::RelativeToContainerBounds(
const gfx::RectF& bounds,
ui::AXOffscreenResult* offscreen_result) const {
int scroll_x = data_.GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
gfx::RectF relative_bounds = bounds;
relative_bounds.Offset(scroll_x, 0);
gfx::RectF container_bounds = gfx::RectF(view()->GetBoundsInScreen());
container_bounds.set_origin(gfx::PointF());
gfx::RectF intersection = relative_bounds;
intersection.Intersect(container_bounds);
if (offscreen_result) {
*offscreen_result = !bounds.IsEmpty() && intersection.IsEmpty()
? ui::AXOffscreenResult::kOffscreen
: ui::AXOffscreenResult::kOnscreen;
}
return intersection;
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(
int screen_physical_pixel_x,
int screen_physical_pixel_y) const {
if (!view() || !view()->GetWidget()) {
return gfx::NativeViewAccessible();
}
if (IsLeaf()) {
return GetNativeViewAccessible();
}
gfx::Point point = ScreenToDIPPoint(
gfx::Point(screen_physical_pixel_x, screen_physical_pixel_y));
for (Widget* child_widget : GetChildWidgets().child_widgets) {
View* child_root_view = child_widget->GetRootView();
gfx::Point point_for_child = point;
View::ConvertPointFromScreen(child_root_view, &point_for_child);
if (child_root_view->HitTestPoint(point_for_child)) {
return child_root_view->GetNativeViewAccessible();
}
}
View::ConvertPointFromScreen(view(), &point);
if (!view()->HitTestPoint(point)) {
return gfx::NativeViewAccessible();
}
if (!virtual_children().empty()) {
for (const std::unique_ptr<AXVirtualView>& child :
base::Reversed(virtual_children())) {
gfx::NativeViewAccessible result =
child->HitTestSync(screen_physical_pixel_x, screen_physical_pixel_y);
if (result) {
return result;
}
}
return GetNativeViewAccessible();
}
View* v = view();
const auto is_point_in_child = [point, v](View* child) {
if (!child->GetVisible()) {
return false;
}
ui::AXNodeData child_data;
child->GetViewAccessibility().GetAccessibleNodeData(&child_data);
if (child_data.IsInvisible()) {
return false;
}
gfx::Point point_in_child_coords = point;
v->ConvertPointToTarget(v, child, &point_in_child_coords);
return child->HitTestPoint(point_in_child_coords);
};
const auto i =
std::ranges::find_if(base::Reversed(v->children()), is_point_in_child);
return (i == v->children().rend()) ? GetNativeViewAccessible()
: (*i)->GetNativeViewAccessible();
}
gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetFocus() const {
gfx::NativeViewAccessible focus_override =
ui::AXPlatformNode::GetPopupFocusOverride();
if (focus_override) {
return focus_override;
}
FocusManager* focus_manager = view()->GetFocusManager();
View* focused_view =
focus_manager ? focus_manager->GetFocusedView() : nullptr;
if (!focused_view) {
return gfx::NativeViewAccessible();
}
return focused_view->GetViewAccessibility().GetFocusedDescendant();
}
ui::AXPlatformNode* ViewAXPlatformNodeDelegate::GetFromNodeID(int32_t id) {
return PlatformNodeFromNodeID(id);
}
ui::AXPlatformNode* ViewAXPlatformNodeDelegate::GetFromTreeIDAndNodeID(
const ui::AXTreeID& ax_tree_id,
int32_t id) {
if (atomic_view_ax_tree_manager_) {
return atomic_view_ax_tree_manager_->GetPlatformNodeFromTree(id);
}
return nullptr;
}
bool ViewAXPlatformNodeDelegate::AccessibilityPerformAction(
const ui::AXActionData& data) {
return view()->HandleAccessibleAction(data);
}
bool ViewAXPlatformNodeDelegate::ShouldIgnoreHoveredStateForTesting() {
return false;
}
bool ViewAXPlatformNodeDelegate::IsOffscreen() const {
return false;
}
std::u16string ViewAXPlatformNodeDelegate::GetAuthorUniqueId() const {
const View* v = view();
if (v) {
const int view_id = v->GetID();
if (view_id) {
return u"view_" + base::NumberToString16(view_id);
}
}
return std::u16string();
}
bool ViewAXPlatformNodeDelegate::IsMinimized() const {
Widget* widget = view()->GetWidget();
return widget && widget->IsMinimized();
}
bool ViewAXPlatformNodeDelegate::IsReadOnlySupported() const {
return ui::IsReadOnlySupported(GetData().role);
}
bool ViewAXPlatformNodeDelegate::IsReadOnlyOrDisabled() const {
switch (GetData().GetRestriction()) {
case ax::mojom::Restriction::kReadOnly:
case ax::mojom::Restriction::kDisabled:
return true;
case ax::mojom::Restriction::kNone: {
if (HasState(ax::mojom::State::kEditable) ||
HasState(ax::mojom::State::kRichlyEditable)) {
return false;
}
if (ui::ShouldHaveReadonlyStateByDefault(GetData().role)) {
return true;
}
return !IsReadOnlySupported();
}
}
}
ui::AXPlatformNodeId ViewAXPlatformNodeDelegate::GetUniqueId() const {
return ViewAccessibility::GetUniqueId();
}
AtomicViewAXTreeManager*
ViewAXPlatformNodeDelegate::GetAtomicViewAXTreeManagerForTesting() const {
return atomic_view_ax_tree_manager_.get();
}
gfx::Point ViewAXPlatformNodeDelegate::ScreenToDIPPoint(
const gfx::Point& screen_point) const {
if (!view() || !view()->GetWidget()) {
return screen_point;
}
gfx::NativeView native_view = view()->GetWidget()->GetNativeView();
float scale_factor = 1.0;
if (native_view) {
scale_factor = ui::GetScaleFactorForNativeView(native_view);
scale_factor = scale_factor <= 0 ? 1.0 : scale_factor;
}
return gfx::Point(screen_point.x() / scale_factor,
screen_point.y() / scale_factor);
}
std::vector<int32_t> ViewAXPlatformNodeDelegate::GetColHeaderNodeIds() const {
std::vector<int32_t> col_header_ids;
if (!virtual_children().empty()) {
for (const std::unique_ptr<AXVirtualView>& header_cell :
virtual_children().front()->children()) {
const ui::AXNodeData& header_data = header_cell->GetData();
if (header_data.role == ax::mojom::Role::kColumnHeader) {
col_header_ids.push_back(header_data.id);
}
}
}
return col_header_ids;
}
std::vector<int32_t> ViewAXPlatformNodeDelegate::GetColHeaderNodeIds(
int col_index) const {
std::vector<int32_t> columns = GetColHeaderNodeIds();
if (static_cast<size_t>(col_index) >= columns.size()) {
return {};
}
return {columns[static_cast<size_t>(col_index)]};
}
std::optional<int32_t> ViewAXPlatformNodeDelegate::GetCellId(
int row_index,
int col_index) const {
if (virtual_children().empty() || !GetAncestorTableView()) {
return std::nullopt;
}
AXVirtualView* ax_cell = GetAncestorTableView()->GetVirtualAccessibilityCell(
static_cast<size_t>(row_index), static_cast<size_t>(col_index));
if (!ax_cell) {
return std::nullopt;
}
const ui::AXNodeData& cell_data = ax_cell->GetData();
if (cell_data.role == ax::mojom::Role::kCell ||
cell_data.role == ax::mojom::Role::kGridCell) {
return cell_data.id;
}
return std::nullopt;
}
TableView* ViewAXPlatformNodeDelegate::GetAncestorTableView() const {
ui::AXNodeData data;
view()->GetViewAccessibility().GetAccessibleNodeData(&data);
if (!ui::IsTableLike(data.role)) {
return nullptr;
}
return static_cast<TableView*>(view());
}
bool ViewAXPlatformNodeDelegate::IsOrderedSetItem() const {
const ui::AXNodeData& data = GetData();
return (view()->GetGroup() >= 0) ||
(data.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet) &&
data.HasIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
bool ViewAXPlatformNodeDelegate::IsOrderedSet() const {
return (view()->GetGroup() >= 0) ||
GetData().HasIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
std::optional<int> ViewAXPlatformNodeDelegate::GetPosInSet() const {
const ui::AXNodeData& data = GetData();
if (data.HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
return data.GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
}
std::vector<raw_ptr<View, VectorExperimental>> views_in_group;
GetViewsInGroupForSet(&views_in_group);
if (views_in_group.empty()) {
return std::nullopt;
}
auto found_view = std::ranges::find(views_in_group, view());
if (found_view == views_in_group.end()) {
return std::nullopt;
}
int pos_in_set = base::checked_cast<int>(
std::distance(views_in_group.begin(), found_view));
return ++pos_in_set;
}
std::optional<int> ViewAXPlatformNodeDelegate::GetSetSize() const {
const ui::AXNodeData& data = GetData();
if (data.HasIntAttribute(ax::mojom::IntAttribute::kSetSize)) {
return data.GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
std::vector<raw_ptr<View, VectorExperimental>> views_in_group;
GetViewsInGroupForSet(&views_in_group);
if (views_in_group.empty()) {
return std::nullopt;
}
auto found_view = std::ranges::find(views_in_group, view());
if (found_view == views_in_group.end()) {
return std::nullopt;
}
return base::checked_cast<int>(views_in_group.size());
}
void ViewAXPlatformNodeDelegate::GetViewsInGroupForSet(
std::vector<raw_ptr<View, VectorExperimental>>* views_in_group) const {
const int group_id = view()->GetGroup();
if (group_id < 0) {
return;
}
View* view_to_check = view();
if (view()->parent()) {
view_to_check = view()->parent();
}
view_to_check->GetViewsInGroup(group_id, views_in_group);
std::erase_if(*views_in_group, [](View* view) {
return view->GetViewAccessibility().GetIsIgnored();
});
}
bool ViewAXPlatformNodeDelegate::TableHasColumnOrRowHeaderNodeForTesting()
const {
if (!GetAncestorTableView()) {
return false;
}
return !GetAncestorTableView()->visible_columns().empty();
}
ViewAXPlatformNodeDelegate::ChildWidgetsResult
ViewAXPlatformNodeDelegate::GetChildWidgets() const {
Widget* widget = view()->GetWidget();
if (!widget || !widget->GetNativeView() || widget->GetRootView() != view()) {
return ChildWidgetsResult();
}
Widget::Widgets owned_widgets =
Widget::GetAllOwnedWidgets(widget->GetNativeView());
std::vector<raw_ptr<Widget, VectorExperimental>> visible_widgets;
std::ranges::copy_if(owned_widgets, std::back_inserter(visible_widgets),
&Widget::IsVisible);
const FocusManager* focus_manager = view()->GetFocusManager();
const View* focused_view =
focus_manager ? focus_manager->GetFocusedView() : nullptr;
const auto is_focused_child = [focused_view](Widget* child_widget) {
return ViewAccessibilityUtils::IsFocusedChildWidget(child_widget,
focused_view);
};
const auto i = std::ranges::find_if(visible_widgets, is_focused_child);
if (i != visible_widgets.cend()) {
return ChildWidgetsResult({*i}, true );
}
return ChildWidgetsResult(visible_widgets, false );
}
}