diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 19fbb42a0d2d..fefaf4e791fd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1228,7 +1228,8 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification { platformView->SetSemanticsEnabled(true); platformView->SetAccessibilityFeatures(flags); #else - bool enabled = UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning(); + _isVoiceOverRunning = UIAccessibilityIsVoiceOverRunning(); + bool enabled = _isVoiceOverRunning || UIAccessibilityIsSwitchControlRunning(); if (enabled) flags |= static_cast(flutter::AccessibilityFeatureFlag::kAccessibleNavigation); platformView->SetSemanticsEnabled(enabled || UIAccessibilityIsSpeakScreenEnabled()); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 80bad5ed20fb..c3ac4bd2a61b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -26,6 +26,7 @@ extern NSNotificationName const FlutterViewControllerShowHomeIndicator; @interface FlutterViewController () @property(nonatomic, readonly) BOOL isPresentingViewController; +@property(nonatomic, readonly) BOOL isVoiceOverRunning; - (fml::WeakPtr)getWeakPtr; - (std::shared_ptr&)platformViewsController; - (FlutterRestorationPlugin*)restorationPlugin; diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index ae739f77a5f1..1f213be05cd7 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -193,12 +193,6 @@ - (void)accessibilityBridgeDidFinishUpdate { [self setFrame:[_semanticsObject accessibilityFrame]]; [self setContentSize:[self contentSizeInternal]]; [self setContentOffset:[self contentOffsetInternal] animated:NO]; - if (self.contentSize.width > self.frame.size.width || - self.contentSize.height > self.frame.size.height) { - self.isAccessibilityElement = YES; - } else { - self.isAccessibilityElement = NO; - } } - (void)setChildren:(NSArray*)children { @@ -219,6 +213,18 @@ - (id)accessibilityContainer { return _container.get(); } +- (BOOL)isAccessibilityElement { + if (![_semanticsObject isAccessibilityBridgeAlive]) { + return NO; + } + if (self.contentSize.width > self.frame.size.width || + self.contentSize.height > self.frame.size.height) { + return !_semanticsObject.bridge->isVoiceOverRunning(); + } else { + return NO; + } +} + // private methods - (float)scrollExtentMax { diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index 7554dbf1dd64..b0379561207d 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -31,6 +31,7 @@ window_ = [[UIWindow alloc] initWithFrame:kScreenSize]; [window_ addSubview:view_]; } + bool isVoiceOverRunning() const override { return isVoiceOverRunningValue; } UIView* view() const override { return view_; } UIView* textInputView() override { return nil; } void DispatchSemanticsAction(int32_t id, SemanticsAction action) override { @@ -49,6 +50,7 @@ void AccessibilityObjectDidLoseFocus(int32_t id) override {} return nil; } std::vector observations; + bool isVoiceOverRunningValue; private: UIView* view_; @@ -344,6 +346,30 @@ - (void)testFlutterScrollableSemanticsObjectIsNotHittestable { XCTAssertEqual([scrollable hitTest:CGPointMake(10, 10) withEvent:nil], nil); } +- (void)testFlutterScrollableSemanticsObjectIsHiddenWhenVoiceOverIsRunning { + flutter::MockAccessibilityBridge* mock = new flutter::MockAccessibilityBridge(); + mock->isVoiceOverRunningValue = false; + fml::WeakPtrFactory factory(mock); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + flutter::SemanticsNode node; + node.flags = static_cast(flutter::SemanticsFlags::kHasImplicitScrolling); + node.actions = flutter::kHorizontalScrollSemanticsActions; + node.rect = SkRect::MakeXYWH(0, 0, 100, 200); + node.scrollExtentMax = 100.0; + node.scrollPosition = 0.0; + + FlutterSemanticsObject* delegate = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; + FlutterScrollableSemanticsObject* scrollable = + [[FlutterScrollableSemanticsObject alloc] initWithSemanticsObject:delegate]; + SemanticsObject* scrollable_object = static_cast(scrollable); + [scrollable_object setSemanticsNode:&node]; + [scrollable_object accessibilityBridgeDidFinishUpdate]; + XCTAssertTrue(scrollable_object.isAccessibilityElement); + mock->isVoiceOverRunningValue = true; + XCTAssertFalse(scrollable_object.isAccessibilityElement); +} + - (void)testSemanticsObjectBuildsAttributedString { fml::WeakPtrFactory factory( new flutter::MockAccessibilityBridge()); diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index c1456ab446e1..ef451b5923b9 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -18,9 +18,9 @@ #include "flutter/lib/ui/semantics/custom_accessibility_action.h" #include "flutter/lib/ui/semantics/semantics_node.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" -#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h" #include "third_party/skia/include/core/SkRect.h" @@ -68,6 +68,8 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { UIView* view() const override { return view_controller_.view; } + bool isVoiceOverRunning() const override { return view_controller_.isVoiceOverRunning; } + fml::WeakPtr GetWeakPtr(); std::shared_ptr GetPlatformViewsController() const override { diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h index a97f179ece53..f91defbf9681 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h @@ -21,6 +21,7 @@ class AccessibilityBridgeIos { public: virtual ~AccessibilityBridgeIos() = default; virtual UIView* view() const = 0; + virtual bool isVoiceOverRunning() const = 0; virtual UIView* textInputView() = 0; virtual void DispatchSemanticsAction(int32_t id, flutter::SemanticsAction action) = 0; virtual void DispatchSemanticsAction(int32_t id, 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 0b229994ce03..403bf119ddf2 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -224,6 +224,32 @@ - (void)testUpdateSemanticsOneNode { OCMVerifyAll(mockFlutterView); } +- (void)testIsVoiceOverRunning { + 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); + OCMStub([mockFlutterViewController isVoiceOverRunning]).andReturn(YES); + + __block auto bridge = + std::make_unique(/*view_controller=*/mockFlutterViewController, + /*platform_view=*/platform_view.get(), + /*platform_views_controller=*/nil); + + XCTAssertTrue(bridge->isVoiceOverRunning()); +} + - (void)testSemanticsDeallocated { @autoreleasepool { flutter::MockDelegate mock_delegate;