diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 84d3c9fd3f98a..783be71d243df 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2701,6 +2701,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextI ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelay.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelay.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelayTest.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUmbrellaImport.m + ../../../flutter/LICENSE @@ -2722,6 +2723,8 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_I ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest_mrc.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController_FlutterScreenAndSceneIfLoadedTest.mm + ../../../flutter/LICENSE @@ -5473,6 +5476,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInp FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelay.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelay.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTextureRegistryRelayTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUIPressProxy.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterUmbrellaImport.m @@ -5494,6 +5498,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/KeyCodeMap_Int FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest_mrc.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/UIViewController_FlutterScreenAndSceneIfLoadedTest.mm diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index fe180c2af422f..b8bd60198ae0a 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -238,8 +238,11 @@ source_set("ios_test_flutter_mrc") { "framework/Source/FlutterEngineTest_mrc.mm", "framework/Source/FlutterPlatformPluginTest.mm", "framework/Source/FlutterPlatformViewsTest.mm", + "framework/Source/FlutterTouchInterceptingView_Test.h", "framework/Source/FlutterViewControllerTest_mrc.mm", "framework/Source/FlutterViewTest.mm", + "framework/Source/SemanticsObjectTestMocks.h", + "framework/Source/SemanticsObjectTest_mrc.mm", "framework/Source/VsyncWaiterIosTest.mm", "framework/Source/accessibility_bridge_test.mm", "platform_message_handler_ios_test.mm", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index bf7e607878d1b..6c3bcd9ccdaab 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -406,10 +406,15 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect, } UIView* FlutterPlatformViewsController::GetPlatformViewByID(int64_t view_id) { + return [GetFlutterTouchInterceptingViewByID(view_id) embeddedView]; +} + +FlutterTouchInterceptingView* FlutterPlatformViewsController::GetFlutterTouchInterceptingViewByID( + int64_t view_id) { if (views_.empty()) { return nil; } - return [touch_interceptors_[view_id].get() embeddedView]; + return touch_interceptors_[view_id].get(); } long FlutterPlatformViewsController::FindFirstResponderPlatformViewId() { @@ -957,6 +962,10 @@ @implementation FlutterTouchInterceptingView { fml::scoped_nsobject _delayingRecognizer; FlutterPlatformViewGestureRecognizersBlockingPolicy _blockingPolicy; UIView* _embeddedView; + // The used as the accessiblityContainer. + // The `accessiblityContainer` is used in UIKit to determine the parent of this accessibility + // node. + NSObject* _flutterAccessibilityContainer; } - (instancetype)initWithEmbeddedView:(UIView*)embeddedView platformViewsController: @@ -1035,6 +1044,14 @@ - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { } +- (void)setFlutterAccessibilityContainer:(NSObject*)flutterAccessibilityContainer { + _flutterAccessibilityContainer = flutterAccessibilityContainer; +} + +- (id)accessibilityContainer { + return _flutterAccessibilityContainer; +} + @end @implementation DelayingGestureRecognizer { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 1e6dad56089aa..c55111136df9b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -11,6 +11,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" @@ -3099,4 +3100,12 @@ - (void)testOnlyPlatformViewsAreRemovedWhenReset { XCTAssertEqual(mockFlutterView.subviews.firstObject, someView); } +- (void)testFlutterTouchInterceptingViewLinksToAccessibilityContainer { + FlutterTouchInterceptingView* touchInteceptorView = + [[[FlutterTouchInterceptingView alloc] init] autorelease]; + NSObject* container = [[[NSObject alloc] init] autorelease]; + [touchInteceptorView setFlutterAccessibilityContainer:container]; + XCTAssertEqualObjects([touchInteceptorView accessibilityContainer], container); +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 43cb43cf4e33b..e18569868115f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -12,6 +12,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" @class FlutterTouchInterceptingView; @@ -230,10 +231,17 @@ class FlutterPlatformViewsController { // Returns the `FlutterPlatformView`'s `view` object associated with the view_id. // // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or - // a `FlutterPlatformView` object asscociated with the view_id cannot be found, the method + // a `FlutterPlatformView` object associated with the view_id cannot be found, the method // returns nil. UIView* GetPlatformViewByID(int64_t view_id); + // Returns the `FlutterTouchInterceptingView` with the view_id. + // + // If the `FlutterPlatformViewsController` does not contain any `FlutterPlatformView` object or + // a `FlutterPlatformView` object associated with the view_id cannot be found, the method + // returns nil. + FlutterTouchInterceptingView* GetFlutterTouchInterceptingViewByID(int64_t view_id); + PostPrerollResult PostPrerollAction( const fml::RefPtr& raster_thread_merger); @@ -424,6 +432,9 @@ class FlutterPlatformViewsController { // Get embedded view - (UIView*)embeddedView; + +// Sets flutterAccessibilityContainer as this view's accessibilityContainer. +- (void)setFlutterAccessibilityContainer:(NSObject*)flutterAccessibilityContainer; @end @interface UIView (FirstResponder) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h new file mode 100644 index 0000000000000..52f4f465c0f16 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h @@ -0,0 +1,14 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" + +#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTER_TOUCH_INTERCEPTING_VIEW_TEST_H_ +#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTER_TOUCH_INTERCEPTING_VIEW_TEST_H_ + +@interface FlutterTouchInterceptingView (Tests) +- (id)accessibilityContainer; +@end + +#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTER_TOUCH_INTERCEPTING_VIEW_TESTS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h index f7c611dec2ced..8a431193d1a8d 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h @@ -18,6 +18,7 @@ constexpr float kScrollExtentMaxForInf = 1000; @class FlutterCustomAccessibilityAction; @class FlutterPlatformViewSemanticsContainer; +@class FlutterTouchInterceptingView; /** * A node in the iOS semantics tree. This object is a wrapper over a native accessibiliy @@ -171,7 +172,8 @@ constexpr float kScrollExtentMaxForInf = 1000; - (instancetype)initWithBridge:(fml::WeakPtr)bridge uid:(int32_t)uid - platformView:(UIView*)platformView NS_DESIGNATED_INITIALIZER; + platformView:(FlutterTouchInterceptingView*)platformView + NS_DESIGNATED_INITIALIZER; @end diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index 5889a32463892..8f278fe31a721 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -157,9 +157,7 @@ @interface FlutterScrollableSemanticsObject () @property(nonatomic, retain) FlutterSemanticsScrollView* scrollView; @end -@implementation FlutterScrollableSemanticsObject { - fml::scoped_nsobject _container; -} +@implementation FlutterScrollableSemanticsObject - (instancetype)initWithBridge:(fml::WeakPtr)bridge uid:(int32_t)uid { @@ -865,9 +863,10 @@ @implementation FlutterPlatformViewSemanticsContainer - (instancetype)initWithBridge:(fml::WeakPtr)bridge uid:(int32_t)uid - platformView:(nonnull UIView*)platformView { + platformView:(nonnull FlutterTouchInterceptingView*)platformView { if (self = [super initWithBridge:bridge uid:uid]) { _platformView = [platformView retain]; + [platformView setFlutterAccessibilityContainer:self]; } return self; } @@ -882,12 +881,6 @@ - (id)nativeAccessibility { return _platformView; } -#pragma mark - UIAccessibilityContainer overrides - -- (NSArray*)accessibilityElements { - return @[ _platformView ]; -} - @end @implementation SemanticsObjectContainer { diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index 2a3f7e799e40f..3d7f2cdf0163e 100644 --- a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -7,97 +7,13 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h" FLUTTER_ASSERT_ARC -const CGRect kScreenSize = CGRectMake(0, 0, 600, 800); - -namespace flutter { -namespace { - -class SemanticsActionObservation { - public: - SemanticsActionObservation(int32_t observed_id, SemanticsAction observed_action) - : id(observed_id), action(observed_action) {} - - int32_t id; - SemanticsAction action; -}; - -class MockAccessibilityBridge : public AccessibilityBridgeIos { - public: - MockAccessibilityBridge() : observations({}) { - view_ = [[UIView alloc] initWithFrame:kScreenSize]; - 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 { - SemanticsActionObservation observation(id, action); - observations.push_back(observation); - } - void DispatchSemanticsAction(int32_t id, - SemanticsAction action, - fml::MallocMapping args) override { - SemanticsActionObservation observation(id, action); - observations.push_back(observation); - } - void AccessibilityObjectDidBecomeFocused(int32_t id) override {} - void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { - return nil; - } - std::vector observations; - bool isVoiceOverRunningValue; - - private: - UIView* view_; - UIWindow* window_; -}; - -class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { - public: - MockAccessibilityBridgeNoWindow() : observations({}) { - view_ = [[UIView alloc] initWithFrame:kScreenSize]; - } - 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 { - SemanticsActionObservation observation(id, action); - observations.push_back(observation); - } - void DispatchSemanticsAction(int32_t id, - SemanticsAction action, - fml::MallocMapping args) override { - SemanticsActionObservation observation(id, action); - observations.push_back(observation); - } - void AccessibilityObjectDidBecomeFocused(int32_t id) override {} - void AccessibilityObjectDidLoseFocus(int32_t id) override {} - std::shared_ptr GetPlatformViewsController() const override { - return nil; - } - std::vector observations; - bool isVoiceOverRunningValue; - - private: - UIView* view_; -}; -} // namespace -} // namespace flutter - @interface SemanticsObjectTest : XCTestCase @end -@interface SemanticsObject (Tests) -- (BOOL)accessibilityScrollToVisible; -- (BOOL)accessibilityScrollToVisibleWithChild:(id)child; -- (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event; -@end - @implementation SemanticsObjectTest - (void)testCreate { @@ -203,54 +119,6 @@ - (void)testAccessibilityHitTestNoFocusableItem { XCTAssertNil(hitTestResult); } -- (void)testAccessibilityHitTestSearchCanReturnPlatformView { - fml::WeakPtrFactory factory( - new flutter::MockAccessibilityBridge()); - fml::WeakPtr bridge = factory.GetWeakPtr(); - SemanticsObject* object0 = [[SemanticsObject alloc] initWithBridge:bridge uid:0]; - SemanticsObject* object1 = [[SemanticsObject alloc] initWithBridge:bridge uid:1]; - SemanticsObject* object3 = [[SemanticsObject alloc] initWithBridge:bridge uid:3]; - UIView* platformView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; - FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer = - [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge - uid:1 - platformView:platformView]; - - object0.children = @[ object1 ]; - object0.childrenInHitTestOrder = @[ object1 ]; - object1.children = @[ platformViewSemanticsContainer, object3 ]; - object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ]; - - flutter::SemanticsNode node0; - node0.id = 0; - node0.rect = SkRect::MakeXYWH(0, 0, 200, 200); - node0.label = "0"; - [object0 setSemanticsNode:&node0]; - - flutter::SemanticsNode node1; - node1.id = 1; - node1.rect = SkRect::MakeXYWH(0, 0, 200, 200); - node1.label = "1"; - [object1 setSemanticsNode:&node1]; - - flutter::SemanticsNode node2; - node2.id = 2; - node2.rect = SkRect::MakeXYWH(0, 0, 100, 100); - node2.label = "2"; - [platformViewSemanticsContainer setSemanticsNode:&node2]; - - flutter::SemanticsNode node3; - node3.id = 3; - node3.rect = SkRect::MakeXYWH(0, 0, 200, 200); - node3.label = "3"; - [object3 setSemanticsNode:&node3]; - - CGPoint point = CGPointMake(10, 10); - id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil]; - - XCTAssertEqual(hitTestResult, platformView); -} - - (void)testAccessibilityScrollToVisible { fml::WeakPtrFactory factory( new flutter::MockAccessibilityBridge()); @@ -897,27 +765,6 @@ - (void)testShouldDispatchShowOnScreenActionForHidden { XCTAssertTrue(bridge->observations[0].action == flutter::SemanticsAction::kShowOnScreen); } -- (void)testFlutterPlatformViewSemanticsContainer { - fml::WeakPtrFactory factory( - new flutter::MockAccessibilityBridge()); - fml::WeakPtr bridge = factory.GetWeakPtr(); - __weak UIView* weakPlatformView; - @autoreleasepool { - UIView* platformView = [[UIView alloc] init]; - - FlutterPlatformViewSemanticsContainer* container = - [[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge - uid:1 - platformView:platformView]; - XCTAssertEqualObjects(container.accessibilityElements, @[ platformView ]); - weakPlatformView = platformView; - XCTAssertNotNil(weakPlatformView); - } - // Check if there's no more strong references to `platformView` after container and platformView - // are released. - XCTAssertNil(weakPlatformView); -} - - (void)testFlutterSwitchSemanticsObjectMatchesUISwitch { fml::WeakPtrFactory factory( new flutter::MockAccessibilityBridge()); diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h new file mode 100644 index 0000000000000..0f0c0303d4a98 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h @@ -0,0 +1,95 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_SEMANTICS_OBJECT_TEST_MOCKS_H_ +#define SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_SEMANTICS_OBJECT_TEST_MOCKS_H_ + +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" + +const CGRect kScreenSize = CGRectMake(0, 0, 600, 800); + +namespace flutter { +namespace { + +class SemanticsActionObservation { + public: + SemanticsActionObservation(int32_t observed_id, SemanticsAction observed_action) + : id(observed_id), action(observed_action) {} + + int32_t id; + SemanticsAction action; +}; + +class MockAccessibilityBridge : public AccessibilityBridgeIos { + public: + MockAccessibilityBridge() : observations({}) { + view_ = [[UIView alloc] initWithFrame:kScreenSize]; + 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 { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void DispatchSemanticsAction(int32_t id, + SemanticsAction action, + fml::MallocMapping args) override { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void AccessibilityObjectDidBecomeFocused(int32_t id) override {} + void AccessibilityObjectDidLoseFocus(int32_t id) override {} + std::shared_ptr GetPlatformViewsController() const override { + return nil; + } + std::vector observations; + bool isVoiceOverRunningValue; + + private: + UIView* view_; + UIWindow* window_; +}; + +class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { + public: + MockAccessibilityBridgeNoWindow() : observations({}) { + view_ = [[UIView alloc] initWithFrame:kScreenSize]; + } + 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 { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void DispatchSemanticsAction(int32_t id, + SemanticsAction action, + fml::MallocMapping args) override { + SemanticsActionObservation observation(id, action); + observations.push_back(observation); + } + void AccessibilityObjectDidBecomeFocused(int32_t id) override {} + void AccessibilityObjectDidLoseFocus(int32_t id) override {} + std::shared_ptr GetPlatformViewsController() const override { + return nil; + } + std::vector observations; + bool isVoiceOverRunningValue; + + private: + UIView* view_; +}; +} // namespace +} // namespace flutter + +@interface SemanticsObject (Tests) +- (BOOL)accessibilityScrollToVisible; +- (BOOL)accessibilityScrollToVisibleWithChild:(id)child; +- (id)_accessibilityHitTest:(CGPoint)point withEvent:(UIEvent*)event; +@end + +#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_SEMANTICS_OBJECT_TEST_MOCKS_H_ diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest_mrc.mm new file mode 100644 index 0000000000000..0567e37c0e30e --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest_mrc.mm @@ -0,0 +1,89 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTouchInterceptingView_Test.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h" + +FLUTTER_ASSERT_NOT_ARC + +@interface SemanticsObjectTestMRC : XCTestCase +@end + +@implementation SemanticsObjectTestMRC + +- (void)testAccessibilityHitTestSearchCanReturnPlatformView { + fml::WeakPtrFactory factory( + new flutter::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + SemanticsObject* object0 = [[[SemanticsObject alloc] initWithBridge:bridge uid:0] autorelease]; + SemanticsObject* object1 = [[[SemanticsObject alloc] initWithBridge:bridge uid:1] autorelease]; + SemanticsObject* object3 = [[[SemanticsObject alloc] initWithBridge:bridge uid:3] autorelease]; + FlutterTouchInterceptingView* platformView = + [[[FlutterTouchInterceptingView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)] autorelease]; + FlutterPlatformViewSemanticsContainer* platformViewSemanticsContainer = + [[[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge + uid:1 + platformView:platformView] autorelease]; + + object0.children = @[ object1 ]; + object0.childrenInHitTestOrder = @[ object1 ]; + object1.children = @[ platformViewSemanticsContainer, object3 ]; + object1.childrenInHitTestOrder = @[ platformViewSemanticsContainer, object3 ]; + + flutter::SemanticsNode node0; + node0.id = 0; + node0.rect = SkRect::MakeXYWH(0, 0, 200, 200); + node0.label = "0"; + [object0 setSemanticsNode:&node0]; + + flutter::SemanticsNode node1; + node1.id = 1; + node1.rect = SkRect::MakeXYWH(0, 0, 200, 200); + node1.label = "1"; + [object1 setSemanticsNode:&node1]; + + flutter::SemanticsNode node2; + node2.id = 2; + node2.rect = SkRect::MakeXYWH(0, 0, 100, 100); + node2.label = "2"; + [platformViewSemanticsContainer setSemanticsNode:&node2]; + + flutter::SemanticsNode node3; + node3.id = 3; + node3.rect = SkRect::MakeXYWH(0, 0, 200, 200); + node3.label = "3"; + [object3 setSemanticsNode:&node3]; + + CGPoint point = CGPointMake(10, 10); + id hitTestResult = [object0 _accessibilityHitTest:point withEvent:nil]; + + XCTAssertEqual(hitTestResult, platformView); +} + +- (void)testFlutterPlatformViewSemanticsContainer { + fml::WeakPtrFactory factory( + new flutter::MockAccessibilityBridge()); + fml::WeakPtr bridge = factory.GetWeakPtr(); + FlutterTouchInterceptingView* platformView = + [[[FlutterTouchInterceptingView alloc] init] autorelease]; + @autoreleasepool { + FlutterPlatformViewSemanticsContainer* container = + [[[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:bridge + uid:1 + platformView:platformView] autorelease]; + XCTAssertEqualObjects(platformView.accessibilityContainer, container); + XCTAssertEqual(platformView.retainCount, 2u); + } + // Check if there's no more strong references to `platformView` after container and platformView + // are released. + XCTAssertEqual(platformView.retainCount, 1u); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm index bf19109e92d07..27b33ad4ca3d4 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm @@ -279,7 +279,7 @@ static void ReplaceSemanticsObject(SemanticsObject* oldObject, return [[[FlutterPlatformViewSemanticsContainer alloc] initWithBridge:weak_ptr uid:node.id - platformView:weak_ptr->GetPlatformViewsController()->GetPlatformViewByID( + platformView:weak_ptr->GetPlatformViewsController()->GetFlutterTouchInterceptingViewByID( node.platformViewId)] autorelease]; } else { return [[[FlutterSemanticsObject alloc] initWithBridge:weak_ptr uid:node.id] autorelease];