From 55e38ef779fd9a48db4f99aaeee4fa01d04fa712 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Wed, 4 Aug 2021 08:30:02 -0700 Subject: [PATCH] Check a11y bridge alive before returning a11y container (flutter/engine#27865) --- .../ios/framework/Source/SemanticsObject.mm | 8 +++ .../framework/Source/SemanticsObjectTest.mm | 72 +++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index cb74cb42758..76e5145443b 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -207,6 +207,10 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) { } - (id)accessibilityContainer { + if (![_semanticsObject isAccessibilityBridgeAlive]) { + return nil; + } + if ([_semanticsObject hasChildren] || [_semanticsObject uid] == kRootNodeId) { if (_container == nil) { _container.reset([[SemanticsObjectContainer alloc] @@ -682,6 +686,10 @@ CGRect ConvertRectToGlobal(SemanticsObject* reference, CGRect local_rect) { } - (id)accessibilityContainer { + if (![self isAccessibilityBridgeAlive]) { + return nil; + } + if ([self hasChildren] || [self uid] == kRootNodeId) { if (_container == nil) _container.reset([[SemanticsObjectContainer alloc] initWithSemanticsObject:self diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index f475a563c16..77e0edfc73e 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -644,4 +644,76 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { XCTAssertEqual(object.accessibilityValue, nativeSwitch.accessibilityValue); } +- (void)testFlutterSemanticsObjectReturnsNilContainerWhenBridgeIsNotAlive { + FlutterSemanticsObject* parentObject; + FlutterSemanticsObject* delegate; + FlutterScrollableSemanticsObject* scrollable; + SemanticsObject* scrollable_object; + FlutterSemanticsObject* object2; + + flutter::SemanticsNode parent; + parent.id = 0; + parent.rect = SkRect::MakeXYWH(0, 0, 100, 200); + parent.label = "label"; + parent.value = "value"; + parent.hint = "hint"; + + flutter::SemanticsNode node; + node.id = 1; + node.flags = static_cast(flutter::SemanticsFlags::kHasImplicitScrolling); + node.actions = flutter::kHorizontalScrollSemanticsActions; + node.rect = SkRect::MakeXYWH(0, 0, 100, 200); + node.label = "label"; + node.value = "value"; + node.hint = "hint"; + node.scrollExtentMax = 100.0; + node.scrollPosition = 0.0; + parent.childrenInTraversalOrder.push_back(1); + + flutter::SemanticsNode node2; + node2.id = 2; + node2.rect = SkRect::MakeXYWH(0, 0, 100, 200); + node2.label = "label"; + node2.value = "value"; + node2.hint = "hint"; + node2.scrollExtentMax = 100.0; + node2.scrollPosition = 0.0; + parent.childrenInTraversalOrder.push_back(2); + + { + flutter::MockAccessibilityBridge* mock = new flutter::MockAccessibilityBridge(); + mock->isVoiceOverRunningValue = true; + fml::WeakPtrFactory factory(mock); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + parentObject = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; + [parentObject setSemanticsNode:&parent]; + + delegate = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:1]; + scrollable = [[FlutterScrollableSemanticsObject alloc] initWithSemanticsObject:delegate]; + scrollable_object = static_cast(scrollable); + [scrollable_object setSemanticsNode:&node]; + + object2 = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:2]; + [object2 setSemanticsNode:&node2]; + + parentObject.children = @[ scrollable_object, object2 ]; + [parentObject accessibilityBridgeDidFinishUpdate]; + [scrollable_object accessibilityBridgeDidFinishUpdate]; + [object2 accessibilityBridgeDidFinishUpdate]; + + // Returns the correct container if the bridge is alive. + SemanticsObjectContainer* container = + static_cast(scrollable_object.accessibilityContainer); + XCTAssertEqual(container.semanticsObject, parentObject); + SemanticsObjectContainer* container2 = + static_cast(object2.accessibilityContainer); + XCTAssertEqual(container2.semanticsObject, parentObject); + } + // The bridge pointer went out of scope and was deallocated. + + XCTAssertNil(scrollable_object.accessibilityContainer); + XCTAssertNil(object2.accessibilityContainer); +} + @end