#include "ui/views/accessibility/view_accessibility.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/notimplemented.h"
#include "base/strings/utf_string_conversions.h"
#include "build/buildflag.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_platform.h"
#include "ui/accessibility/platform/ax_platform_node.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
#include "ui/base/buildflags.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/accessibility/atomic_view_ax_tree_manager.h"
#include "ui/views/accessibility/ax_update_notifier.h"
#include "ui/views/accessibility/ax_virtual_view.h"
#include "ui/views/view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_WIN)
#include "ui/views/accessibility/view_ax_platform_node_delegate_win.h"
#elif BUILDFLAG(IS_MAC)
#include "ui/views/accessibility/view_ax_platform_node_delegate_mac.h"
#elif BUILDFLAG(IS_LINUX)
#include "ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h"
#endif
namespace views {
namespace {
bool IsValidRoleForViews(ax::mojom::Role role) {
switch (role) {
case ax::mojom::Role::kDesktop:
case ax::mojom::Role::kIframe:
case ax::mojom::Role::kIframePresentational:
case ax::mojom::Role::kPdfRoot:
case ax::mojom::Role::kRootWebArea:
case ax::mojom::Role::kSvgRoot:
case ax::mojom::Role::kUnknown:
return false;
case ax::mojom::Role::kDocument:
return true;
default:
return true;
}
}
}
#define RETURN_IF_UNAVAILABLE() \
if (is_widget_closed_) { \
return; \
} \
CHECK(initialization_state_ != State::kInitializing) \
<< "Accessibility cache setters must not be used during complete " \
"initialization of the accessibility cache. Instead, set the " \
"attributes directly on `AXNodeData` parameter.";
std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) {
if (::features::IsAccessibilityTreeForViewsEnabled()) {
return base::WrapUnique(new ViewAccessibility(view));
}
#if !BUILDFLAG(HAS_NATIVE_ACCESSIBILITY)
return base::WrapUnique(new ViewAccessibility(view));
#elif BUILDFLAG(IS_WIN)
return ViewAXPlatformNodeDelegateWin::CreatePlatformSpecific(view);
#elif BUILDFLAG(IS_MAC)
return ViewAXPlatformNodeDelegateMac::CreatePlatformSpecific(view);
#elif BUILDFLAG(IS_LINUX)
return ViewAXPlatformNodeDelegateAuraLinux::CreatePlatformSpecific(view);
#endif
}
ViewAccessibility::ViewAccessibility(View* view)
: view_(view), focused_virtual_child_(nullptr) {
data_.id = GetUniqueId();
CHECK(data_.id != ui::kInvalidAXNodeID);
}
ViewAccessibility::~ViewAccessibility() = default;
void ViewAccessibility::AddVirtualChildView(
std::unique_ptr<AXVirtualView> virtual_view) {
AddVirtualChildViewAt(std::move(virtual_view), virtual_children_.size());
}
void ViewAccessibility::AddVirtualChildViewAt(
std::unique_ptr<AXVirtualView> virtual_view,
size_t index) {
CHECK(view_);
DCHECK(virtual_view);
DCHECK_LE(index, virtual_children_.size());
if (virtual_view->parent_view() == this) {
return;
}
DCHECK(!virtual_view->parent_view()) << "This |view| already has a View "
"parent. Call RemoveVirtualChildView "
"first.";
DCHECK(!virtual_view->virtual_parent_view()) << "This |view| already has an "
"AXVirtualView parent. Call "
"RemoveChildView first.";
virtual_view->set_parent_view(this);
auto insert_iterator =
virtual_children_.begin() + static_cast<ptrdiff_t>(index);
virtual_children_.insert(insert_iterator, std::move(virtual_view));
AXVirtualView* added_view = virtual_children_[index].get();
added_view->OnViewHasNewAncestor(view_);
}
std::unique_ptr<AXVirtualView> ViewAccessibility::RemoveVirtualChildView(
AXVirtualView* virtual_view) {
DCHECK(virtual_view);
auto cur_index = GetIndexOf(virtual_view);
if (!cur_index.has_value()) {
return {};
}
std::unique_ptr<AXVirtualView> child =
std::move(virtual_children_[cur_index.value()]);
virtual_children_.erase(virtual_children_.begin() +
static_cast<ptrdiff_t>(cur_index.value()));
child->set_parent_view(nullptr);
if (focused_virtual_child_ && child->Contains(focused_virtual_child_)) {
OverrideFocus(nullptr);
}
return child;
}
void ViewAccessibility::RemoveAllVirtualChildViews() {
while (!virtual_children_.empty()) {
RemoveVirtualChildView(virtual_children_.back().get());
}
}
bool ViewAccessibility::Contains(const AXVirtualView* virtual_view) const {
DCHECK(virtual_view);
for (const auto& virtual_child : virtual_children_) {
if (virtual_child->Contains(virtual_view)) {
return true;
}
}
return false;
}
std::optional<size_t> ViewAccessibility::GetIndexOf(
const AXVirtualView* virtual_view) const {
DCHECK(virtual_view);
const auto iter = std::ranges::find(virtual_children_, virtual_view,
&std::unique_ptr<AXVirtualView>::get);
return iter != virtual_children_.end()
? std::make_optional(
static_cast<size_t>(iter - virtual_children_.begin()))
: std::nullopt;
}
void ViewAccessibility::GetAccessibleNodeData(ui::AXNodeData* data) const {
if (is_widget_closed_) {
SetDataForClosedWidget(data);
return;
}
*data = data_;
}
void ViewAccessibility::NotifyEvent(ax::mojom::Event event_type,
bool send_native_event) {
CHECK(view_);
if (!ready_to_notify_events_) {
return;
}
Widget* const widget = view_->GetWidget();
if (widget && !widget->GetNativeView()) {
return;
}
AXUpdateNotifier::Get()->NotifyViewEvent(view_, event_type);
if (send_native_event && widget) {
FireNativeEvent(event_type);
}
view_->OnAccessibilityEvent(event_type);
}
void ViewAccessibility::OverrideFocus(AXVirtualView* virtual_view) {
DCHECK(!virtual_view || Contains(virtual_view))
<< "|virtual_view| must be nullptr or a descendant of this view.";
focused_virtual_child_ = virtual_view;
if (view_->HasFocus()) {
if (focused_virtual_child_) {
focused_virtual_child_->NotifyEvent(ax::mojom::Event::kFocus, true);
} else {
NotifyEvent(ax::mojom::Event::kFocus, true);
}
}
}
bool ViewAccessibility::IsAccessibilityFocusable() const {
bool focusable = data_.HasState(ax::mojom::State::kFocusable);
if (focusable) {
CHECK(!should_be_invisible_ &&
!data_.HasState(ax::mojom::State::kInvisible))
<< "A view that is focusable should not be marked as invisible. This is"
"also enforced in RunAccessibilityPaintChecks.";
}
return focusable;
}
bool ViewAccessibility::IsFocusedForTesting() const {
return view_->HasFocus() && !focused_virtual_child_;
}
void ViewAccessibility::SetPopupFocusOverride() {
NOTIMPLEMENTED();
}
void ViewAccessibility::EndPopupFocusOverride() {
NOTIMPLEMENTED();
}
void ViewAccessibility::FireFocusAfterMenuClose() {
NotifyEvent(ax::mojom::Event::kFocusAfterMenuClose, true);
}
void ViewAccessibility::SetIsLeaf(bool value) {
if (value == ViewAccessibility::IsLeaf()) {
return;
}
if (value) {
PruneSubtree();
} else {
UnpruneSubtree();
}
is_leaf_ = value;
}
bool ViewAccessibility::IsLeaf() const {
return is_leaf_;
}
bool ViewAccessibility::IsChildOfLeaf() const {
return pruned_;
}
void ViewAccessibility::SetReadOnly(bool read_only) {
if ((read_only &&
data_.GetRestriction() == ax::mojom::Restriction::kReadOnly) ||
(!read_only &&
data_.GetRestriction() != ax::mojom::Restriction::kReadOnly)) {
return;
}
if (read_only) {
data_.SetRestriction(ax::mojom::Restriction::kReadOnly);
} else {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kRestriction);
}
NotifyDataChanged();
}
bool ViewAccessibility::GetIsPruned() const {
return pruned_;
}
void ViewAccessibility::SetCharacterOffsets(
const std::vector<int32_t>& offsets) {
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets,
offsets);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kCharacterOffsets,
offsets);
NotifyDataChanged();
}
const std::vector<int32_t>& ViewAccessibility::GetCharacterOffsets() const {
return data_.GetIntListAttribute(
ax::mojom::IntListAttribute::kCharacterOffsets);
}
void ViewAccessibility::SetWordStarts(const std::vector<int32_t>& offsets) {
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts, offsets);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordStarts, offsets);
NotifyDataChanged();
}
const std::vector<int32_t>& ViewAccessibility::GetWordStarts() const {
return data_.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
}
void ViewAccessibility::SetWordEnds(const std::vector<int32_t>& offsets) {
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds, offsets);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordEnds, offsets);
NotifyDataChanged();
}
const std::vector<int32_t>& ViewAccessibility::GetWordEnds() const {
return data_.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
}
void ViewAccessibility::ClearTextOffsets() {
data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kCharacterOffsets);
data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kCharacterOffsets,
std::nullopt);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordStarts,
std::nullopt);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kWordEnds,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::SetControlIds(const std::vector<int32_t>& ids) {
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, ids);
NotifyDataChanged();
}
void ViewAccessibility::RemoveControlIds() {
data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kControlsIds);
NotifyDataChanged();
}
void ViewAccessibility::SetClipsChildren(bool clips_children) {
data_.AddBoolAttribute(ax::mojom::BoolAttribute::kClipsChildren,
clips_children);
OnBoolAttributeChanged(ax::mojom::BoolAttribute::kClipsChildren,
clips_children);
NotifyDataChanged();
}
void ViewAccessibility::SetClassName(const std::string& class_name) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kClassName, class_name);
OnStringAttributeChanged(ax::mojom::StringAttribute::kClassName, class_name);
NotifyDataChanged();
}
void ViewAccessibility::SetHasPopup(const ax::mojom::HasPopup has_popup) {
data_.SetHasPopup(has_popup);
NotifyDataChanged();
}
void ViewAccessibility::SetRole(const ax::mojom::Role role) {
RETURN_IF_UNAVAILABLE();
DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views.";
if (role == GetCachedRole()) {
return;
}
data_.role = role;
if (data_.role == ax::mojom::Role::kAlertDialog) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kRole, "alertdialog");
} else {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kRole);
}
UpdateIgnoredState();
UpdateInvisibleState();
OnRoleChanged(role);
NotifyDataChanged();
}
void ViewAccessibility::SetRole(const ax::mojom::Role role,
const std::u16string& role_description) {
RETURN_IF_UNAVAILABLE();
SetRole(role);
SetRoleDescription(role_description);
}
void ViewAccessibility::SetName(std::u16string name,
ax::mojom::NameFrom name_from) {
RETURN_IF_UNAVAILABLE();
if (view_) {
view_->AdjustAccessibleName(name, name_from);
}
data_.SetNameFrom(name_from);
if (name == GetCachedName()) {
return;
}
std::u16string old_name = GetCachedName();
if (name.empty()) {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kName);
} else {
data_.SetNameChecked(name);
}
if (view_) {
if (!old_name.empty() && old_name == view_->GetTooltipText()) {
OnTooltipTextChanged();
}
if (GetCachedName() == view_->GetTooltipText() &&
GetCachedDescription() == view_->GetTooltipText()) {
RemoveDescription();
}
view_->OnAccessibleNameChanged(name);
}
OnStringAttributeChanged(ax::mojom::StringAttribute::kName,
base::UTF16ToUTF8(name));
NotifyEvent(ax::mojom::Event::kTextChanged, true);
NotifyDataChanged();
}
void ViewAccessibility::SetName(std::string_view name,
ax::mojom::NameFrom name_from) {
SetName(base::UTF8ToUTF16(name), name_from);
}
void ViewAccessibility::SetName(std::string_view name) {
SetName(name, GetCachedNameFrom());
}
void ViewAccessibility::SetName(std::u16string name) {
SetName(std::move(name), GetCachedNameFrom());
}
void ViewAccessibility::SetName(View& naming_view) {
DCHECK_NE(view_, &naming_view);
std::u16string name = naming_view.GetViewAccessibility().GetCachedName();
DCHECK(!name.empty());
SetName(std::move(name), ax::mojom::NameFrom::kRelatedElement);
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
{naming_view.GetViewAccessibility().GetUniqueId()});
NotifyDataChanged();
}
void ViewAccessibility::RemoveName() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kName);
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kNameFrom);
OnStringAttributeChanged(ax::mojom::StringAttribute::kName, std::nullopt);
NotifyDataChanged();
}
std::u16string ViewAccessibility::GetCachedName() const {
return data_.GetString16Attribute(ax::mojom::StringAttribute::kName);
}
ax::mojom::NameFrom ViewAccessibility::GetCachedNameFrom() const {
return static_cast<ax::mojom::NameFrom>(
data_.GetIntAttribute(ax::mojom::IntAttribute::kNameFrom));
}
ax::mojom::Role ViewAccessibility::GetCachedRole() const {
return data_.role;
}
void ViewAccessibility::SetRoleDescription(
const std::u16string& role_description) {
if (role_description == data_.GetString16Attribute(
ax::mojom::StringAttribute::kRoleDescription)) {
return;
}
if (!role_description.empty()) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
base::UTF16ToUTF8(role_description));
} else {
RemoveRoleDescription();
}
OnStringAttributeChanged(ax::mojom::StringAttribute::kRoleDescription,
base::UTF16ToUTF8(role_description));
NotifyDataChanged();
}
void ViewAccessibility::SetRoleDescription(
const std::string& role_description) {
SetRoleDescription(base::UTF8ToUTF16(role_description));
}
std::u16string ViewAccessibility::GetRoleDescription() const {
return data_.GetString16Attribute(
ax::mojom::StringAttribute::kRoleDescription);
}
void ViewAccessibility::RemoveRoleDescription() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kRoleDescription);
OnStringAttributeChanged(ax::mojom::StringAttribute::kRoleDescription,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::SetIsEditable(bool editable) {
SetState(ax::mojom::State::kEditable, editable);
}
void ViewAccessibility::SetBounds(const gfx::RectF& bounds) {
if (bounds == data_.relative_bounds.bounds) {
return;
}
data_.relative_bounds.bounds = bounds;
NotifyEvent(ax::mojom::Event::kLocationChanged, false);
NotifyDataChanged();
}
void ViewAccessibility::SetPosInSet(int pos_in_set) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, pos_in_set);
OnIntAttributeChanged(ax::mojom::IntAttribute::kPosInSet, pos_in_set);
NotifyDataChanged();
}
void ViewAccessibility::SetSetSize(int set_size) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size);
OnIntAttributeChanged(ax::mojom::IntAttribute::kSetSize, set_size);
NotifyDataChanged();
}
void ViewAccessibility::ClearPosInSet() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kPosInSet);
OnIntAttributeChanged(ax::mojom::IntAttribute::kPosInSet, 0);
NotifyDataChanged();
}
void ViewAccessibility::ClearSetSize() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kSetSize);
OnIntAttributeChanged(ax::mojom::IntAttribute::kSetSize, 0);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollX(int scroll_x) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollX, scroll_x);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollX, scroll_x);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollXMin(int scroll_x_min) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMin, scroll_x_min);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollXMin, scroll_x_min);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollXMax(int scroll_x_max) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollXMax, scroll_x_max);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollXMax, scroll_x_max);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollY(int scroll_y) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollY, scroll_y);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollY, scroll_y);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollYMin(int scroll_y_min) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMin, scroll_y_min);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollYMin, scroll_y_min);
NotifyDataChanged();
}
void ViewAccessibility::SetScrollYMax(int scroll_y_max) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kScrollYMax, scroll_y_max);
OnIntAttributeChanged(ax::mojom::IntAttribute::kScrollYMax, scroll_y_max);
NotifyDataChanged();
}
void ViewAccessibility::SetIsScrollable(bool is_scrollable) {
data_.AddBoolAttribute(ax::mojom::BoolAttribute::kScrollable, is_scrollable);
OnBoolAttributeChanged(ax::mojom::BoolAttribute::kScrollable, is_scrollable);
NotifyDataChanged();
}
void ViewAccessibility::SetActiveDescendant(views::View& view) {
SetActiveDescendant(view.GetViewAccessibility().GetUniqueId());
}
void ViewAccessibility::SetActiveDescendant(ui::AXPlatformNodeId id) {
if (data_.GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId) ==
id) {
return;
}
data_.AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId, id);
OnIntAttributeChanged(ax::mojom::IntAttribute::kActivedescendantId, id);
NotifyEvent(ax::mojom::Event::kActiveDescendantChanged, true);
NotifyDataChanged();
}
void ViewAccessibility::ClearActiveDescendant() {
if (!data_.HasIntAttribute(ax::mojom::IntAttribute::kActivedescendantId)) {
return;
}
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kActivedescendantId);
OnIntAttributeChanged(ax::mojom::IntAttribute::kActivedescendantId,
std::nullopt);
NotifyEvent(ax::mojom::Event::kActiveDescendantChanged, true);
NotifyDataChanged();
}
void ViewAccessibility::SetIsInvisible(bool is_invisible) {
if (is_invisible == should_be_invisible_) {
return;
}
should_be_invisible_ = is_invisible;
UpdateInvisibleState();
}
void ViewAccessibility::SetIsDefault(bool is_default) {
if (data_.HasState(ax::mojom::State::kDefault) == is_default) {
return;
}
SetState(ax::mojom::State::kDefault, is_default);
}
bool ViewAccessibility::GetIsDefault() const {
return data_.HasState(ax::mojom::State::kDefault);
}
void ViewAccessibility::SetIsEnabled(bool is_enabled) {
RETURN_IF_UNAVAILABLE();
if (is_enabled == GetIsEnabled()) {
return;
}
if (!is_enabled) {
data_.SetRestriction(ax::mojom::Restriction::kDisabled);
} else if (data_.GetRestriction() == ax::mojom::Restriction::kDisabled) {
data_.SetRestriction(ax::mojom::Restriction::kNone);
}
for (auto& virtual_child : virtual_children()) {
virtual_child->SetIsEnabledRecursive(is_enabled);
}
UpdateFocusableState();
OnIntAttributeChanged(ax::mojom::IntAttribute::kRestriction,
static_cast<int32_t>(data_.GetRestriction()));
NotifyEvent(ax::mojom::Event::kStateChanged, true);
NotifyDataChanged();
}
bool ViewAccessibility::GetIsEnabled() const {
return data_.GetRestriction() != ax::mojom::Restriction::kDisabled;
}
void ViewAccessibility::SetTableRowCount(int row_count) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableRowCount, row_count);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowCount, row_count);
NotifyDataChanged();
}
void ViewAccessibility::SetTableColumnCount(int column_count) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableColumnCount,
column_count);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableColumnCount,
column_count);
NotifyDataChanged();
}
void ViewAccessibility::SetAriaTableRowCount(int row_count) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kAriaRowCount, row_count);
OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaRowCount, row_count);
NotifyDataChanged();
}
void ViewAccessibility::SetAriaTableColumnCount(int column_count) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kAriaColumnCount,
column_count);
OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaColumnCount,
column_count);
NotifyDataChanged();
}
void ViewAccessibility::ClearTableRowCount() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kTableRowCount);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowCount, std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::ClearTableColumnCount() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kTableColumnCount);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableColumnCount,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::ClearAriaTableRowCount() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kAriaRowCount);
OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaRowCount, std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::ClearAriaTableColumnCount() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kAriaColumnCount);
OnIntAttributeChanged(ax::mojom::IntAttribute::kAriaColumnCount,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::SetTableRowIndex(int cell_index) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableRowIndex, cell_index);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableRowIndex, cell_index);
NotifyDataChanged();
}
int ViewAccessibility::GetTableRowIndex() const {
return data_.GetIntAttribute(ax::mojom::IntAttribute::kTableRowIndex);
}
void ViewAccessibility::SetTableCellColumnIndex(int cell_index) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex,
cell_index);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellColumnIndex,
cell_index);
NotifyDataChanged();
}
void ViewAccessibility::SetTableCellRowIndex(int row_index) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex, row_index);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellRowIndex, row_index);
NotifyDataChanged();
}
void ViewAccessibility::SetTableCellRowSpan(int row_span) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, row_span);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellRowSpan, row_span);
NotifyDataChanged();
}
void ViewAccessibility::SetTableCellColumnSpan(int column_span) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
column_span);
OnIntAttributeChanged(ax::mojom::IntAttribute::kTableCellColumnSpan,
column_span);
NotifyDataChanged();
}
void ViewAccessibility::SetSortDirection(
ax::mojom::SortDirection sort_direction) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kSortDirection,
static_cast<int>(sort_direction));
OnIntAttributeChanged(ax::mojom::IntAttribute::kSortDirection,
static_cast<int>(sort_direction));
NotifyDataChanged();
}
void ViewAccessibility::ClearDescriptionAndDescriptionFrom() {
data_.SetDescriptionExplicitlyEmpty();
OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::RemoveDescription() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kDescription);
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom);
OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
std::nullopt);
OnIntAttributeChanged(ax::mojom::IntAttribute::kDescriptionFrom,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::SetDescription(
const std::string& description,
const ax::mojom::DescriptionFrom description_from) {
if (description.empty() &&
description_from !=
ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty) {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kDescription);
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDescriptionFrom);
return;
}
data_.SetDescriptionFrom(description_from);
data_.SetDescription(description);
OnStringAttributeChanged(ax::mojom::StringAttribute::kDescription,
description);
OnIntAttributeChanged(ax::mojom::IntAttribute::kDescriptionFrom,
static_cast<int>(description_from));
NotifyDataChanged();
}
void ViewAccessibility::SetDescription(
const std::u16string& description,
const ax::mojom::DescriptionFrom description_from) {
SetDescription(base::UTF16ToUTF8(description), description_from);
}
void ViewAccessibility::SetDescription(View& describing_view) {
CHECK(view_);
DCHECK_NE(view_, &describing_view);
std::u16string name = describing_view.GetViewAccessibility().GetCachedName();
DCHECK(!name.empty())
<< "The describing view must have an accessible name set.";
SetDescription(name, ax::mojom::DescriptionFrom::kRelatedElement);
std::vector<int32_t> ids = {
describing_view.GetViewAccessibility().GetUniqueId()};
data_.AddIntListAttribute(ax::mojom::IntListAttribute::kDescribedbyIds, ids);
OnIntListAttributeChanged(ax::mojom::IntListAttribute::kDescribedbyIds, ids);
NotifyDataChanged();
}
std::u16string ViewAccessibility::GetCachedDescription() const {
if (data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
return base::UTF8ToUTF16(
data_.GetStringAttribute(ax::mojom::StringAttribute::kDescription));
}
return std::u16string();
}
void ViewAccessibility::OnTooltipTextChanged(
std::optional<std::u16string> old_tooltip_text) {
if (!view_) {
return;
}
if (data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription) &&
view_->GetTooltipText() == GetCachedDescription()) {
return;
}
const std::u16string tooltip = view_->GetTooltipText();
if ((old_tooltip_text.has_value() &&
old_tooltip_text == GetCachedDescription()) ||
!data_.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
if (!tooltip.empty() && tooltip != GetCachedName()) {
SetDescription(tooltip);
} else {
RemoveDescription();
}
}
}
void ViewAccessibility::OnViewAddedToWidget() {
if (ViewAccessibility* parent = GetUnignoredParent()) {
AXUpdateNotifier::Get()->NotifyChildAdded(this, parent);
}
std::string effective_class = std::string(view_->GetClassName());
#if BUILDFLAG(IS_WIN)
if (::ui::AXPlatform::GetInstance().IsUiaProviderEnabled() &&
features::IsFixNarratorWebContentContainmentEnabled() &&
effective_class == "ContentsContainerView") {
effective_class = "Chrome_WidgetWin_1";
}
#endif
SetClassName(effective_class);
}
void ViewAccessibility::OnViewRemovedFromWidget() {
if (ViewAccessibility* parent = GetUnignoredParent()) {
AXUpdateNotifier::Get()->NotifyChildRemoved(this, parent);
}
}
void ViewAccessibility::SetPlaceholder(const std::string& placeholder) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kPlaceholder,
placeholder);
NotifyDataChanged();
}
void ViewAccessibility::AddAction(ax::mojom::Action action) {
if (data_.HasAction(action)) {
return;
}
data_.AddAction(action);
NotifyDataChanged();
}
void ViewAccessibility::SetCheckedState(ax::mojom::CheckedState checked_state) {
if (checked_state == data_.GetCheckedState()) {
return;
}
data_.SetCheckedState(checked_state);
OnIntAttributeChanged(ax::mojom::IntAttribute::kCheckedState,
static_cast<int>(checked_state));
NotifyEvent(ax::mojom::Event::kCheckedStateChanged, true);
NotifyDataChanged();
}
ax::mojom::CheckedState ViewAccessibility::GetCheckedState() const {
return data_.GetCheckedState();
}
void ViewAccessibility::RemoveCheckedState() {
if (data_.HasCheckedState()) {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kCheckedState);
OnIntAttributeChanged(ax::mojom::IntAttribute::kCheckedState, std::nullopt);
NotifyDataChanged();
}
}
void ViewAccessibility::SetKeyShortcuts(const std::string& key_shortcuts) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts,
key_shortcuts);
NotifyDataChanged();
}
void ViewAccessibility::RemoveKeyShortcuts() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kKeyShortcuts);
NotifyDataChanged();
}
void ViewAccessibility::SetAccessKey(const std::string& access_key) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kAccessKey, access_key);
NotifyDataChanged();
}
void ViewAccessibility::RemoveAccessKey() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kAccessKey);
NotifyDataChanged();
}
void ViewAccessibility::SetChildTreeNodeAppId(const std::string& app_id) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kChildTreeNodeAppId,
app_id);
NotifyDataChanged();
}
void ViewAccessibility::RemoveChildTreeNodeAppId() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kChildTreeNodeAppId);
NotifyDataChanged();
}
void ViewAccessibility::SetIsSelected(bool selected) {
if (data_.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected) &&
selected == data_.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
return;
}
data_.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected);
OnBoolAttributeChanged(ax::mojom::BoolAttribute::kSelected, selected);
if (selected) {
NotifyEvent(ax::mojom::Event::kSelection, true);
}
NotifyDataChanged();
}
void ViewAccessibility::SetIsMultiselectable(bool multiselectable) {
SetState(ax::mojom::State::kMultiselectable, multiselectable);
NotifyDataChanged();
}
void ViewAccessibility::SetIsModal(bool modal) {
data_.AddBoolAttribute(ax::mojom::BoolAttribute::kModal, modal);
NotifyDataChanged();
}
void ViewAccessibility::AddHTMLAttributes(
std::pair<std::string, std::string> attribute) {
data_.html_attributes.push_back(attribute);
NotifyDataChanged();
}
void ViewAccessibility::SetIsIgnored(bool is_ignored) {
if (is_ignored == should_be_ignored_) {
return;
}
should_be_ignored_ = is_ignored;
UpdateIgnoredState();
NotifyEvent(ax::mojom::Event::kTreeChanged, true);
}
bool ViewAccessibility::GetIsIgnored() const {
return data_.HasState(ax::mojom::State::kIgnored);
}
void ViewAccessibility::OverrideNativeWindowTitle(const std::string& title) {
NOTIMPLEMENTED() << "Only implemented on Mac for now.";
}
void ViewAccessibility::OverrideNativeWindowTitle(const std::u16string& title) {
OverrideNativeWindowTitle(base::UTF16ToUTF8(title));
}
void ViewAccessibility::SetNextFocus(Widget* widget) {
if (widget) {
next_focus_ = widget->GetWeakPtr();
} else {
next_focus_ = nullptr;
}
}
void ViewAccessibility::SetPreviousFocus(Widget* widget) {
if (widget) {
previous_focus_ = widget->GetWeakPtr();
} else {
previous_focus_ = nullptr;
}
}
Widget* ViewAccessibility::GetNextWindowFocus() const {
return next_focus_.get();
}
Widget* ViewAccessibility::GetPreviousWindowFocus() const {
return previous_focus_.get();
}
void ViewAccessibility::SetShowContextMenu(bool show_context_menu) {
if (show_context_menu) {
data_.AddAction(ax::mojom::Action::kShowContextMenu);
} else {
data_.RemoveAction(ax::mojom::Action::kShowContextMenu);
}
for (auto& virtual_child : virtual_children()) {
virtual_child->SetShowContextMenuRecursive(show_context_menu);
}
NotifyDataChanged();
}
void ViewAccessibility::SetContainerLiveStatus(const std::string& status) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus,
status);
NotifyDataChanged();
}
void ViewAccessibility::RemoveContainerLiveStatus() {
if (!data_.HasStringAttribute(
ax::mojom::StringAttribute::kContainerLiveStatus)) {
return;
}
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kContainerLiveStatus);
NotifyDataChanged();
}
void ViewAccessibility::SetValue(const std::string& value) {
if (value == data_.GetStringAttribute(ax::mojom::StringAttribute::kValue)) {
return;
}
data_.AddStringAttribute(ax::mojom::StringAttribute::kValue, value);
if (ready_to_notify_events_) {
OnStringAttributeChanged(ax::mojom::StringAttribute::kValue, value);
NotifyEvent(ax::mojom::Event::kValueChanged, true);
if (data_.IsTextField() || ui::IsSelectElement(data_.role)) {
NotifyEvent(ax::mojom::Event::kTextChanged, true);
}
}
NotifyDataChanged();
}
void ViewAccessibility::SetValue(std::u16string_view value) {
SetValue(base::UTF16ToUTF8(value));
}
void ViewAccessibility::RemoveValue() {
if (!data_.HasStringAttribute(ax::mojom::StringAttribute::kValue)) {
return;
}
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kValue);
OnStringAttributeChanged(ax::mojom::StringAttribute::kValue, std::nullopt);
NotifyEvent(ax::mojom::Event::kValueChanged, true);
NotifyDataChanged();
}
std::u16string ViewAccessibility::GetValue() const {
return base::UTF8ToUTF16(
data_.GetStringAttribute(ax::mojom::StringAttribute::kValue));
}
void ViewAccessibility::SetDefaultActionVerb(
const ax::mojom::DefaultActionVerb default_action_verb) {
data_.SetDefaultActionVerb(default_action_verb);
}
ax::mojom::DefaultActionVerb ViewAccessibility::GetDefaultActionVerb() const {
return data_.GetDefaultActionVerb();
}
void ViewAccessibility::RemoveDefaultActionVerb() {
data_.RemoveIntAttribute(ax::mojom::IntAttribute::kDefaultActionVerb);
OnIntAttributeChanged(ax::mojom::IntAttribute::kDefaultActionVerb,
std::nullopt);
NotifyDataChanged();
}
void ViewAccessibility::SetAutoComplete(const std::string& autocomplete) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kAutoComplete,
autocomplete);
NotifyDataChanged();
}
void ViewAccessibility::SetHasFocusableAncestor(bool ancestor_focusable) {
has_focusable_ancestor_ = ancestor_focusable;
UpdateIgnoredState();
}
void ViewAccessibility::SetHasFocusableAncestorRecursive(
bool ancestor_focusable) {
if (view_) {
for (auto& child : view_->children()) {
child->GetViewAccessibility().SetHasFocusableAncestor(ancestor_focusable);
if (child->GetFocusBehavior() != View::FocusBehavior::NEVER) {
continue;
}
child->GetViewAccessibility().SetHasFocusableAncestorRecursive(
ancestor_focusable);
}
}
for (auto& child : virtual_children()) {
child->SetHasFocusableAncestor(ancestor_focusable);
child->SetHasFocusableAncestorRecursive(ancestor_focusable);
}
UpdateIgnoredState();
}
void ViewAccessibility::UpdateFocusableState() {
CHECK(view_);
bool is_focusable = view_->GetFocusBehavior() != View::FocusBehavior::NEVER &&
GetIsEnabled() &&
!data_.HasState(ax::mojom::State::kInvisible) &&
!ViewAccessibility::GetIsIgnored();
if (is_focusable) {
CHECK(!should_be_invisible_ &&
!data_.HasState(ax::mojom::State::kInvisible))
<< "A view that focusable should not be marked as invisible. This is a "
"check we also make in the Paint Checks.";
}
SetState(ax::mojom::State::kFocusable, is_focusable);
}
void ViewAccessibility::UpdateInvisibleByInheritanceRecursive(
const View* initial_view,
bool invisible_by_inheritance) {
CHECK(view_);
internal::ScopedChildrenLock lock(view_);
if (view_.get() != initial_view) {
is_invisible_by_inheritance_ = invisible_by_inheritance;
if (!view_->GetVisible()) {
return;
}
}
UpdateInvisibleState();
for (auto& child : view_->children()) {
child->GetViewAccessibility().UpdateInvisibleByInheritanceRecursive(
initial_view, invisible_by_inheritance);
}
for (auto& child : virtual_children()) {
child->UpdateParentViewIsDrawnRecursive(initial_view,
!invisible_by_inheritance);
}
}
void ViewAccessibility::OnViewHasNewAncestor(const View* new_ancestor) {
CHECK(view_->parent());
bool parent_invisible =
view_->parent()->GetViewAccessibility().is_invisible_by_inheritance() ||
!view_->parent()->GetVisible();
bool ancestor_focusable =
new_ancestor->GetFocusBehavior() != View::FocusBehavior::NEVER ||
new_ancestor->GetViewAccessibility().has_focusable_ancestor();
internal::ScopedChildrenLock lock(view_);
is_invisible_by_inheritance_ = parent_invisible;
UpdateInvisibleState();
if (ancestor_focusable) {
SetHasFocusableAncestor(ancestor_focusable);
}
UpdateReadyToNotifyEvents();
for (auto& child : view_->children()) {
child->GetViewAccessibility().OnViewHasNewAncestor(new_ancestor);
}
for (auto& child : virtual_children()) {
child->OnViewHasNewAncestor(ancestor_focusable);
}
}
void ViewAccessibility::SetRootViewURL(const std::string& url) {
CHECK(view_);
CHECK(!view_->parent())
<< "This method should only be called on the RootView.";
data_.AddStringAttribute(ax::mojom::StringAttribute::kUrl, url);
OnStringAttributeChanged(ax::mojom::StringAttribute::kUrl, url);
NotifyDataChanged();
}
void ViewAccessibility::SetRootViewIsReadyToNotifyEvents() {
CHECK(view_);
CHECK(!view_->parent())
<< "This method should only be called on the RootView.";
ready_to_notify_events_ = true;
}
void ViewAccessibility::UpdateInvisibleState() {
bool is_invisible =
(!view_->GetVisible() && data_.role != ax::mojom::Role::kAlert) ||
is_invisible_by_inheritance_ || should_be_invisible_;
SetState(ax::mojom::State::kInvisible, is_invisible);
UpdateFocusableState();
}
void ViewAccessibility::SetChildTreeID(ui::AXTreeID tree_id) {
CHECK(view_);
if (tree_id != ui::AXTreeIDUnknown()) {
data_.AddChildTreeId(tree_id);
const views::Widget* widget = view_->GetWidget();
if (widget && widget->GetNativeView() && display::Screen::Get()) {
const float scale_factor =
display::Screen::Get()
->GetDisplayNearestView(widget->GetNativeView())
.device_scale_factor();
SetChildTreeScaleFactor(scale_factor);
}
OnStringAttributeChanged(ax::mojom::StringAttribute::kChildTreeId,
tree_id.ToString());
NotifyDataChanged();
}
}
ui::AXTreeID ViewAccessibility::GetChildTreeID() const {
std::optional<ui::AXTreeID> child_tree_id = data_.GetChildTreeID();
return child_tree_id ? child_tree_id.value() : ui::AXTreeIDUnknown();
}
void ViewAccessibility::RemoveChildTreeID() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kChildTreeId);
OnStringAttributeChanged(ax::mojom::StringAttribute::kChildTreeId,
std::string());
NotifyDataChanged();
}
void ViewAccessibility::SetChildTreeScaleFactor(float scale_factor) {
if (data_.HasChildTreeID()) {
data_.AddFloatAttribute(ax::mojom::FloatAttribute::kChildTreeScale,
scale_factor);
NotifyDataChanged();
}
}
gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() const {
return gfx::NativeViewAccessible();
}
void ViewAccessibility::AnnounceAlert(std::u16string_view text) {
CHECK(view_);
if (auto* const widget = view_->GetWidget()) {
if (auto* const root_view =
static_cast<internal::RootView*>(widget->GetRootView())) {
root_view->AnnounceTextAs(std::u16string(text),
ui::AXPlatformNode::AnnouncementType::kAlert);
}
}
}
void ViewAccessibility::AnnouncePolitely(std::u16string_view text) {
CHECK(view_);
if (auto* const widget = view_->GetWidget()) {
if (auto* const root_view =
static_cast<internal::RootView*>(widget->GetRootView())) {
root_view->AnnounceTextAs(std::u16string(text),
ui::AXPlatformNode::AnnouncementType::kPolite);
}
}
}
void ViewAccessibility::AnnounceText(std::u16string_view text) {
AnnounceAlert(text);
}
ui::AXPlatformNodeId ViewAccessibility::GetUniqueId() const {
return unique_id_;
}
AtomicViewAXTreeManager*
ViewAccessibility::GetAtomicViewAXTreeManagerForTesting() const {
return nullptr;
}
Widget* ViewAccessibility::GetWidget() const {
if (!view_) {
return nullptr;
}
return view_->GetWidget();
}
ViewAccessibility* ViewAccessibility::GetViewAccessibilityParent() const {
if (!view_) {
return nullptr;
}
if (auto* parent = view_->parent()) {
return &parent->GetViewAccessibility();
}
return nullptr;
}
ViewAccessibility* ViewAccessibility::GetUnignoredParent() const {
ViewAccessibility* parent = GetViewAccessibilityParent();
while (parent && parent->GetIsIgnored()) {
parent = parent->GetViewAccessibilityParent();
}
return parent;
}
gfx::NativeViewAccessible ViewAccessibility::GetFocusedDescendant() {
CHECK(view_);
if (focused_virtual_child_) {
return focused_virtual_child_->GetNativeObject();
}
return view_->GetNativeViewAccessible();
}
std::vector<raw_ptr<ViewAccessibility>> ViewAccessibility::GetChildren() const {
std::vector<raw_ptr<ViewAccessibility>> out;
if (IsLeaf()) {
return out;
}
if (!virtual_children_.empty()) {
out.reserve(virtual_children_.size());
for (auto& v : virtual_children_) {
out.push_back(v.get());
}
return out;
}
if (!view_) {
return out;
}
const auto& view_children = view_->children();
out.reserve(view_children.size());
for (auto child_view : view_children) {
out.push_back(&child_view->GetViewAccessibility());
}
return out;
}
std::string ViewAccessibility::GetDebugString() const {
return std::string(view_ ? view_->GetClassName() : "ViewAccessibility");
}
void ViewAccessibility::FireNativeEvent(ax::mojom::Event event_type) {
if (accessibility_events_callback_) {
accessibility_events_callback_.Run(nullptr, event_type);
}
}
const ViewAccessibility::AccessibilityEventsCallback&
ViewAccessibility::accessibility_events_callback() const {
return accessibility_events_callback_;
}
void ViewAccessibility::set_accessibility_events_callback(
ViewAccessibility::AccessibilityEventsCallback callback) {
accessibility_events_callback_ = std::move(callback);
}
void ViewAccessibility::CompleteCacheInitializationRecursive() {
if (view_) {
internal::ScopedChildrenLock lock(view_);
}
if (initialization_state_ == State::kInitialized) {
return;
}
initialization_state_ = State::kInitializing;
ui::AXNodeData data;
if (view_) {
view_->OnAccessibilityInitializing(&data);
}
#if DCHECK_IS_ON()
views::ViewAccessibilityUtils::ValidateAttributesNotSet(data, data_);
#endif
views::ViewAccessibilityUtils::Merge( data, data_);
initialization_state_ = State::kInitialized;
if (view_) {
for (auto& child : view_->children()) {
child->GetViewAccessibility().CompleteCacheInitializationRecursive();
}
}
for (auto& child : virtual_children()) {
child->CompleteCacheInitializationRecursive();
}
}
void ViewAccessibility::OnWidgetUpdatedRecursive(Widget* widget,
Widget* old_widget) {
CHECK(widget);
is_widget_closed_ = widget->IsClosed();
if (needs_ax_tree_manager()) {
EnsureAtomicViewAXTreeManager();
}
if (view_) {
internal::ScopedChildrenLock lock(view_);
for (auto& child : view_->children()) {
child->GetViewAccessibility().OnWidgetUpdatedRecursive(widget,
old_widget);
}
}
for (auto& child : virtual_children()) {
child->OnWidgetUpdatedRecursive(widget, old_widget);
}
}
void ViewAccessibility::OnWidgetClosing(Widget* widget) {
CHECK_EQ(view_, widget->GetRootView());
SetWidgetClosedRecursive(widget, true);
}
void ViewAccessibility::OnWidgetDestroyed(Widget* widget) {
CHECK(widget->GetRootView());
CHECK_EQ(view_, widget->GetRootView());
SetWidgetClosedRecursive(widget, true);
}
void ViewAccessibility::OnWidgetUpdated(Widget* widget, Widget* old_widget) {
CHECK(widget);
DCHECK_EQ(widget, view_->GetWidget());
if (widget == old_widget) {
return;
}
if (old_widget && old_widget != widget) {
old_widget->RemoveObserver(this);
}
OnWidgetUpdatedRecursive(widget, old_widget);
}
void ViewAccessibility::CompleteCacheInitialization() {
if (initialization_state_ == State::kInitialized) {
return;
}
CompleteCacheInitializationRecursive();
}
bool ViewAccessibility::IsAccessibilityEnabled() const {
return ui::AXPlatform::GetInstance().GetMode() == ui::AXMode::kNativeAPIs;
}
void ViewAccessibility::PruneSubtree() {
CHECK(view_);
internal::ScopedChildrenLock lock(view_);
for (auto& child : view_->children()) {
child->GetViewAccessibility().pruned_ = true;
child->GetViewAccessibility().UpdateIgnoredState();
child->GetViewAccessibility().PruneSubtree();
}
for (auto& child : virtual_children()) {
child->PruneVirtualSubtree();
}
}
void ViewAccessibility::UnpruneSubtree() {
CHECK(view_);
internal::ScopedChildrenLock lock(view_);
for (auto& child : view_->children()) {
child->GetViewAccessibility().pruned_ = false;
child->GetViewAccessibility().UpdateIgnoredState();
if (child->GetViewAccessibility().ViewAccessibility::IsLeaf()) {
continue;
}
child->GetViewAccessibility().UnpruneSubtree();
}
for (auto& child : virtual_children()) {
child->UnpruneVirtualSubtree();
}
}
void ViewAccessibility::UpdateIgnoredState() {
#if !BUILDFLAG(IS_CHROMEOS)
bool is_ignored = should_be_ignored_ || pruned_ ||
GetCachedRole() == ax::mojom::Role::kNone ||
(has_focusable_ancestor_ &&
view_->GetFocusBehavior() == View::FocusBehavior::NEVER);
#else
bool is_ignored = should_be_ignored_ || pruned_ ||
GetCachedRole() == ax::mojom::Role::kNone;
#endif
SetState(ax::mojom::State::kIgnored, is_ignored);
UpdateFocusableState();
}
void ViewAccessibility::UpdateReadyToNotifyEvents() {
CHECK(view_);
View* parent = view_->parent();
if (parent && parent->GetViewAccessibility().ready_to_notify_events_) {
SetReadyToNotifyEvents();
}
}
void ViewAccessibility::SetReadyToNotifyEvents() {
ready_to_notify_events_ = true;
}
void ViewAccessibility::SetWidgetClosedRecursive(Widget* widget, bool value) {
is_widget_closed_ = value;
if (view_) {
internal::ScopedChildrenLock lock(view_);
for (auto& child : view_->children()) {
child->GetViewAccessibility().SetWidgetClosedRecursive(widget, value);
}
}
for (auto& child : virtual_children()) {
child->SetWidgetClosedRecursive(widget, value);
}
}
void ViewAccessibility::SetDataForClosedWidget(ui::AXNodeData* data) const {
data->id = data_.id;
CHECK_EQ(data->id, GetUniqueId());
data->role = ax::mojom::Role::kUnknown;
data->SetRestriction(ax::mojom::Restriction::kDisabled);
if (ViewAccessibility::IsAccessibilityFocusable()) {
data->AddState(ax::mojom::State::kFocusable);
data->SetNameExplicitlyEmpty();
}
if (data_.HasState(ax::mojom::State::kCollapsed)) {
data->AddState(ax::mojom::State::kCollapsed);
}
if (data_.HasState(ax::mojom::State::kInvisible)) {
data->AddState(ax::mojom::State::kInvisible);
}
}
void ViewAccessibility::OnRoleChanged(ax::mojom::Role role) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyRoleChanged(role);
}
base::CallbackListSubscription ViewAccessibility::AddRoleChangedCallback(
RoleCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()->AddRoleChangedCallback(
callback);
}
void ViewAccessibility::OnStringAttributeChanged(
ax::mojom::StringAttribute attribute,
const std::optional<std::string>& value) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyStringAttributeChanged(
attribute, value);
}
base::CallbackListSubscription
ViewAccessibility::AddStringAttributeChangedCallback(
ax::mojom::StringAttribute attribute,
StringAttributeCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()
->AddStringAttributeChangedCallback(attribute, callback);
}
void ViewAccessibility::OnIntAttributeChanged(ax::mojom::IntAttribute attribute,
std::optional<int> value) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyIntAttributeChanged(attribute,
value);
}
base::CallbackListSubscription
ViewAccessibility::AddIntAttributeChangedCallback(
ax::mojom::IntAttribute attribute,
IntAttributeCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()
->AddIntAttributeChangedCallback(attribute, callback);
}
void ViewAccessibility::OnBoolAttributeChanged(
ax::mojom::BoolAttribute attribute,
std::optional<bool> value) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyBoolAttributeChanged(
attribute, value);
}
base::CallbackListSubscription
ViewAccessibility::AddBoolAttributeChangedCallback(
ax::mojom::BoolAttribute attribute,
BoolAttributeCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()
->AddBoolAttributeChangedCallback(attribute, callback);
}
void ViewAccessibility::OnStateChanged(ax::mojom::State state, bool value) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyStateChanged(state, value);
}
base::CallbackListSubscription ViewAccessibility::AddStateChangedCallback(
ax::mojom::State state,
StateCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()->AddStateChangedCallback(
state, callback);
}
void ViewAccessibility::OnIntListAttributeChanged(
ax::mojom::IntListAttribute attribute,
const std::optional<std::vector<int>>& value) {
GetOrCreateAXAttributeChangedCallbacks()->NotifyIntListAttributeChanged(
attribute, value);
}
base::CallbackListSubscription
ViewAccessibility::AddIntListAttributeChangedCallback(
ax::mojom::IntListAttribute attribute,
IntListAttributeCallbackList::CallbackType callback) {
return GetOrCreateAXAttributeChangedCallbacks()
->AddIntListAttributeChangedCallback(attribute, callback);
}
void ViewAccessibility::SetHierarchicalLevel(int hierarchical_level) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kHierarchicalLevel,
hierarchical_level);
}
void ViewAccessibility::SetState(ax::mojom::State state, bool is_enabled) {
if (is_enabled) {
data_.AddState(state);
} else {
data_.RemoveState(state);
}
OnStateChanged(state, is_enabled);
NotifyDataChanged();
}
void ViewAccessibility::SetBlockNotifyEvents(bool block) {
ready_to_notify_events_ = !block;
}
void ViewAccessibility::SetIsHovered(bool is_hovered) {
if (is_hovered == GetIsHovered()) {
return;
}
SetState(ax::mojom::State::kHovered, is_hovered);
}
bool ViewAccessibility::GetIsHovered() const {
return data_.HasState(ax::mojom::State::kHovered);
}
void ViewAccessibility::SetPopupForId(ui::AXPlatformNodeId popup_for_id) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kPopupForId, popup_for_id);
NotifyDataChanged();
}
void ViewAccessibility::SetTextDirection(int text_direction) {
CHECK_GE(text_direction,
static_cast<int32_t>(ax::mojom::WritingDirection::kMinValue));
CHECK_LE(text_direction,
static_cast<int32_t>(ax::mojom::WritingDirection::kMaxValue));
data_.AddIntAttribute(ax::mojom::IntAttribute::kTextDirection,
text_direction);
NotifyDataChanged();
}
void ViewAccessibility::SetIsProtected(bool is_protected) {
if (data_.HasState(ax::mojom::State::kProtected) == is_protected) {
return;
}
SetState(ax::mojom::State::kProtected, is_protected);
}
void ViewAccessibility::SetIsExpanded() {
if (data_.HasState(ax::mojom::State::kExpanded)) {
CHECK(!data_.HasState(ax::mojom::State::kCollapsed));
return;
}
bool should_notify = data_.HasState(ax::mojom::State::kCollapsed);
SetState(ax::mojom::State::kExpanded, true);
SetState(ax::mojom::State::kCollapsed, false);
if (should_notify) {
NotifyEvent(ax::mojom::Event::kExpandedChanged, true);
}
}
void ViewAccessibility::SetIsCollapsed() {
if (data_.HasState(ax::mojom::State::kCollapsed)) {
CHECK(!data_.HasState(ax::mojom::State::kExpanded));
return;
}
bool should_notify = data_.HasState(ax::mojom::State::kExpanded);
SetState(ax::mojom::State::kCollapsed, true);
SetState(ax::mojom::State::kExpanded, false);
if (should_notify) {
NotifyEvent(ax::mojom::Event::kExpandedChanged, true);
}
}
void ViewAccessibility::RemoveExpandCollapseState() {
SetState(ax::mojom::State::kExpanded, false);
SetState(ax::mojom::State::kCollapsed, false);
}
void ViewAccessibility::SetIsVertical(bool vertical) {
CHECK(!data_.HasState(ax::mojom::State::kHorizontal));
if (data_.HasState(ax::mojom::State::kVertical)) {
return;
}
SetState(ax::mojom::State::kVertical, vertical);
}
void ViewAccessibility::SetTextSelStart(int32_t text_sel_start) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, text_sel_start);
NotifyDataChanged();
}
void ViewAccessibility::SetTextSelEnd(int32_t text_sel_end) {
data_.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, text_sel_end);
NotifyDataChanged();
}
void ViewAccessibility::SetLiveAtomic(bool live_atomic) {
data_.AddBoolAttribute(ax::mojom::BoolAttribute::kLiveAtomic, live_atomic);
NotifyDataChanged();
}
void ViewAccessibility::SetLiveStatus(const std::string& live_status) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
live_status);
NotifyDataChanged();
}
void ViewAccessibility::SetLiveRelevant(const std::string& live_relevant) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kLiveRelevant,
live_relevant);
NotifyDataChanged();
}
void ViewAccessibility::RemoveLiveRelevant() {
data_.RemoveStringAttribute(ax::mojom::StringAttribute::kLiveRelevant);
NotifyDataChanged();
}
void ViewAccessibility::SetContainerLiveRelevant(
const std::string& live_relevant) {
data_.AddStringAttribute(ax::mojom::StringAttribute::kContainerLiveRelevant,
live_relevant);
NotifyDataChanged();
}
void ViewAccessibility::RemoveContainerLiveRelevant() {
data_.RemoveStringAttribute(
ax::mojom::StringAttribute::kContainerLiveRelevant);
NotifyDataChanged();
}
ui::AXAttributeChangedCallbacks*
ViewAccessibility::GetOrCreateAXAttributeChangedCallbacks() {
if (!attribute_changed_callbacks_) {
attribute_changed_callbacks_ =
std::make_unique<ui::AXAttributeChangedCallbacks>();
}
return attribute_changed_callbacks_.get();
}
void ViewAccessibility::NotifyDataChanged() {
CHECK(view_);
AXUpdateNotifier::Get()->NotifyViewDataChanged(view_);
}
}