Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

invalidate a11y #1260

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion Source/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import <AsyncDisplayKit/AsyncDisplayKit+Debug.h>
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
#import <AsyncDisplayKit/ASCellNode+Internal.h>
#import <AsyncDisplayKit/_ASDisplayViewAccessiblity.h>

#import <objc/runtime.h>

Expand Down Expand Up @@ -2286,6 +2287,7 @@ - (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnod
if (disableNotifications) {
[subnode __decrementVisibilityNotificationsDisabled];
}
[subnode invalidateAccessibleElementsIfNeeded];
}

/*
Expand Down Expand Up @@ -2620,7 +2622,9 @@ - (void)_removeFromSupernode
ASDisplayNodeAssertThreadAffinity(self);
// TODO: Disabled due to PR: https://github.com/TextureGroup/Texture/pull/1204
// ASAssertUnlocked(__instanceLock__);


[self invalidateAccessibleElementsIfNeeded];

__instanceLock__.lock();
__weak ASDisplayNode *supernode = _supernode;
__weak UIView *view = _view;
Expand Down Expand Up @@ -3674,6 +3678,48 @@ - (UIAccessibilityTraits)defaultAccessibilityTraits
return UIAccessibilityTraitNone;
}

- (void)invalidateAccessibleElementsIfNeeded {
// The container needs to reaggregate.
[self invalidateUptoContainer];

// The view needs to reggregate.
if (self.isLayerBacked) {
[self.supernode invalidateForLayerBackedNodes];
}
}

// Find the first container and invalidated it so that the container can reaggregate next time.
- (void)invalidateUptoContainer {
if (self.isAccessibilityContainer) {
if (self.isNodeLoaded) {
if (ASDisplayNodeThreadIsMain()) {
[ASDynamicCast(self.view, _ASDisplayView) setAccessibilityElements:nil];
} else {
ASPerformBlockOnMainThread(^{
[ASDynamicCast(self.view, _ASDisplayView) setAccessibilityElements:nil];
});
}
}
return;
}
[self.supernode invalidateUptoContainer];
}

// Find first view up the tree and invalidated it.
- (void)invalidateForLayerBackedNodes {
if (self.isLayerBacked) {
[self.supernode invalidateForLayerBackedNodes];
} else if (self.isNodeLoaded) {
if (ASDisplayNodeThreadIsMain()) {
[ASDynamicCast(self.view, _ASDisplayView) setAccessibilityElements:nil];
} else {
ASPerformBlockOnMainThread(^{
[ASDynamicCast(self.view, _ASDisplayView) setAccessibilityElements:nil];
});
}
}
}

#pragma mark - Debugging (Private)

#if ASEVENTLOG_ENABLE
Expand Down
1 change: 1 addition & 0 deletions Source/ASTextNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
let currentAttributedText = self.attributedText; // Grab attributed string again in case it changed in the meantime
self.accessibilityLabel = self.defaultAccessibilityLabel;
self.isAccessibilityElement = (currentAttributedText.length != 0); // We're an accessibility element by default if there is a string.
[self invalidateAccessibleElementsIfNeeded];

#if AS_TEXTNODE_RECORD_ATTRIBUTED_STRINGS
[ASTextNode _registerAttributedText:_attributedText];
Expand Down
2 changes: 1 addition & 1 deletion Source/ASTextNode2.mm
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
// Accessiblity
self.accessibilityLabel = self.defaultAccessibilityLabel;
self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string.

[self invalidateAccessibleElementsIfNeeded];
#if AS_TEXTNODE2_RECORD_ATTRIBUTED_STRINGS
[ASTextNode _registerAttributedText:_attributedText];
#endif
Expand Down
22 changes: 22 additions & 0 deletions Source/Details/_ASDisplayView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,28 @@ - (void)addSubview:(UIView *)view
#endif
}

- (void)insertSubview:(UIView *)view atIndex:(NSInteger)index {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be called instead of addSubview above.

[super insertSubview:view atIndex:index];

#ifndef ASDK_ACCESSIBILITY_DISABLE
self.accessibilityElements = nil;
#endif
}

- (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview {
[super insertSubview:view aboveSubview:siblingSubview];
#ifndef ASDK_ACCESSIBILITY_DISABLE
self.accessibilityElements = nil;
#endif
}

- (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview {
[super insertSubview:view aboveSubview:siblingSubview];
#ifndef ASDK_ACCESSIBILITY_DISABLE
self.accessibilityElements = nil;
#endif
}

- (void)willRemoveSubview:(UIView *)subview
{
[super willRemoveSubview:subview];
Expand Down
1 change: 1 addition & 0 deletions Source/Private/ASDisplayNode+FrameworkPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc

@interface ASDisplayNode (AccessibilityInternal)
- (NSArray *)accessibilityElements;
- (void)invalidateAccessibleElementsIfNeeded;
@end;

@interface UIView (ASDisplayNodeInternal)
Expand Down
204 changes: 204 additions & 0 deletions Tests/ASDisplayViewAccessibilityTests.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASTextNode.h>


@interface ASDisplayViewAccessibilityTests : XCTestCase
@end
Expand Down Expand Up @@ -82,4 +84,206 @@ - (void)testThatContainerAccessibilityLabelOverrideStopsAggregation {
[node.view.accessibilityElements.firstObject accessibilityLabel]);
}

- (void)testAccessibilityLayerbackedNodesOperationInContainer {
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
contianer.frame = CGRectMake(50, 50, 200, 400);
contianer.backgroundColor = [UIColor grayColor];
contianer.isAccessibilityContainer = YES;

// Do any additional setup after loading the view, typically from a nib.
ASTextNode *text1 = [[ASTextNode alloc] init];
text1.layerBacked = YES;
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
text1.frame = CGRectMake(50, 100, 200, 200);
[contianer addSubnode:text1];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
XCTAssertTrue(elements.count == 1);
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);

ASTextNode *text2 = [[ASTextNode alloc] init];
text2.layerBacked = YES;
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
text2.frame = CGRectMake(50, 300, 200, 200);
[contianer addSubnode:text2];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
XCTAssertTrue(updatedElements.count == 1);
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);

ASTextNode *text3 = [[ASTextNode alloc] init];
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
text3.frame = CGRectMake(50, 400, 200, 100);
text3.layerBacked = YES;
[text2 addSubnode:text3];
[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
}

- (void)testAccessibilityNonLayerbackedNodesOperationInContainer {
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
contianer.frame = CGRectMake(50, 50, 200, 600);
contianer.backgroundColor = [UIColor grayColor];
contianer.isAccessibilityContainer = YES;

// Do any additional setup after loading the view, typically from a nib.
ASTextNode *text1 = [[ASTextNode alloc] init];
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
text1.frame = CGRectMake(50, 100, 200, 200);
[contianer addSubnode:text1];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
XCTAssertTrue(elements.count == 1);
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);

ASTextNode *text2 = [[ASTextNode alloc] init];
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
text2.frame = CGRectMake(50, 300, 200, 200);
[contianer addSubnode:text2];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
XCTAssertTrue(updatedElements.count == 1);
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello, world"]);

ASTextNode *text3 = [[ASTextNode alloc] init];
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
text3.frame = CGRectMake(50, 400, 200, 100);
[text2 addSubnode:text3];
[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello, world, !!!!"]);
}

- (void)testAccessibilityNonLayerbackedNodesOperationInNonContainer {
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
contianer.frame = CGRectMake(50, 50, 200, 600);
contianer.backgroundColor = [UIColor grayColor];

// Do any additional setup after loading the view, typically from a nib.
ASTextNode *text1 = [[ASTextNode alloc] init];
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
text1.frame = CGRectMake(50, 100, 200, 200);
[contianer addSubnode:text1];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
XCTAssertTrue(elements.count == 1);
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);

ASTextNode *text2 = [[ASTextNode alloc] init];
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
text2.frame = CGRectMake(50, 300, 200, 200);
[contianer addSubnode:text2];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
XCTAssertTrue(updatedElements.count == 2);
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);


ASTextNode *text3 = [[ASTextNode alloc] init];
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
text3.frame = CGRectMake(50, 400, 200, 100);
[text2 addSubnode:text3];
[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
//text3 won't be read out cause it's overshadowed by text2
XCTAssertTrue(updatedElements2.count == 2);
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
}

- (void)testAccessibilityLayerbackedNodesOperationInNonContainer {
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
contianer.frame = CGRectMake(50, 50, 200, 600);
contianer.backgroundColor = [UIColor grayColor];

// Do any additional setup after loading the view, typically from a nib.
ASTextNode *text1 = [[ASTextNode alloc] init];
text1.layerBacked = YES;
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
text1.frame = CGRectMake(50, 0, 100, 100);
[contianer addSubnode:text1];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
XCTAssertTrue(elements.count == 1);
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);

ASTextNode *text2 = [[ASTextNode alloc] init];
text2.layerBacked = YES;
text2.attributedText = [[NSAttributedString alloc] initWithString:@"world"];
text2.frame = CGRectMake(50, 100, 100, 100);
[contianer addSubnode:text2];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];
NSArray<UIAccessibilityElement *> *updatedElements = contianer.view.accessibilityElements;
XCTAssertTrue(updatedElements.count == 2);
XCTAssertTrue([[updatedElements.firstObject accessibilityLabel] isEqualToString:@"hello"]);
XCTAssertTrue([[updatedElements.lastObject accessibilityLabel] isEqualToString:@"world"]);

ASTextNode *text3 = [[ASTextNode alloc] init];
text3.layerBacked = YES;
text3.attributedText = [[NSAttributedString alloc] initWithString:@"!!!!"];
text3.frame = CGRectMake(50, 200, 100, 100);
[text2 addSubnode:text3];
[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *updatedElements2 = contianer.view.accessibilityElements;
//text3 won't be read out cause it's overshadowed by text2
XCTAssertTrue(updatedElements2.count == 2);
XCTAssertTrue([[updatedElements2.firstObject accessibilityLabel] isEqualToString:@"hello"]);
XCTAssertTrue([[updatedElements2.lastObject accessibilityLabel] isEqualToString:@"world"]);
}

- (void)testAccessibilityUpdatesWithElementsChanges {
ASDisplayNode *contianer = [[ASDisplayNode alloc] init];
contianer.frame = CGRectMake(50, 50, 200, 600);
contianer.backgroundColor = [UIColor grayColor];
contianer.isAccessibilityContainer = YES;
// Do any additional setup after loading the view, typically from a nib.
ASTextNode *text1 = [[ASTextNode alloc] init];
text1.layerBacked = YES;
text1.attributedText = [[NSAttributedString alloc] initWithString:@"hello"];
text1.frame = CGRectMake(50, 0, 100, 100);
[contianer addSubnode:text1];

[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements = contianer.view.accessibilityElements;
XCTAssertTrue(elements.count == 1);
XCTAssertTrue([[elements.firstObject accessibilityLabel] isEqualToString:@"hello"]);

text1.attributedText = [[NSAttributedString alloc] initWithString:@"greeting"];
[contianer layoutIfNeeded];
[contianer.layer displayIfNeeded];

NSArray<UIAccessibilityElement *> *elements2 = contianer.view.accessibilityElements;
XCTAssertTrue(elements2.count == 1);
XCTAssertTrue([[elements2.firstObject accessibilityLabel] isEqualToString:@"greeting"]);
}

@end