1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ui/accessibility/platform/ax_platform_node_ui_kit_element.h"
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#include <string>
#include "base/apple/foundation_util.h"
#include "base/auto_reset.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "ui/accessibility/ax_common.h"
#import "ui/accessibility/ax_enum_util.h"
#import "ui/accessibility/ax_enums.mojom.h"
#import "ui/accessibility/ax_role_properties.h"
#import "ui/accessibility/platform/ax_platform_node_ios.h"
#import "ui/accessibility/platform/ax_platform_tree_manager_delegate.h"
#import "ui/accessibility/platform/child_iterator_base.h"
#include "ui/gfx/native_widget_types.h"
@implementation AXPlatformNodeUIKitElement {
// The AXPlatformNode corresponding to this wrapper instance.
raw_ptr<ui::AXPlatformNodeIOS> _node;
// An array of children of this object. Cached to avoid re-computing.
NSMutableArray* _children;
// Whether the children have changed and need to be updated.
BOOL _needsToUpdateChildren;
// Whether _children is currently being computed.
BOOL _gettingChildren;
}
+ (AXPlatformNodeUIKitElement*)elementFromNativeViewAccessible:
(gfx::NativeViewAccessible)nativeViewAccessible {
return base::apple::ObjCCast<AXPlatformNodeUIKitElement>(
nativeViewAccessible.Get());
}
- (instancetype)initWithPlatformNode:(ui::AXPlatformNodeIOS*)platformNode {
id container = platformNode->GetParent().Get();
// TODO(crbug.com/336611337): Sometimes container is null for new subframes.
// We need a way to retry after the AXTreeManager is connected to its parent.
if (!container) {
return nil;
}
if ((self = [super initWithAccessibilityContainer:container])) {
_node = platformNode;
_needsToUpdateChildren = YES;
_gettingChildren = NO;
}
return self;
}
- (void)childrenChanged {
if (!_node || _gettingChildren) {
return;
}
_needsToUpdateChildren = YES;
if (![self isIncludedInPlatformTree]) {
ui::AXPlatformNode* parentNode =
ui::AXPlatformNode::FromNativeViewAccessible(_node->GetParent());
if (parentNode) {
[[AXPlatformNodeUIKitElement
elementFromNativeViewAccessible:parentNode->GetNativeViewAccessible()]
childrenChanged];
}
}
}
#pragma mark - AXPlatformNodeUIKit
- (void)detach {
_node = nullptr;
}
- (ui::AXPlatformNodeIOS*)node {
return _node.get();
}
#pragma mark - UIAccessibilityContainer
- (NSArray*)accessibilityElements {
if (!_node) {
return nil;
}
if (_needsToUpdateChildren) {
base::AutoReset<BOOL> setGettingChildren(&_gettingChildren, YES);
uint32_t childCount = _node->GetChildCount();
_children = [[NSMutableArray alloc] initWithCapacity:childCount];
for (auto it = _node->GetDelegate()->ChildrenBegin();
*it != *_node->GetDelegate()->ChildrenEnd(); ++(*it)) {
AXPlatformNodeUIKitElement* child = [AXPlatformNodeUIKitElement
elementFromNativeViewAccessible:it->GetNativeViewAccessible()];
if ([child isIncludedInPlatformTree]) {
[_children addObject:child];
} else {
[_children addObjectsFromArray:child.accessibilityElements];
}
}
// Also, add indirect children (if any).
const std::vector<int32_t>& indirectChildIds = _node->GetIntListAttribute(
ax::mojom::IntListAttribute::kIndirectChildIds);
for (int32_t childId : indirectChildIds) {
ui::AXPlatformNode* childNode =
_node->GetDelegate()->GetFromNodeID(childId);
if (childNode) {
[_children addObject:childNode->GetNativeViewAccessible().Get()];
}
}
_needsToUpdateChildren = NO;
}
return _children;
}
#pragma mark - UIAccessibility
- (UIAccessibilityTraits)accessibilityTraits {
// TODO(crbug.com/336611337): Choose appropriate traits based on node's role.
return UIAccessibilityTraitLink;
}
- (CGRect)accessibilityFrame {
if (!_node) {
return CGRectZero;
}
gfx::Rect rect = _node->GetDelegate()->GetBoundsRect(
ui::AXCoordinateSystem::kScreenDIPs, ui::AXClippingBehavior::kClipped);
rect = ScaleToRoundedRect(
rect, 1.f / _node->GetIOSDelegate()->GetDeviceScaleFactor());
return rect.ToCGRect();
}
- (id)accessibilityFocusedUIElement {
if (!_node) {
return nil;
}
return _node->GetFocus().Get();
}
- (BOOL)isAccessibilityElement {
if (!_node) {
return NO;
}
if (_node->GetRole() == ax::mojom::Role::kImage &&
_node->GetNameFrom() == ax::mojom::NameFrom::kAttributeExplicitlyEmpty) {
return NO;
}
// TODO(crbug.com/336611337): If a node is an accessibility element, then
// VoiceOver will not visit its accessibilityElements (that is, its children).
// If we ever need both a node and its descendants to be interactable using
// VoiceOver, we will need to restructure the UIAccessibility tree by
// inserting an additional node to act as a container.
if (self.accessibilityElements.count) {
return NO;
}
return (_node->GetRole() != ax::mojom::Role::kUnknown &&
!_node->GetDelegate()->IsIgnored());
}
- (NSString*)accessibilityLabel {
if (!_node) {
return nil;
}
std::string name = _node->GetName();
if (!name.empty()) {
return base::SysUTF8ToNSString(name);
}
// Given an image where there's no other title, return the base part
// of the filename as the description.
if ([self isImage]) {
std::string url;
if (_node->GetStringAttribute(ax::mojom::StringAttribute::kUrl, &url)) {
// Given a url like http://foo.com/bar/baz.png, just return the
// base name, e.g., "baz.png".
size_t leftIndex = url.rfind('/');
std::string basename =
leftIndex != std::string::npos ? url.substr(leftIndex) : url;
return base::SysUTF8ToNSString(basename);
}
}
return @"";
}
#pragma mark - UIAccessibilityAction
- (NSArray<UIAccessibilityCustomAction*>*)accessibilityCustomActions {
// TODO(crbug.com/336611337): Wire up custom actions for each node, like
// being able to tap or otherwise interact.
return nil;
}
#pragma mark - Private
- (BOOL)isIncludedInPlatformTree {
return _node && _node->GetRole() != ax::mojom::Role::kUnknown &&
!_node->IsInvisibleOrIgnored();
}
- (BOOL)isImage {
return ui::IsImage(_node->GetRole()) &&
!_node->GetBoolAttribute(
ax::mojom::BoolAttribute::kCanvasHasFallback) &&
!_node->GetChildCount() &&
_node->GetNameFrom() != ax::mojom::NameFrom::kAttributeExplicitlyEmpty;
}
@end
|