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

[macOS] Wait for binding to be ready before requesting exits from framework #41753

Merged
merged 3 commits into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ - (NSString*)applicationName {

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnull)sender {
// If the framework has already told us to terminate, terminate immediately.
if ([[self terminationHandler] shouldTerminate]) {
if ([self terminationHandler] == nil || [[self terminationHandler] shouldTerminate]) {
return NSTerminateNow;
}

Expand Down
9 changes: 7 additions & 2 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ @implementation FlutterEngineTerminationHandler {
- (instancetype)initWithEngine:(FlutterEngine*)engine
terminator:(FlutterTerminationCallback)terminator {
self = [super init];
_acceptingRequests = NO;
_engine = engine;
_terminator = terminator ? terminator : ^(id sender) {
// Default to actually terminating the application. The terminator exists to
Expand Down Expand Up @@ -205,6 +206,11 @@ - (void)requestApplicationTermination:(id)sender
exitType:(FlutterAppExitType)type
result:(nullable FlutterResult)result {
_shouldTerminate = YES;
if (![self acceptingRequests]) {
// Until the Dart application has signaled that it is ready to handle
// termination requests, the app will just terminate when asked.
type = kFlutterAppExitTypeRequired;
}
switch (type) {
case kFlutterAppExitTypeCancelable: {
FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
Expand Down Expand Up @@ -1032,8 +1038,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
} else if ([call.method isEqualToString:@"System.exitApplication"]) {
[[self terminationHandler] handleRequestAppExitMethodCall:call.arguments result:result];
} else if ([call.method isEqualToString:@"System.initializationComplete"]) {
// TODO(gspencergoog): Handle this message to enable exit message listening.
// https://github.com/flutter/flutter/issues/126033
[self terminationHandler].acceptingRequests = YES;
result(nil);
} else {
result(FlutterMethodNotImplemented);
Expand Down
20 changes: 15 additions & 5 deletions shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
TEST_F(FlutterEngineTest, HandlesTerminationRequest) {
id engineMock = CreateMockFlutterEngine(nil);
__block NSString* nextResponse = @"exit";
__block BOOL triedToTerminate = FALSE;
__block BOOL triedToTerminate = NO;
FlutterEngineTerminationHandler* terminationHandler =
[[FlutterEngineTerminationHandler alloc] initWithEngine:engineMock
terminator:^(id sender) {
Expand Down Expand Up @@ -744,22 +744,32 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
[FlutterMethodCall methodCallWithMethodName:@"System.exitApplication"
arguments:@{@"type" : @"cancelable"}];

triedToTerminate = FALSE;
// Always terminate when the binding isn't ready (which is the default).
triedToTerminate = NO;
calledAfterTerminate = @"";
nextResponse = @"cancel";
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
EXPECT_STREQ([calledAfterTerminate UTF8String], "");
EXPECT_TRUE(triedToTerminate);

// Once the binding is ready, handle the request.
terminationHandler.acceptingRequests = YES;
triedToTerminate = NO;
calledAfterTerminate = @"";
nextResponse = @"exit";
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
EXPECT_STREQ([calledAfterTerminate UTF8String], "exit");
EXPECT_TRUE(triedToTerminate);

triedToTerminate = FALSE;
triedToTerminate = NO;
calledAfterTerminate = @"";
nextResponse = @"cancel";
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
EXPECT_STREQ([calledAfterTerminate UTF8String], "cancel");
EXPECT_FALSE(triedToTerminate);

// Check that it doesn't crash on error.
triedToTerminate = FALSE;
triedToTerminate = NO;
calledAfterTerminate = @"";
nextResponse = @"error";
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
Expand All @@ -768,7 +778,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
}

TEST_F(FlutterEngineTest, HandleAccessibilityEvent) {
__block BOOL announced = FALSE;
__block BOOL announced = NO;
id engineMock = CreateMockFlutterEngine(nil);

OCMStub([engineMock announceAccessibilityMessage:[OCMArg any]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
@interface FlutterEngineTerminationHandler : NSObject

@property(nonatomic, readonly) BOOL shouldTerminate;
@property(nonatomic, readwrite) BOOL acceptingRequests;

- (instancetype)initWithEngine:(FlutterEngine*)engine
terminator:(nullable FlutterTerminationCallback)terminator;
Expand Down