[macOS] Reset keyboard states on engine restart (flutter/engine#29283)

With this PR, the state of the keyboard system will be reset when the engine is reset (typically during a hot restart.)
This commit is contained in:
Tong Mu
2021-10-27 17:03:41 -07:00
committed by GitHub
parent 0a9afc2728
commit 79249cf134
11 changed files with 116 additions and 10 deletions

View File

@@ -169,7 +169,7 @@ public class FlutterNativeView implements BinaryMessenger {
}
private final class EngineLifecycleListenerImpl implements EngineLifecycleListener {
// Called by native to notify when the engine is restarted (cold reload).
// Called by native to notify right before the engine is restarted (cold reload).
@SuppressWarnings("unused")
public void onPreEngineRestart() {
if (mFlutterView != null) {

View File

@@ -53,4 +53,12 @@ FLUTTER_DARWIN_EXPORT
NS_DESIGNATED_INITIALIZER;
- (nonnull instancetype)initWithCoder:(nonnull NSCoder*)nibNameOrNil NS_DESIGNATED_INITIALIZER;
/**
* Invoked by the engine right before the engine is restarted.
*
* This should reset states to as if the application has just started. It
* usually indicates a hot restart (Shift-R in Flutter CLI.)
*/
- (void)onPreEngineRestart;
@end

View File

@@ -75,6 +75,14 @@ static FlutterLocale FlutterLocaleFromNSLocale(NSLocale* locale) {
*/
- (void)engineCallbackOnPlatformMessage:(const FlutterPlatformMessage*)message;
/**
* Invoked right before the engine is restarted.
*
* This should reset states to as if the application has just started. It
* usually indicates a hot restart (Shift-R in Flutter CLI.)
*/
- (void)engineCallbackOnPreEngineRestart;
/**
* Requests that the task be posted back the to the Flutter engine at the target time. The target
* time is in the clock used by the Flutter engine.
@@ -296,6 +304,11 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
flutterArguments.compositor = [self createFlutterCompositor];
flutterArguments.on_pre_engine_restart_callback = [](void* user_data) {
FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
[engine engineCallbackOnPreEngineRestart];
};
FlutterRendererConfig rendererConfig = [_renderer createRendererConfig];
FlutterEngineResult result = _embedderAPI.Initialize(
FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine);
@@ -584,6 +597,12 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
}
}
- (void)engineCallbackOnPreEngineRestart {
if (_viewController) {
[_viewController onPreEngineRestart];
}
}
/**
* Note: Called from dealloc. Should not use accessors or other methods.
*/

View File

@@ -186,7 +186,7 @@ static flutter::TextRange RangeFromBaseExtent(NSNumber* base,
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[unsafeSelf handleMethodCall:call result:result];
}];
_textInputContext = [[NSTextInputContext alloc] initWithClient:self];
_textInputContext = [[NSTextInputContext alloc] initWithClient:unsafeSelf];
_previouslyPressedFlags = 0;
_flutterViewController = viewController;
@@ -208,6 +208,11 @@ static flutter::TextRange RangeFromBaseExtent(NSNumber* base,
- (void)dealloc {
[_channel setMethodCallHandler:nil];
if (_textInputContext) {
[_textInputContext deactivate];
[_textInputContext discardMarkedText];
_textInputContext = nil;
}
}
#pragma mark - Private

View File

@@ -138,6 +138,11 @@ struct MouseState {
*/
- (void)addInternalPlugins;
/**
* Creates and registers keyboard related components.
*/
- (void)initializeKeyboard;
/**
* Calls dispatchMouseEvent:phase: with a phase determined by self.mouseState.
*
@@ -358,6 +363,10 @@ static void CommonInit(FlutterViewController* controller) {
[self configureTrackingArea];
}
- (void)onPreEngineRestart {
[self initializeKeyboard];
}
#pragma mark - Private methods
- (BOOL)launchEngine {
@@ -431,9 +440,9 @@ static void CommonInit(FlutterViewController* controller) {
}
}
- (void)addInternalPlugins {
- (void)initializeKeyboard {
__weak FlutterViewController* weakSelf = self;
[FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
_textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:weakSelf];
_keyboardManager = [[FlutterKeyboardManager alloc] initWithOwner:weakSelf];
[_keyboardManager addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
initWithSendEvent:^(const FlutterKeyEvent& event,
@@ -451,6 +460,12 @@ static void CommonInit(FlutterViewController* controller) {
codec:[FlutterJSONMessageCodec
sharedInstance]]]];
[_keyboardManager addSecondaryResponder:_textInputPlugin];
}
- (void)addInternalPlugins {
__weak FlutterViewController* weakSelf = self;
[FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]];
[self initializeKeyboard];
_settingsChannel =
[FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings"
binaryMessenger:_engine.binaryMessenger

View File

@@ -20,6 +20,7 @@
- (bool)testKeyEventsAreNotPropagatedIfHandled;
- (bool)testFlagsChangedEventsArePropagatedIfNotHandled;
- (bool)testPerformKeyEquivalentSynthesizesKeyUp;
- (bool)testKeyboardIsRestartedOnEngineRestart;
+ (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
callback:(nullable FlutterKeyEventCallback)callback
@@ -171,6 +172,10 @@ TEST(FlutterViewControllerTest, TestPerformKeyEquivalentSynthesizesKeyUp) {
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testPerformKeyEquivalentSynthesizesKeyUp]);
}
TEST(FlutterViewControllerTest, TestKeyboardIsRestartedOnEngineRestart) {
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyboardIsRestartedOnEngineRestart]);
}
} // namespace flutter::testing
@implementation FlutterViewControllerTestObjC
@@ -456,6 +461,60 @@ TEST(FlutterViewControllerTest, TestPerformKeyEquivalentSynthesizesKeyUp) {
return true;
}
- (bool)testKeyboardIsRestartedOnEngineRestart {
id engineMock = OCMClassMock([FlutterEngine class]);
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
[engineMock binaryMessenger])
.andReturn(binaryMessengerMock);
__block bool called = false;
__block FlutterKeyEvent last_event;
OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
callback:nil
userData:nil])
.andDo((^(NSInvocation* invocation) {
FlutterKeyEvent* event;
[invocation getArgument:&event atIndex:2];
called = true;
last_event = *event;
}));
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock
nibName:@""
bundle:nil];
[viewController viewWillAppear];
NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
location:NSZeroPoint
modifierFlags:0x100
timestamp:0
windowNumber:0
context:nil
characters:@"a"
charactersIgnoringModifiers:@"a"
isARepeat:FALSE
keyCode:0];
const uint64_t kPhysicalKeyA = 0x70004;
// Send KeyA key down event twice. Without restarting the keyboard during
// onPreEngineRestart, the second event received will be an empty event with
// physical key 0x0 because duplicate key down events are ignored.
called = false;
[viewController keyDown:keyADown];
EXPECT_TRUE(called);
EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
EXPECT_EQ(last_event.physical, kPhysicalKeyA);
[viewController onPreEngineRestart];
called = false;
[viewController keyDown:keyADown];
EXPECT_TRUE(called);
EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
EXPECT_EQ(last_event.physical, kPhysicalKeyA);
return true;
}
+ (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
callback:(nullable FlutterKeyEventCallback)callback
userData:(nullable void*)userData {

View File

@@ -1580,7 +1580,7 @@ typedef struct {
// callbacks on `log_message_callback`. Defaults to "flutter" if unspecified.
const char* log_tag;
// A callback that is invoked when the engine is restarted.
// A callback that is invoked right before the engine is restarted.
//
// This optional callback is typically used to reset states to as if the
// engine has just been started, and usually indicates the user has requested

View File

@@ -48,7 +48,7 @@ struct _FlEngine {
gpointer update_semantics_node_handler_data;
GDestroyNotify update_semantics_node_handler_destroy_notify;
// Function to call when the engine is restarted.
// Function to call right before the engine is restarted.
FlEngineOnPreEngineRestartHandler on_pre_engine_restart_handler;
gpointer on_pre_engine_restart_handler_data;
GDestroyNotify on_pre_engine_restart_handler_destroy_notify;
@@ -284,7 +284,7 @@ static void fl_engine_update_semantics_node_cb(const FlutterSemanticsNode* node,
}
}
// Called when the engine is restarted.
// Called right before the engine is restarted.
//
// This method should reset states to as if the engine has just been started,
// which usually indicates the user has requested a hot restart (Shift-R in the

View File

@@ -133,7 +133,7 @@ void fl_engine_set_update_semantics_node_handler(
* @destroy_notify: (allow-none): a function which gets called to free
* @user_data, or %NULL.
*
* Registers the function called when the engine is restarted.
* Registers the function called right before the engine is restarted.
*/
void fl_engine_set_on_pre_engine_restart_handler(
FlEngine* engine,

View File

@@ -102,7 +102,7 @@ static void fl_view_init_keyboard(FlView* self) {
FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger)));
}
// Called when the engine is restarted.
// Invoked by the engine right before the engine is restarted.
//
// This method should reset states to be as if the engine had just been started,
// which usually indicates the user has requested a hot restart (Shift-R in the

View File

@@ -90,7 +90,7 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate,
// Returns the frame buffer id for the engine to render to.
uint32_t GetFrameBufferId(size_t width, size_t height);
// Called when the engine is restarted.
// Invoked by the engine right before the engine is restarted.
//
// This should reset necessary states to as if the view has just been
// created. This is typically caused by a hot restart (Shift-R in CLI.)