17#pragma GCC diagnostic error "-Wundeclared-selector"
24class DefaultIosDelegate :
public AccessibilityBridge::IosDelegate {
26 bool IsFlutterViewControllerPresentingModalViewController(
28 if (view_controller) {
29 return view_controller.isPresentingViewController;
35 void PostAccessibilityNotification(UIAccessibilityNotifications notification,
36 id argument)
override {
37 UIAccessibilityPostNotification(notification, argument);
46 std::unique_ptr<IosDelegate> ios_delegate)
47 : view_controller_(view_controller),
49 platform_views_controller_(platform_views_controller),
50 objects_([[NSMutableDictionary alloc] init]),
52 ios_delegate_(ios_delegate ? std::move(ios_delegate)
53 :
std::make_unique<DefaultIosDelegate>()),
56 initWithName:
@"flutter/accessibility"
57 binaryMessenger:
platform_view->GetOwnerViewController().engine.binaryMessenger
62 weak_self->HandleEvent((NSDictionary*)
message);
67AccessibilityBridge::~AccessibilityBridge() {
68 [accessibility_channel_ setMessageHandler:nil];
72UIView<UITextInput>* AccessibilityBridge::textInputView() {
73 return [[platform_view_->GetOwnerViewController().engine
textInputPlugin] textInputView];
76void AccessibilityBridge::AccessibilityObjectDidBecomeFocused(int32_t
id) {
77 last_focused_semantics_object_id_ =
id;
78 [accessibility_channel_ sendMessage:@{
@"type" :
@"didGainFocus",
@"nodeId" : @(
id)}];
81void AccessibilityBridge::AccessibilityObjectDidLoseFocus(int32_t
id) {
82 if (last_focused_semantics_object_id_ ==
id) {
83 last_focused_semantics_object_id_ = kSemanticObjectIdInvalid;
87void AccessibilityBridge::UpdateSemantics(
90 BOOL layoutChanged = NO;
91 BOOL scrollOccured = NO;
92 BOOL needsAnnouncement = NO;
93 for (
const auto& entry : actions) {
97 for (
const auto& entry : nodes) {
100 layoutChanged = layoutChanged || [
object nodeWillCauseLayoutChange:&node];
101 scrollOccured = scrollOccured || [
object nodeWillCauseScroll:&node];
102 needsAnnouncement = [
object nodeShouldTriggerAnnouncement:&node];
103 [
object setSemanticsNode:&node];
105 NSMutableArray* newChildren =
106 [[NSMutableArray alloc] initWithCapacity:newChildCountInTraversalOrder];
107 for (NSUInteger
i = 0;
i < newChildCountInTraversalOrder; ++
i) {
109 [newChildren addObject:child];
112 NSMutableArray* newChildrenInHitTestOrder =
113 [[NSMutableArray alloc] initWithCapacity:newChildCountInHitTestOrder];
114 for (NSUInteger
i = 0;
i < newChildCountInHitTestOrder; ++
i) {
116 [newChildrenInHitTestOrder addObject:child];
118 object.children = newChildren;
119 object.childrenInHitTestOrder = newChildrenInHitTestOrder;
121 NSMutableArray<FlutterCustomAccessibilityAction*>* accessibilityCustomActions =
122 [[NSMutableArray alloc] init];
123 for (int32_t action_id : node.customAccessibilityActions) {
125 if (
action.overrideId != -1) {
130 NSString* label = @(
action.label.data());
131 SEL selector =
@selector(onCustomAccessibilityAction:);
136 customAction.
uid = action_id;
137 [accessibilityCustomActions addObject:customAction];
139 object.accessibilityCustomActions = accessibilityCustomActions;
142 if (needsAnnouncement) {
148 NSString* announcement = [[NSString alloc] initWithUTF8String:
object.node.label.c_str()];
149 UIAccessibilityPostNotification(
150 UIAccessibilityAnnouncementNotification,
151 [[NSAttributedString alloc] initWithString:announcement
153 UIAccessibilitySpeechAttributeQueueAnnouncement : @YES
160 bool routeChanged =
false;
164 if (!view_controller_.view.accessibilityElements) {
165 view_controller_.view.accessibilityElements =
166 @[ [root accessibilityContainer] ?: [NSNull null] ];
168 NSMutableArray<SemanticsObject*>* newRoutes = [[NSMutableArray alloc] init];
169 [root collectRoutes:newRoutes];
172 if (std::find(previous_routes_.begin(), previous_routes_.end(), [route uid]) ==
173 previous_routes_.end()) {
178 if (lastAdded == nil && [newRoutes count] > 0) {
179 int index = [newRoutes count] - 1;
180 lastAdded = [newRoutes objectAtIndex:index];
189 if (lastAdded != nil &&
190 ([lastAdded uid] != previous_route_id_ || [newRoutes count] != previous_routes_.size())) {
191 previous_route_id_ = [lastAdded uid];
194 previous_routes_.clear();
196 previous_routes_.push_back([route uid]);
199 view_controller_.viewIfLoaded.accessibilityElements = nil;
202 NSMutableArray<NSNumber*>* doomed_uids = [NSMutableArray arrayWithArray:objects_.allKeys];
204 VisitObjectsRecursivelyAndRemove(root, doomed_uids);
206 [objects_ removeObjectsForKeys:doomed_uids];
209 [
object accessibilityBridgeDidFinishUpdate];
212 if (!ios_delegate_->IsFlutterViewControllerPresentingModalViewController(view_controller_)) {
213 layoutChanged = layoutChanged || [doomed_uids count] > 0;
216 NSString* routeName = [lastAdded routeName];
217 ios_delegate_->PostAccessibilityNotification(UIAccessibilityScreenChangedNotification,
223 SemanticsObject* lastFocused = [objects_ objectForKey:@(last_focused_semantics_object_id_)];
227 ios_delegate_->PostAccessibilityNotification(
228 UIAccessibilityLayoutChangedNotification,
230 }
else if (scrollOccured) {
234 ios_delegate_->PostAccessibilityNotification(
235 UIAccessibilityPageScrolledNotification,
236 FindNextFocusableIfNecessary().nativeAccessibility);
241void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
245 platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid,
action, {});
248void AccessibilityBridge::DispatchSemanticsAction(int32_t node_uid,
253 platform_view_->DispatchSemanticsAction(kFlutterImplicitViewId, node_uid,
action,
259 NSMutableDictionary<NSNumber*, SemanticsObject*>* objects) {
262 NSNumber* nodeId = @(oldObject.
node.
id);
263 NSUInteger positionInChildlist = [oldObject.
parent.
children indexOfObject:oldObject];
265 [oldObject.
parent replaceChildAtIndex:positionInChildlist withChild:newObject];
266 [objects removeObjectForKey:nodeId];
267 objects[nodeId] = newObject;
283 weak_ptr->GetPlatformViewsController();
285 [platformViewsController flutterTouchInterceptingViewForId:node.
platformViewId];
288 platformView:touchInterceptingView];
298 object = CreateObject(updates[uid], GetWeakPtr());
299 objects_[@(uid)] =
object;
302 auto nodeEntry = updates.find(
object.node.
id);
303 if (nodeEntry != updates.end()) {
318 SemanticsObject* newSemanticsObject = CreateObject(node, GetWeakPtr());
319 ReplaceSemanticsObject(
object, newSemanticsObject, objects_);
320 object = newSemanticsObject;
327void AccessibilityBridge::VisitObjectsRecursivelyAndRemove(
SemanticsObject*
object,
328 NSMutableArray<NSNumber*>* doomed_uids) {
329 [doomed_uids removeObject:@(
object.uid)];
331 VisitObjectsRecursivelyAndRemove(child, doomed_uids);
337 if (last_focused_semantics_object_id_ == kSemanticObjectIdInvalid) {
342 return FindFirstFocusable(objects_[@(last_focused_semantics_object_id_)]);
347 if (!currentObject) {
350 if (currentObject.isAccessibilityElement) {
351 return currentObject;
363void AccessibilityBridge::HandleEvent(NSDictionary<NSString*, id>* annotatedEvent) {
364 NSString*
type = annotatedEvent[
@"type"];
365 if ([
type isEqualToString:
@"announce"]) {
366 NSString*
message = annotatedEvent[
@"data"][
@"message"];
367 ios_delegate_->PostAccessibilityNotification(UIAccessibilityAnnouncementNotification,
message);
369 if ([
type isEqualToString:
@"focus"]) {
371 ios_delegate_->PostAccessibilityNotification(UIAccessibilityLayoutChangedNotification, node);
376 return weak_factory_.GetWeakPtr();
379void AccessibilityBridge::clearState() {
380 [objects_ removeAllObjects];
381 previous_route_id_ = 0;
382 previous_routes_.clear();
383 view_controller_.viewIfLoaded.accessibilityElements = nil;
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterReply)(id _Nullable reply)
NS_ASSUME_NONNULL_BEGIN constexpr int32_t kRootNodeId
AccessibilityBridge()
Creates a new instance of a accessibility bridge.
A Mapping like NonOwnedMapping, but uses Free as its release proc.
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
#define FML_DCHECK(condition)
NSArray< SemanticsObject * > * children
flutter::SemanticsNode node
FlutterTextInputPlugin * textInputPlugin
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
std::unordered_map< int32_t, CustomAccessibilityAction > CustomAccessibilityActionUpdates
impeller::ShaderType type
SemanticsCheckState isChecked
SemanticsTristate isToggled
bool isInMutuallyExclusiveGroup
bool hasImplicitScrolling
std::vector< int32_t > childrenInHitTestOrder
std::vector< int32_t > customAccessibilityActions
bool IsPlatformViewNode() const
std::vector< int32_t > childrenInTraversalOrder