diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm index 5c5f444c6a196..e934c0a6b663e 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm @@ -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; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 91aa17e903a33..d13917d610eb5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -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 @@ -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]; @@ -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); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 92e87100dd0de..1bc60e4c533b4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -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) { @@ -744,14 +744,24 @@ - (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]; @@ -759,7 +769,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable EXPECT_FALSE(triedToTerminate); // Check that it doesn't crash on error. - triedToTerminate = FALSE; + triedToTerminate = NO; calledAfterTerminate = @""; nextResponse = @"error"; [engineMock handleMethodCall:methodExitApplication result:appExitResult]; @@ -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] diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index e291094eaa342..31c076dc1785f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -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;