Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SemanticsObject.mm
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h"
6
7#include "flutter/fml/platform/darwin/scoped_nsobject.h"
8#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
9#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h"
10
11namespace {
12
13flutter::SemanticsAction GetSemanticsActionForScrollDirection(
14 UIAccessibilityScrollDirection direction) {
15 // To describe the vertical scroll direction, UIAccessibilityScrollDirection uses the
16 // direction the scroll bar moves in and SemanticsAction uses the direction the finger
17 // moves in. However, the horizontal scroll direction matches the SemanticsAction direction.
18 // That is way the following maps vertical opposite of the SemanticsAction, but the horizontal
19 // maps directly.
20 switch (direction) {
21 case UIAccessibilityScrollDirectionRight:
22 case UIAccessibilityScrollDirectionPrevious: // TODO(abarth): Support RTL using
23 // _node.textDirection.
25 case UIAccessibilityScrollDirectionLeft:
26 case UIAccessibilityScrollDirectionNext: // TODO(abarth): Support RTL using
27 // _node.textDirection.
29 case UIAccessibilityScrollDirectionUp:
31 case UIAccessibilityScrollDirectionDown:
33 }
34 FML_DCHECK(false); // Unreachable
36}
37
38SkM44 GetGlobalTransform(SemanticsObject* reference) {
39 SkM44 globalTransform = [reference node].transform;
40 for (SemanticsObject* parent = [reference parent]; parent; parent = parent.parent) {
41 globalTransform = parent.node.transform * globalTransform;
42 }
43 return globalTransform;
44}
45
47 SkV4 vector = transform.map(point.x(), point.y(), 0, 1);
48 return SkPoint::Make(vector.x / vector.w, vector.y / vector.w);
49}
50
51CGPoint ConvertPointToGlobal(SemanticsObject* reference, CGPoint local_point) {
52 SkM44 globalTransform = GetGlobalTransform(reference);
53 SkPoint point = SkPoint::Make(local_point.x, local_point.y);
54 point = ApplyTransform(point, globalTransform);
55 // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
56 // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
57 // convert.
58 UIScreen* screen = [[[reference bridge]->view() window] screen];
59 // Screen can be nil if the FlutterView is covered by another native view.
60 CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
61 auto result = CGPointMake(point.x() / scale, point.y() / scale);
62 return [[reference bridge]->view() convertPoint:result toView:nil];
63}
64
65CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) {
66 SkM44 globalTransform = GetGlobalTransform(reference);
67
68 SkPoint quad[4] = {
69 SkPoint::Make(local_rect.origin.x, local_rect.origin.y), // top left
70 SkPoint::Make(local_rect.origin.x + local_rect.size.width, local_rect.origin.y), // top right
71 SkPoint::Make(local_rect.origin.x + local_rect.size.width,
72 local_rect.origin.y + local_rect.size.height), // bottom right
73 SkPoint::Make(local_rect.origin.x,
74 local_rect.origin.y + local_rect.size.height) // bottom left
75 };
76 for (auto& point : quad) {
77 point = ApplyTransform(point, globalTransform);
78 }
80 NSCAssert(rect.setBoundsCheck(quad, 4), @"Transformed points can't form a rect");
81 rect.setBounds(quad, 4);
82
83 // `rect` is in the physical pixel coordinate system. iOS expects the accessibility frame in
84 // the logical pixel coordinate system. Therefore, we divide by the `scale` (pixel ratio) to
85 // convert.
86 UIScreen* screen = [[[reference bridge]->view() window] screen];
87 // Screen can be nil if the FlutterView is covered by another native view.
88 CGFloat scale = screen == nil ? [UIScreen mainScreen].scale : screen.scale;
89 auto result =
90 CGRectMake(rect.x() / scale, rect.y() / scale, rect.width() / scale, rect.height() / scale);
91 return UIAccessibilityConvertFrameToScreenCoordinates(result, [reference bridge]->view());
92}
93
94} // namespace
95
97@property(nonatomic, readonly) UISwitch* nativeSwitch;
98@end
99
100@implementation FlutterSwitchSemanticsObject
101
102- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
103 uid:(int32_t)uid {
104 self = [super initWithBridge:bridge uid:uid];
105 if (self) {
106 _nativeSwitch = [[UISwitch alloc] init];
107 }
108 return self;
109}
110
111- (void)dealloc {
112 [_nativeSwitch release];
113 [super dealloc];
114}
115
116- (NSMethodSignature*)methodSignatureForSelector:(SEL)sel {
117 NSMethodSignature* result = [super methodSignatureForSelector:sel];
118 if (!result) {
119 result = [_nativeSwitch methodSignatureForSelector:sel];
120 }
121 return result;
122}
123
124- (void)forwardInvocation:(NSInvocation*)anInvocation {
125 [anInvocation setTarget:_nativeSwitch];
126 [anInvocation invoke];
127}
128
129- (NSString*)accessibilityValue {
131 [self node].HasFlag(flutter::SemanticsFlags::kIsChecked)) {
132 _nativeSwitch.on = YES;
133 } else {
134 _nativeSwitch.on = NO;
135 }
136
138 return nil;
139 } else {
140 return _nativeSwitch.accessibilityValue;
141 }
142}
143
144- (UIAccessibilityTraits)accessibilityTraits {
146 _nativeSwitch.enabled = YES;
147 } else {
148 _nativeSwitch.enabled = NO;
149 }
150
151 return _nativeSwitch.accessibilityTraits;
152}
153
154@end // FlutterSwitchSemanticsObject
155
157@property(nonatomic, retain) FlutterSemanticsScrollView* scrollView;
158@end
159
161
162- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
163 uid:(int32_t)uid {
164 self = [super initWithBridge:bridge uid:uid];
165 if (self) {
166 _scrollView = [[FlutterSemanticsScrollView alloc] initWithSemanticsObject:self];
167 [_scrollView setShowsHorizontalScrollIndicator:NO];
168 [_scrollView setShowsVerticalScrollIndicator:NO];
169 [self.bridge->view() addSubview:_scrollView];
170 }
171 return self;
172}
173
174- (void)dealloc {
175 [_scrollView removeFromSuperview];
176 _scrollView.semanticsObject = nil;
177 [_scrollView release];
178 [super dealloc];
179}
180
182 // In order to make iOS think this UIScrollView is scrollable, the following
183 // requirements must be true.
184 // 1. contentSize must be bigger than the frame size.
185 // 2. The scrollable isAccessibilityElement must return YES
186 //
187 // Once the requirements are met, the iOS uses contentOffset to determine
188 // what scroll actions are available. e.g. If the view scrolls vertically and
189 // contentOffset is 0.0, only the scroll down action is available.
190 [_scrollView setFrame:[self accessibilityFrame]];
191 [_scrollView setContentSize:[self contentSizeInternal]];
192 [_scrollView setContentOffset:[self contentOffsetInternal] animated:NO];
193}
194
196 return _scrollView;
197}
198
199// private methods
200
203 return 0.0f;
204 }
205 float scrollExtentMax = self.node.scrollExtentMax;
206 if (isnan(scrollExtentMax)) {
207 scrollExtentMax = 0.0f;
208 } else if (!isfinite(scrollExtentMax)) {
210 }
211 return scrollExtentMax;
212}
213
216 return 0.0f;
217 }
218 float scrollPosition = self.node.scrollPosition;
219 if (isnan(scrollPosition)) {
220 scrollPosition = 0.0f;
221 }
222 NSCAssert(isfinite(scrollPosition), @"The scrollPosition must not be infinity");
223 return scrollPosition;
224}
225
226- (CGSize)contentSizeInternal {
227 CGRect result;
228 const SkRect& rect = self.node.rect;
229
231 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height() + [self scrollExtentMax]);
232 } else if (self.node.actions & flutter::kHorizontalScrollSemanticsActions) {
233 result = CGRectMake(rect.x(), rect.y(), rect.width() + [self scrollExtentMax], rect.height());
234 } else {
235 result = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
236 }
237 return ConvertRectToGlobal(self, result).size;
238}
239
240- (CGPoint)contentOffsetInternal {
241 CGPoint result;
242 CGPoint origin = _scrollView.frame.origin;
243 const SkRect& rect = self.node.rect;
245 result = ConvertPointToGlobal(self, CGPointMake(rect.x(), rect.y() + [self scrollPosition]));
246 } else if (self.node.actions & flutter::kHorizontalScrollSemanticsActions) {
247 result = ConvertPointToGlobal(self, CGPointMake(rect.x() + [self scrollPosition], rect.y()));
248 } else {
249 result = origin;
250 }
251 return CGPointMake(result.x - origin.x, result.y - origin.y);
252}
253
254@end // FlutterScrollableSemanticsObject
255
256@implementation FlutterCustomAccessibilityAction {
257}
258@end
259
260@interface SemanticsObject ()
261/** Should only be called in conjunction with setting child/parent relationship. */
262- (void)privateSetParent:(SemanticsObject*)parent;
263@end
264
265@implementation SemanticsObject {
267 NSMutableArray<SemanticsObject*>* _children;
268 NSMutableArray<SemanticsObject*>* _childrenInHitTestOrder;
270}
271
272#pragma mark - Override base class designated initializers
273
274// Method declared as unavailable in the interface
275- (instancetype)init {
276 [self release];
277 [super doesNotRecognizeSelector:_cmd];
278 return nil;
279}
280
281#pragma mark - Designated initializers
282
283- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
284 uid:(int32_t)uid {
285 FML_DCHECK(bridge) << "bridge must be set";
286 FML_DCHECK(uid >= kRootNodeId);
287 // Initialize with the UIView as the container.
288 // The UIView will not necessarily be accessibility parent for this object.
289 // The bridge informs the OS of the actual structure via
290 // `accessibilityContainer` and `accessibilityElementAtIndex`.
291 self = [super initWithAccessibilityContainer:bridge->view()];
292
293 if (self) {
294 _bridge = bridge;
295 _uid = uid;
296 _children = [[NSMutableArray alloc] init];
297 _childrenInHitTestOrder = [[NSMutableArray alloc] init];
298 }
299
300 return self;
301}
302
303- (void)dealloc {
304 for (SemanticsObject* child in _children) {
305 [child privateSetParent:nil];
306 }
307 [_children removeAllObjects];
308 [_childrenInHitTestOrder removeAllObjects];
309 [_children release];
310 [_childrenInHitTestOrder release];
311
312 _parent = nil;
313 _container.get().semanticsObject = nil;
314 _inDealloc = YES;
315 [super dealloc];
316}
317
318#pragma mark - Semantic object property accesser
319
320- (void)setChildren:(NSArray<SemanticsObject*>*)children {
321 for (SemanticsObject* child in _children) {
322 [child privateSetParent:nil];
323 }
324 [_children release];
325 _children = [[NSMutableArray alloc] initWithArray:children];
326 for (SemanticsObject* child in _children) {
327 [child privateSetParent:self];
328 }
329}
330
331- (void)setChildrenInHitTestOrder:(NSArray<SemanticsObject*>*)childrenInHitTestOrder {
333 [child privateSetParent:nil];
334 }
335 [_childrenInHitTestOrder release];
336 _childrenInHitTestOrder = [[NSMutableArray alloc] initWithArray:childrenInHitTestOrder];
338 [child privateSetParent:self];
339 }
340}
341
342- (BOOL)hasChildren {
343 return [self.children count] != 0;
344}
345
346#pragma mark - Semantic object method
347
348- (BOOL)isAccessibilityBridgeAlive {
349 return [self bridge].get() != nil;
350}
351
352- (void)setSemanticsNode:(const flutter::SemanticsNode*)node {
353 _node = *node;
354}
355
356- (void)accessibilityBridgeDidFinishUpdate { /* Do nothing by default */
357}
358
359/**
360 * Whether calling `setSemanticsNode:` with `node` would cause a layout change.
361 */
362- (BOOL)nodeWillCauseLayoutChange:(const flutter::SemanticsNode*)node {
363 return [self node].rect != node->rect || [self node].transform != node->transform;
364}
365
366/**
367 * Whether calling `setSemanticsNode:` with `node` would cause a scroll event.
368 */
369- (BOOL)nodeWillCauseScroll:(const flutter::SemanticsNode*)node {
370 return !isnan([self node].scrollPosition) && !isnan(node->scrollPosition) &&
371 [self node].scrollPosition != node->scrollPosition;
372}
373
374/**
375 * Whether calling `setSemanticsNode:` with `node` should trigger an
376 * announcement.
377 */
378- (BOOL)nodeShouldTriggerAnnouncement:(const flutter::SemanticsNode*)node {
379 // The node dropped the live region flag, if it ever had one.
380 if (!node || !node->HasFlag(flutter::SemanticsFlags::kIsLiveRegion)) {
381 return NO;
382 }
383
384 // The node has gained a new live region flag, always announce.
385 if (![self node].HasFlag(flutter::SemanticsFlags::kIsLiveRegion)) {
386 return YES;
387 }
388
389 // The label has updated, and the new node has a live region flag.
390 return [self node].label != node->label;
391}
392
393- (void)replaceChildAtIndex:(NSInteger)index withChild:(SemanticsObject*)child {
394 SemanticsObject* oldChild = _children[index];
395 [oldChild privateSetParent:nil];
396 [child privateSetParent:self];
397 [_children replaceObjectAtIndex:index withObject:child];
398}
399
400- (NSString*)routeName {
401 // Returns the first non-null and non-empty semantic label of a child
402 // with an NamesRoute flag. Otherwise returns nil.
403 if ([self node].HasFlag(flutter::SemanticsFlags::kNamesRoute)) {
404 NSString* newName = [self accessibilityLabel];
405 if (newName != nil && [newName length] > 0) {
406 return newName;
407 }
408 }
409 if ([self hasChildren]) {
410 for (SemanticsObject* child in self.children) {
411 NSString* newName = [child routeName];
412 if (newName != nil && [newName length] > 0) {
413 return newName;
414 }
415 }
416 }
417 return nil;
418}
419
420- (id)nativeAccessibility {
421 return self;
422}
423
424#pragma mark - Semantic object private method
425
426- (void)privateSetParent:(SemanticsObject*)parent {
427 _parent = parent;
428}
429
430- (NSAttributedString*)createAttributedStringFromString:(NSString*)string
431 withAttributes:
432 (const flutter::StringAttributes&)attributes {
433 NSMutableAttributedString* attributedString =
434 [[[NSMutableAttributedString alloc] initWithString:string] autorelease];
435 for (const auto& attribute : attributes) {
436 NSRange range = NSMakeRange(attribute->start, attribute->end - attribute->start);
437 switch (attribute->type) {
439 std::shared_ptr<flutter::LocaleStringAttribute> locale_attribute =
440 std::static_pointer_cast<flutter::LocaleStringAttribute>(attribute);
441 NSDictionary* attributeDict = @{
442 UIAccessibilitySpeechAttributeLanguage : @(locale_attribute->locale.data()),
443 };
444 [attributedString setAttributes:attributeDict range:range];
445 break;
446 }
448 if (@available(iOS 13.0, *)) {
449 NSDictionary* attributeDict = @{
450 UIAccessibilitySpeechAttributeSpellOut : @YES,
451 };
452 [attributedString setAttributes:attributeDict range:range];
453 }
454 break;
455 }
456 }
457 }
458 return attributedString;
459}
460
461- (void)showOnScreen {
462 [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kShowOnScreen);
463}
464
465#pragma mark - UIAccessibility overrides
466
467- (BOOL)isAccessibilityElement {
468 if (![self isAccessibilityBridgeAlive]) {
469 return false;
470 }
471
472 // Note: hit detection will only apply to elements that report
473 // -isAccessibilityElement of YES. The framework will continue scanning the
474 // entire element tree looking for such a hit.
475
476 // We enforce in the framework that no other useful semantics are merged with these nodes.
477 if ([self node].HasFlag(flutter::SemanticsFlags::kScopesRoute)) {
478 return false;
479 }
480
481 return [self isFocusable];
482}
483
484- (bool)isFocusable {
485 // If the node is scrollable AND hidden OR
486 // The node has a label, value, or hint OR
487 // The node has non-scrolling related actions.
488 //
489 // The kIsHidden flag set with the scrollable flag means this node is now
490 // hidden but still is a valid target for a11y focus in the tree, e.g. a list
491 // item that is currently off screen but the a11y navigation needs to know
492 // about.
493 return (([self node].flags & flutter::kScrollableSemanticsFlags) != 0 &&
494 ([self node].flags & static_cast<int32_t>(flutter::SemanticsFlags::kIsHidden)) != 0) ||
495 ![self node].label.empty() || ![self node].value.empty() || ![self node].hint.empty() ||
496 ([self node].actions & ~flutter::kScrollableSemanticsActions) != 0;
497}
498
499- (void)collectRoutes:(NSMutableArray<SemanticsObject*>*)edges {
500 if ([self node].HasFlag(flutter::SemanticsFlags::kScopesRoute)) {
501 [edges addObject:self];
502 }
503 if ([self hasChildren]) {
504 for (SemanticsObject* child in self.children) {
505 [child collectRoutes:edges];
506 }
507 }
508}
509
510- (BOOL)onCustomAccessibilityAction:(FlutterCustomAccessibilityAction*)action {
511 if (![self node].HasAction(flutter::SemanticsAction::kCustomAction)) {
512 return NO;
513 }
514 int32_t action_id = action.uid;
515 std::vector<uint8_t> args;
516 args.push_back(3); // type=int32.
517 args.push_back(action_id);
518 args.push_back(action_id >> 8);
519 args.push_back(action_id >> 16);
520 args.push_back(action_id >> 24);
521 [self bridge]->DispatchSemanticsAction(
523 fml::MallocMapping::Copy(args.data(), args.size() * sizeof(uint8_t)));
524 return YES;
525}
526
527- (NSString*)accessibilityIdentifier {
528 if (![self isAccessibilityBridgeAlive]) {
529 return nil;
530 }
531
532 if ([self node].identifier.empty()) {
533 return nil;
534 }
535 return @([self node].identifier.data());
536}
537
538- (NSString*)accessibilityLabel {
539 if (![self isAccessibilityBridgeAlive]) {
540 return nil;
541 }
542 NSString* label = nil;
543 if (![self node].label.empty()) {
544 label = @([self node].label.data());
545 }
546 if (![self node].tooltip.empty()) {
547 label = label ? [NSString stringWithFormat:@"%@\n%@", label, @([self node].tooltip.data())]
548 : @([self node].tooltip.data());
549 }
550 return label;
551}
552
553- (bool)containsPoint:(CGPoint)point {
554 // The point is in global coordinates, so use the global rect here.
555 return CGRectContainsPoint([self globalRect], point);
556}
557
558// Finds the first eligiable semantics object in hit test order.
559- (id)search:(CGPoint)point {
560 // Search children in hit test order.
561 for (SemanticsObject* child in [self childrenInHitTestOrder]) {
562 if ([child containsPoint:point]) {
563 id childSearchResult = [child search:point];
564 if (childSearchResult != nil) {
565 return childSearchResult;
566 }
567 }
568 }
569 // Check if the current semantic object should be returned.
570 if ([self containsPoint:point] && [self isFocusable]) {
571 return self.nativeAccessibility;
572 }
573 return nil;
574}
575
576// iOS uses this method to determine the hittest results when users touch
577// explore in VoiceOver.
578//
579// For overlapping UIAccessibilityElements (e.g. a stack) in IOS, the focus
580// goes to the smallest object before IOS 16, but to the top-left object in
581// IOS 16. Overrides this method to focus the first eligiable semantics
582// object in hit test order.
583- (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event {
584 return [self search:point];
585}
586
587// iOS calls this method when this item is swipe-to-focusd in VoiceOver.
588- (BOOL)accessibilityScrollToVisible {
589 [self showOnScreen];
590 return YES;
591}
592
593// iOS calls this method when this item is swipe-to-focusd in VoiceOver.
594- (BOOL)accessibilityScrollToVisibleWithChild:(id)child {
595 if ([child isKindOfClass:[SemanticsObject class]]) {
596 [child showOnScreen];
597 return YES;
598 }
599 return NO;
600}
601
602- (NSAttributedString*)accessibilityAttributedLabel {
603 NSString* label = [self accessibilityLabel];
604 if (label.length == 0) {
605 return nil;
606 }
607 return [self createAttributedStringFromString:label withAttributes:[self node].labelAttributes];
608}
609
610- (NSString*)accessibilityHint {
611 if (![self isAccessibilityBridgeAlive]) {
612 return nil;
613 }
614
615 if ([self node].hint.empty()) {
616 return nil;
617 }
618 return @([self node].hint.data());
619}
620
621- (NSAttributedString*)accessibilityAttributedHint {
622 NSString* hint = [self accessibilityHint];
623 if (hint.length == 0) {
624 return nil;
625 }
626 return [self createAttributedStringFromString:hint withAttributes:[self node].hintAttributes];
627}
628
629- (NSString*)accessibilityValue {
630 if (![self isAccessibilityBridgeAlive]) {
631 return nil;
632 }
633
634 if (![self node].value.empty()) {
635 return @([self node].value.data());
636 }
637
638 // iOS does not announce values of native radio buttons.
640 return nil;
641 }
642
643 // FlutterSwitchSemanticsObject should supercede these conditionals.
645 [self node].HasFlag(flutter::SemanticsFlags::kHasCheckedState)) {
646 if ([self node].HasFlag(flutter::SemanticsFlags::kIsToggled) ||
647 [self node].HasFlag(flutter::SemanticsFlags::kIsChecked)) {
648 return @"1";
649 } else {
650 return @"0";
651 }
652 }
653
654 return nil;
655}
656
657- (NSAttributedString*)accessibilityAttributedValue {
658 NSString* value = [self accessibilityValue];
659 if (value.length == 0) {
660 return nil;
661 }
662 return [self createAttributedStringFromString:value withAttributes:[self node].valueAttributes];
663}
664
665- (CGRect)accessibilityFrame {
666 if (![self isAccessibilityBridgeAlive]) {
667 return CGRectMake(0, 0, 0, 0);
668 }
669
670 if ([self node].HasFlag(flutter::SemanticsFlags::kIsHidden)) {
671 return [super accessibilityFrame];
672 }
673 return [self globalRect];
674}
675
676- (CGRect)globalRect {
677 const SkRect& rect = [self node].rect;
678 CGRect localRect = CGRectMake(rect.x(), rect.y(), rect.width(), rect.height());
679 return ConvertRectToGlobal(self, localRect);
680}
681
682#pragma mark - UIAccessibilityElement protocol
683
684- (void)setAccessibilityContainer:(id)container {
685 // Explicit noop. The containers are calculated lazily in `accessibilityContainer`.
686 // See also: https://github.com/flutter/flutter/issues/54366
687}
688
689- (id)accessibilityContainer {
690 if (_inDealloc) {
691 // In iOS9, `accessibilityContainer` will be called by `[UIAccessibilityElementSuperCategory
692 // dealloc]` during `[super dealloc]`. And will crash when accessing `_children` which has
693 // called `[_children release]` in `[SemanticsObject dealloc]`.
694 // https://github.com/flutter/flutter/issues/87247
695 return nil;
696 }
697
698 if (![self isAccessibilityBridgeAlive]) {
699 return nil;
700 }
701
702 if ([self hasChildren] || [self uid] == kRootNodeId) {
703 if (_container == nil) {
704 _container.reset([[SemanticsObjectContainer alloc] initWithSemanticsObject:self
705 bridge:[self bridge]]);
706 }
707 return _container.get();
708 }
709 if ([self parent] == nil) {
710 // This can happen when we have released the accessibility tree but iOS is
711 // still holding onto our objects. iOS can take some time before it
712 // realizes that the tree has changed.
713 return nil;
714 }
715 return [[self parent] accessibilityContainer];
716}
717
718#pragma mark - UIAccessibilityAction overrides
719
720- (BOOL)accessibilityActivate {
721 if (![self isAccessibilityBridgeAlive]) {
722 return NO;
723 }
724 if (![self node].HasAction(flutter::SemanticsAction::kTap)) {
725 return NO;
726 }
727 [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kTap);
728 return YES;
729}
730
731- (void)accessibilityIncrement {
732 if (![self isAccessibilityBridgeAlive]) {
733 return;
734 }
735 if ([self node].HasAction(flutter::SemanticsAction::kIncrease)) {
736 [self node].value = [self node].increasedValue;
737 [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kIncrease);
738 }
739}
740
741- (void)accessibilityDecrement {
742 if (![self isAccessibilityBridgeAlive]) {
743 return;
744 }
745 if ([self node].HasAction(flutter::SemanticsAction::kDecrease)) {
746 [self node].value = [self node].decreasedValue;
747 [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kDecrease);
748 }
749}
750
751- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
752 if (![self isAccessibilityBridgeAlive]) {
753 return NO;
754 }
755 flutter::SemanticsAction action = GetSemanticsActionForScrollDirection(direction);
756 if (![self node].HasAction(action)) {
757 return NO;
758 }
759 [self bridge]->DispatchSemanticsAction([self uid], action);
760 return YES;
761}
762
763- (BOOL)accessibilityPerformEscape {
764 if (![self isAccessibilityBridgeAlive]) {
765 return NO;
766 }
767 if (![self node].HasAction(flutter::SemanticsAction::kDismiss)) {
768 return NO;
769 }
770 [self bridge]->DispatchSemanticsAction([self uid], flutter::SemanticsAction::kDismiss);
771 return YES;
772}
773
774#pragma mark UIAccessibilityFocus overrides
775
776- (void)accessibilityElementDidBecomeFocused {
777 if (![self isAccessibilityBridgeAlive]) {
778 return;
779 }
780 [self bridge]->AccessibilityObjectDidBecomeFocused([self uid]);
781 if ([self node].HasFlag(flutter::SemanticsFlags::kIsHidden) ||
783 [self showOnScreen];
784 }
786 [self bridge]->DispatchSemanticsAction([self uid],
788 }
789}
790
791- (void)accessibilityElementDidLoseFocus {
792 if (![self isAccessibilityBridgeAlive]) {
793 return;
794 }
795 [self bridge]->AccessibilityObjectDidLoseFocus([self uid]);
797 [self bridge]->DispatchSemanticsAction([self uid],
799 }
800}
801
802@end
803
804@implementation FlutterSemanticsObject {
805}
806
807#pragma mark - Override base class designated initializers
808
809// Method declared as unavailable in the interface
810- (instancetype)init {
811 [self release];
812 [super doesNotRecognizeSelector:_cmd];
813 return nil;
814}
815
816#pragma mark - Designated initializers
817
818- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
819 uid:(int32_t)uid {
820 self = [super initWithBridge:bridge uid:uid];
821 return self;
822}
823
824#pragma mark - UIAccessibility overrides
825
826- (UIAccessibilityTraits)accessibilityTraits {
827 UIAccessibilityTraits traits = UIAccessibilityTraitNone;
828 if ([self node].HasAction(flutter::SemanticsAction::kIncrease) ||
829 [self node].HasAction(flutter::SemanticsAction::kDecrease)) {
830 traits |= UIAccessibilityTraitAdjustable;
831 }
832 // This should also capture radio buttons.
835 traits |= UIAccessibilityTraitButton;
836 }
837 if ([self node].HasFlag(flutter::SemanticsFlags::kIsSelected)) {
838 traits |= UIAccessibilityTraitSelected;
839 }
840 if ([self node].HasFlag(flutter::SemanticsFlags::kIsButton)) {
841 traits |= UIAccessibilityTraitButton;
842 }
844 ![self node].HasFlag(flutter::SemanticsFlags::kIsEnabled)) {
845 traits |= UIAccessibilityTraitNotEnabled;
846 }
847 if ([self node].HasFlag(flutter::SemanticsFlags::kIsHeader)) {
848 traits |= UIAccessibilityTraitHeader;
849 }
850 if ([self node].HasFlag(flutter::SemanticsFlags::kIsImage)) {
851 traits |= UIAccessibilityTraitImage;
852 }
853 if ([self node].HasFlag(flutter::SemanticsFlags::kIsLiveRegion)) {
854 traits |= UIAccessibilityTraitUpdatesFrequently;
855 }
856 if ([self node].HasFlag(flutter::SemanticsFlags::kIsLink)) {
857 traits |= UIAccessibilityTraitLink;
858 }
859 if (traits == UIAccessibilityTraitNone && ![self hasChildren] &&
860 [[self accessibilityLabel] length] != 0 &&
862 traits = UIAccessibilityTraitStaticText;
863 }
864 return traits;
865}
866
867@end
868
870@property(nonatomic, retain) UIView* platformView;
871@end
872
874
875- (instancetype)initWithBridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
876 uid:(int32_t)uid
877 platformView:(nonnull FlutterTouchInterceptingView*)platformView {
878 if (self = [super initWithBridge:bridge uid:uid]) {
879 _platformView = [platformView retain];
880 [platformView setFlutterAccessibilityContainer:self];
881 }
882 return self;
883}
884
885- (void)dealloc {
886 [_platformView release];
887 _platformView = nil;
888 [super dealloc];
889}
890
892 return _platformView;
893}
894
895@end
896
897@implementation SemanticsObjectContainer {
898 SemanticsObject* _semanticsObject;
900}
901
902#pragma mark - initializers
903
904// Method declared as unavailable in the interface
905- (instancetype)init {
906 [self release];
907 [super doesNotRecognizeSelector:_cmd];
908 return nil;
909}
910
911- (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject
912 bridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge {
913 FML_DCHECK(semanticsObject) << "semanticsObject must be set";
914 // Initialize with the UIView as the container.
915 // The UIView will not necessarily be accessibility parent for this object.
916 // The bridge informs the OS of the actual structure via
917 // `accessibilityContainer` and `accessibilityElementAtIndex`.
918 self = [super initWithAccessibilityContainer:bridge->view()];
919
920 if (self) {
921 _semanticsObject = semanticsObject;
922 _bridge = bridge;
923 }
924
925 return self;
926}
927
928#pragma mark - UIAccessibilityContainer overrides
929
930- (NSInteger)accessibilityElementCount {
931 NSInteger count = [[_semanticsObject children] count] + 1;
932 return count;
933}
934
935- (nullable id)accessibilityElementAtIndex:(NSInteger)index {
936 if (index < 0 || index >= [self accessibilityElementCount]) {
937 return nil;
938 }
939 if (index == 0) {
940 return _semanticsObject.nativeAccessibility;
941 }
942
943 SemanticsObject* child = [_semanticsObject children][index - 1];
944
945 if ([child hasChildren]) {
946 return [child accessibilityContainer];
947 }
948 return child.nativeAccessibility;
949}
950
951- (NSInteger)indexOfAccessibilityElement:(id)element {
952 if (element == _semanticsObject.nativeAccessibility) {
953 return 0;
954 }
955
956 NSArray<SemanticsObject*>* children = [_semanticsObject children];
957 for (size_t i = 0; i < [children count]; i++) {
958 SemanticsObject* child = children[i];
959 if ((![child hasChildren] && child.nativeAccessibility == element) ||
960 ([child hasChildren] && [child.nativeAccessibility accessibilityContainer] == element)) {
961 return i + 1;
962 }
963 }
964 return NSNotFound;
965}
966
967#pragma mark - UIAccessibilityElement protocol
968
969- (BOOL)isAccessibilityElement {
970 return NO;
971}
972
973- (CGRect)accessibilityFrame {
974 return [_semanticsObject accessibilityFrame];
975}
976
977- (id)accessibilityContainer {
978 if (!_bridge) {
979 return nil;
980 }
981 return ([_semanticsObject uid] == kRootNodeId)
982 ? _bridge->view()
983 : [[_semanticsObject parent] accessibilityContainer];
984}
985
986#pragma mark - UIAccessibilityAction overrides
987
988- (BOOL)accessibilityScroll:(UIAccessibilityScrollDirection)direction {
989 return [_semanticsObject accessibilityScroll:direction];
990}
991
992@end
int count
constexpr int32_t kRootNodeId
constexpr float kScrollExtentMaxForInf
NSMutableArray< SemanticsObject * > * _children
NSMutableArray< SemanticsObject * > * _childrenInHitTestOrder
fml::WeakPtr< flutter::AccessibilityBridgeIos > _bridge
BOOL _inDealloc
void ApplyTransform(SkPath &orig, SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar pers0, SkScalar pers1, SkScalar pers2)
void setFlutterAccessibilityContainer:(NSObject *flutterAccessibilityContainer)
Definition SkM44.h:150
virtual UIView * view() const =0
static MallocMapping Copy(const T *begin, const T *end)
Definition mapping.h:162
T * get() const
Definition weak_ptr.h:87
static SkString identifier(const FontFamilyDesc &family, const FontDesc &font)
GLFWwindow * window
Definition main.cc:45
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
GAsyncResult * result
#define FML_DCHECK(condition)
Definition logging.h:103
UIAccessibilityTraits accessibilityTraits()
SemanticsObject * parent
void privateSetParent:(SemanticsObject *parent)
flutter::SemanticsNode node
NSArray< SemanticsObject * > * children
instancetype initWithBridge:uid:(fml::WeakPtr< flutter::AccessibilityBridgeIos > bridge,[uid] int32_t NS_DESIGNATED_INITIALIZER)
fml::WeakPtr< flutter::AccessibilityBridgeIos > bridge
fml::scoped_nsobject< UIScrollView > _scrollView
size_t length
sk_sp< SkBlender > blender SkRect rect
Definition SkRecords.h:350
auto WeakPtr(std::shared_ptr< T > pointer)
const int kScrollableSemanticsFlags
const int kHorizontalScrollSemanticsActions
const int kVerticalScrollSemanticsActions
std::vector< StringAttributePtr > StringAttributes
SINT bool isfinite(const Vec< N, T > &v)
Definition SkVx.h:1003
init(device_serial, adb_binary)
Definition _adb_path.py:12
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition p3.cpp:47
const Scalar scale
static constexpr SkPoint Make(float x, float y)
constexpr float y() const
constexpr float x() const
Definition SkM44.h:98
float w
Definition SkM44.h:99
float y
Definition SkM44.h:99
float x
Definition SkM44.h:99
const uintptr_t id
int BOOL