#include "ui/views/accessibility/view_accessibility_utils.h"
#include <algorithm>
#include <set>
#include <string>
#include "base/memory/raw_ptr.h"
#include "ui/accessibility/ax_enum_util.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
namespace views {
Widget* ViewAccessibilityUtils::GetFocusedChildWidgetForAccessibility(
const View* view) {
const FocusManager* focus_manager = view->GetFocusManager();
if (!focus_manager) {
return nullptr;
}
const View* focused_view = view->GetFocusManager()->GetFocusedView();
if (!focused_view) {
return nullptr;
}
Widget::Widgets child_widgets =
Widget::GetAllOwnedWidgets(view->GetWidget()->GetNativeView());
const auto i =
std::ranges::find_if(child_widgets, [focused_view](Widget* child_widget) {
return IsFocusedChildWidget(child_widget, focused_view);
});
return (i == child_widgets.cend()) ? nullptr : *i;
}
bool ViewAccessibilityUtils::IsFocusedChildWidget(Widget* widget,
const View* focused_view) {
return widget->IsVisible() &&
widget->GetContentsView()->Contains(focused_view);
}
void ViewAccessibilityUtils::Merge(const ui::AXNodeData& source,
ui::AXNodeData& destination) {
if (source.role != ax::mojom::Role::kUnknown) {
destination.role = source.role;
}
for (const auto& attr : source.int_attributes) {
destination.AddIntAttribute(attr.first, attr.second);
}
for (const auto& attr : source.string_attributes) {
if (attr.first == ax::mojom::StringAttribute::kChildTreeId) {
destination.AddChildTreeId(ui::AXTreeID::FromString(attr.second));
} else {
destination.AddStringAttribute(attr.first, attr.second);
}
}
destination.bool_attributes.Append(source.bool_attributes);
for (const auto& attr : source.intlist_attributes) {
destination.AddIntListAttribute(attr.first, attr.second);
}
for (const auto& attr : source.stringlist_attributes) {
destination.AddStringListAttribute(attr.first, attr.second);
}
for (const auto& attr : source.float_attributes) {
destination.AddFloatAttribute(attr.first, attr.second);
}
if (!source.relative_bounds.bounds.IsEmpty()) {
destination.relative_bounds.bounds = source.relative_bounds.bounds;
}
if (source.id != ui::kInvalidAXNodeID) {
destination.id = source.id;
}
destination.state.value() |= source.state.value();
destination.actions |= source.actions;
}
void ViewAccessibilityUtils::ValidateAttributesNotSet(
const ui::AXNodeData& new_data,
const ui::AXNodeData& existing_data) {
auto attributeErrorMessage = [](std::string attr) -> std::string {
return "The \"" + attr +
"\" attribute has been migrated to use the new AXNodeData pipeline. "
"Please use "
"the setters/getters in ViewAccessibility to set the attribute."
"See the comment in ViewAccessibility::GetAccessibleNodeData for "
"more info.";
};
for (const auto& attr : new_data.int_attributes) {
DCHECK(!existing_data.HasIntAttribute(attr.first))
<< attributeErrorMessage(std::string(ui::ToString(attr.first)));
}
for (const auto& attr : new_data.string_attributes) {
DCHECK(!existing_data.HasStringAttribute(attr.first))
<< attributeErrorMessage(std::string(ui::ToString(attr.first)));
}
new_data.bool_attributes.ForEach(
[&existing_data, &attributeErrorMessage](ax::mojom::BoolAttribute attr,
bool value) {
DCHECK(!existing_data.HasBoolAttribute(attr))
<< attributeErrorMessage(std::string(ui::ToString(attr)));
});
for (const auto& attr : new_data.float_attributes) {
DCHECK(!existing_data.HasFloatAttribute(attr.first))
<< attributeErrorMessage(std::string(ui::ToString(attr.first)));
}
for (const auto& attr : new_data.intlist_attributes) {
DCHECK(!existing_data.HasIntListAttribute(attr.first))
<< attributeErrorMessage(std::string(ui::ToString(attr.first)));
}
for (const auto& attr : new_data.stringlist_attributes) {
DCHECK(!existing_data.HasStringListAttribute(attr.first))
<< attributeErrorMessage(std::string(ui::ToString(attr.first)));
}
auto bitfieldErrorMessage = [](std::string bitfield_name) -> std::string {
return "The accessible " + bitfield_name +
" should be set directly in the accessibility cache through the "
"ViewAccessibility setters, not through this lazy loading "
"mechanism. The purpose of the lazy loading mechanism is to avoid "
"expensive memory allocations and calculations until the "
"accessibility tree is actually needed. However, the state is "
"stored efficiently in a bitfield always initialized to zero, so "
"there's not performance improvement to be gained by lazy loading "
"it.";
};
DCHECK(new_data.state.value() == 0U) << bitfieldErrorMessage("state");
DCHECK(new_data.actions == 0U) << bitfieldErrorMessage("action");
DCHECK(new_data.relative_bounds.bounds.IsEmpty())
<< "The `relative_bounds` should not be set in the lazy loading "
"function. Instead, use `ViewAccessibility::SetBounds.`";
}
}