diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h index 33fbcdeb3c526..ce9347c7c44e9 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h @@ -20,7 +20,11 @@ constexpr float kScrollExtentMaxForInf = 1000; @class FlutterPlatformViewSemanticsContainer; /** - * A node in the iOS semantics tree. + * A node in the iOS semantics tree. This object is a wrapper over a native accessibiliy + * object, which is stored in the property `nativeAccessibility`. In the most case, the + * `nativeAccessibility` directly returns this object. Some subclasses such as the + * `FlutterScrollableSemanticsObject` creates a native `UIScrollView` as its `nativeAccessibility` + * so that it can interact with iOS. */ @interface SemanticsObject : UIAccessibilityElement diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index e73e776686d2b..e0bec49c0440b 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -855,15 +855,15 @@ - (nullable id)accessibilityElementAtIndex:(NSInteger)index { } - (NSInteger)indexOfAccessibilityElement:(id)element { - if (element == _semanticsObject) { + if (element == _semanticsObject.nativeAccessibility) { return 0; } NSArray* children = [_semanticsObject children]; for (size_t i = 0; i < [children count]; i++) { SemanticsObject* child = children[i]; - if ((![child hasChildren] && child == element) || - ([child hasChildren] && [child accessibilityContainer] == element)) { + if ((![child hasChildren] && child.nativeAccessibility == element) || + ([child hasChildren] && [child.nativeAccessibility accessibilityContainer] == element)) { return i + 1; } } diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 34577cab43d4f..731b1adb7b5f6 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -661,6 +661,61 @@ - (void)testLayoutChangeDoesCallNativeAccessibility { UIAccessibilityLayoutChangedNotification); } +- (void)testScrollableSemanticsContainerReturnsCorrectChildren { + flutter::MockDelegate mock_delegate; + auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest"); + flutter::TaskRunners runners(/*label=*/self.name.UTF8String, + /*platform=*/thread_task_runner, + /*raster=*/thread_task_runner, + /*ui=*/thread_task_runner, + /*io=*/thread_task_runner); + auto platform_view = std::make_unique( + /*delegate=*/mock_delegate, + /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware, + /*platform_views_controller=*/nil, + /*task_runners=*/runners); + id mockFlutterView = OCMClassMock([FlutterView class]); + id mockFlutterViewController = OCMClassMock([FlutterViewController class]); + OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView); + + OCMExpect([mockFlutterView + setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) { + if ([value count] != 1) { + return NO; + } + SemanticsObjectContainer* container = value[0]; + SemanticsObject* object = container.semanticsObject; + FlutterScrollableSemanticsObject* scrollable = + (FlutterScrollableSemanticsObject*)object.children[0]; + id nativeScrollable = scrollable.nativeAccessibility; + SemanticsObjectContainer* scrollableContainer = [nativeScrollable accessibilityContainer]; + return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1; + }]]); + auto ios_delegate = std::make_unique(); + __block auto bridge = + std::make_unique(/*view_controller=*/mockFlutterViewController, + /*platform_view=*/platform_view.get(), + /*platform_views_controller=*/nil, + /*ios_delegate=*/std::move(ios_delegate)); + + flutter::CustomAccessibilityActionUpdates actions; + flutter::SemanticsNodeUpdates nodes; + + flutter::SemanticsNode node1; + node1.id = 1; + node1.label = "node1"; + node1.flags = static_cast(flutter::SemanticsFlags::kHasImplicitScrolling); + nodes[node1.id] = node1; + flutter::SemanticsNode root_node; + root_node.id = kRootNodeId; + root_node.label = "root"; + root_node.childrenInTraversalOrder = {1}; + root_node.childrenInHitTestOrder = {1}; + nodes[root_node.id] = root_node; + bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions); + OCMVerifyAll(mockFlutterView); +} + - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate { flutter::MockDelegate mock_delegate; auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");