Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9ffa362

Browse files
committed
fix first responder update after accessibility is turned off
1 parent 70e4aa0 commit 9ffa362

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

shell/platform/darwin/macos/framework/Source/FlutterViewController.mm

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,20 @@ - (void)onAccessibilityStatusChanged:(NSNotification*)notification {
546546
if (!_engine) {
547547
return;
548548
}
549-
_engine.semanticsEnabled = !!notification.userInfo[EnhancedUserInterfaceKey];
549+
BOOL enabled = [notification.userInfo[EnhancedUserInterfaceKey] boolValue];
550+
if (!enabled && self.viewLoaded && [_textInputPlugin isFirstResponder]) {
551+
// The client (i.e. the FlutterTextField) of the textInputPlugin is a sibling
552+
// of the FlutterView. The macOS will pick the ancestor to be the next responder
553+
// when the client is removed from the view hierarchy, which is the result of
554+
// turning off semantics. This will cause the keyboard focus to stuck at the
555+
// NSWindow.
556+
//
557+
// Since we create an illustion that the FlutterTextField is below the FlutterView
558+
// in accessibility (See FlutterViewWrapper), we have to manually pick our next
559+
// responder.
560+
[self.view.window makeFirstResponder:_flutterView];
561+
}
562+
_engine.semanticsEnabled = [notification.userInfo[EnhancedUserInterfaceKey] boolValue];
550563
}
551564

552565
- (void)onSettingsChanged:(NSNotification*)notification {

shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
2929

3030
namespace {
3131

32+
// Returns an engine configured for the test fixture resource configuration.
33+
FlutterEngine* CreateTestEngine() {
34+
NSString* fixtures = @(testing::GetFixturesPath());
35+
FlutterDartProject* project = [[FlutterDartProject alloc]
36+
initWithAssetsPath:fixtures
37+
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
38+
return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
39+
}
40+
3241
NSResponder* mockResponder() {
3342
NSResponder* mock = OCMStrictClassMock([NSResponder class]);
3443
OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
@@ -98,6 +107,39 @@ + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
98107
EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
99108
}
100109

110+
TEST(FlutterViewController,
111+
MakesFlutterViewFirstResponderWhenAccessibilityIsTurnedOffAndTextInputPluginIsFirstResponder) {
112+
FlutterEngine* engine = CreateTestEngine();
113+
NSString* fixtures = @(testing::GetFixturesPath());
114+
FlutterDartProject* project = [[FlutterDartProject alloc]
115+
initWithAssetsPath:fixtures
116+
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
117+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project];
118+
[viewController loadView];
119+
[engine setViewController:viewController];
120+
// Creates a NSWindow so that sub view can be first responder.
121+
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
122+
styleMask:NSBorderlessWindowMask
123+
backing:NSBackingStoreBuffered
124+
defer:NO];
125+
window.contentView = viewController.view;
126+
// Attaches FlutterTextInputPlugin to the view;
127+
[viewController.view addSubview:viewController.textInputPlugin];
128+
// Makes sure the textInputPlugin can be the first responder.
129+
EXPECT_TRUE([window makeFirstResponder:viewController.textInputPlugin]);
130+
EXPECT_EQ([window firstResponder], viewController.textInputPlugin);
131+
// Sends a notification to turn off the accessibility.
132+
NSDictionary* userInfo = @{
133+
@"AXEnhancedUserInterface" : @(NO),
134+
};
135+
NSNotification* accessibilityOff = [NSNotification notificationWithName:@""
136+
object:nil
137+
userInfo:userInfo];
138+
[viewController onAccessibilityStatusChanged:accessibilityOff];
139+
// FlutterView becomes the first responder.
140+
EXPECT_EQ([window firstResponder], viewController.flutterView);
141+
}
142+
101143
TEST(FlutterViewControllerTest, TestKeyEventsAreSentToFramework) {
102144
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreSentToFramework]);
103145
}

shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@
3737
bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER;
3838

3939
@end
40+
41+
// Private methods made visible for testing
42+
@interface FlutterViewController (TestMethods)
43+
- (void)onAccessibilityStatusChanged:(nonnull NSNotification*)notification;
44+
@end

0 commit comments

Comments
 (0)