Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Handle termination in FlutterAppDelegate #40929

Merged
merged 1 commit into from
Apr 5, 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
2 changes: 0 additions & 2 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2618,7 +2618,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h + ../../../flutter/LICENSE
Expand Down Expand Up @@ -5214,7 +5213,6 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h
Expand Down
25 changes: 18 additions & 7 deletions shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification {
}
}

// This always returns NSTerminateNow, since by the time we get here, the
// application has already been asked if it should terminate or not, and if not,
// then termination never gets this far.
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender {
return NSTerminateNow;
}

#pragma mark Private Methods

- (NSString*)applicationName {
Expand All @@ -60,4 +53,22 @@ - (NSString*)applicationName {
return applicationName;
}

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

// Send a termination request to the framework.
FlutterEngineTerminationHandler* terminationHandler = [self terminationHandler];
[terminationHandler requestApplicationTermination:sender
exitType:kFlutterAppExitTypeCancelable
result:nil];

// Cancel termination to allow the framework to handle the request asynchronously. When the
// termination request returns from the app, if termination is desired, this method will be
// reinvoked with self.terminationHandler.shouldTerminate set to YES.
return NSTerminateCancel;
}

@end
82 changes: 0 additions & 82 deletions shell/platform/darwin/macos/framework/Source/FlutterApplication.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,6 @@
// found in the LICENSE file.

#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h"

#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
#include "flutter/shell/platform/embedder/embedder.h"

// An NSApplication subclass that implements overrides necessary for some
// Flutter features, like application lifecycle handling.
@implementation FlutterApplication

// Initialize NSApplication using the custom subclass. Check whether NSApp was
// already initialized using another class, because that would break some
// things. Warn about the mismatch only once, and only in debug builds.
+ (NSApplication*)sharedApplication {
NSApplication* app = [super sharedApplication];

// +sharedApplication initializes the global NSApp, so if we're delivering
// something other than a FlutterApplication, warn the developer once.
#ifndef FLUTTER_RELEASE
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
if (![app respondsToSelector:@selector(terminateApplication:)]) {
NSLog(@"NSApp should be of type %s, not %s.\n"
"System requests for the application to terminate will not be sent to "
"the Flutter framework, so the framework will be unable to cancel "
"those requests.\n"
"Modify the application's NSPrincipleClass to be %s in the "
"Info.plist to fix this.",
[[self className] UTF8String], [[NSApp className] UTF8String],
[[self className] UTF8String]);
}
});
#endif // !FLUTTER_RELEASE
return app;
}

// |terminate| is the entry point for orderly "quit" operations in Cocoa. This
// includes the application menu's Quit menu item and keyboard equivalent, the
// application's dock icon menu's Quit menu item, "quit" (not "force quit") in
// the Activity Monitor, and quits triggered by user logout and system restart
// and shutdown.
//
// We override the normal |terminate| implementation. Our implementation, which
// is specific to the asynchronous nature of Flutter, works by asking the
// application delegate to terminate using its |requestApplicationTermination|
// method instead of going through |applicationShouldTerminate|.
//
// The standard |applicationShouldTerminate| is not used because returning
// NSTerminateLater from that function moves the run loop into a modal dialog
// mode (NSModalPanelRunLoopMode), which stops the main run loop from processing
// messages like, for instance, the response to the method channel call, and
// code paths leading to it must be redirected to |requestApplicationTermination|.
//
// |requestApplicationTermination| differs from the standard
// |applicationShouldTerminate| in that no special event loop is run in the case
// that immediate termination is not possible (e.g., if dialog boxes allowing
// the user to cancel have to be shown, or data needs to be saved). Instead,
// requestApplicationTermination sends a method channel call to the framework asking
// it if it is OK to terminate. When that method channel call returns with a
// result, the application either terminates or continues running.
- (void)terminate:(id)sender {
FlutterAppDelegate* delegate = [self delegate];
if (!delegate || ![delegate respondsToSelector:@selector(terminationHandler)] ||
[delegate terminationHandler] == nil) {
// If there's no termination handler, then just terminate.
[super terminate:sender];
}
FlutterEngineTerminationHandler* terminationHandler =
[static_cast<FlutterAppDelegate*>([self delegate]) terminationHandler];
[terminationHandler requestApplicationTermination:sender
exitType:kFlutterAppExitTypeCancelable
result:nil];
// Return, don't exit. The application delegate is responsible for exiting on
// its own by calling |terminateApplication|.
}

// Starts the regular Cocoa application termination flow, so that plugins will
// get the appropriate notifications after the application has already decided
// to quit. This is called after the application has decided that
// it's OK to terminate.
- (void)terminateApplication:(id)sender {
[super terminate:sender];
}
@end

This file was deleted.

17 changes: 7 additions & 10 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
#include "flutter/shell/platform/embedder/embedder.h"

#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
Expand Down Expand Up @@ -176,15 +174,11 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine
_terminator = terminator ? terminator : ^(id sender) {
// Default to actually terminating the application. The terminator exists to
// allow tests to override it so that an actual exit doesn't occur.
FlutterApplication* flutterApp = [FlutterApplication sharedApplication];
if (flutterApp && [flutterApp respondsToSelector:@selector(terminateApplication:)]) {
[[FlutterApplication sharedApplication] terminateApplication:sender];
} else if (flutterApp) {
[flutterApp terminate:sender];
}
NSApplication* flutterApp = [NSApplication sharedApplication];
cbracken marked this conversation as resolved.
Show resolved Hide resolved
[flutterApp terminate:sender];
};
FlutterAppDelegate* appDelegate =
(FlutterAppDelegate*)[[FlutterApplication sharedApplication] delegate];
(FlutterAppDelegate*)[[NSApplication sharedApplication] delegate];
appDelegate.terminationHandler = self;
return self;
}
Expand All @@ -202,7 +196,7 @@ - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
FlutterAppExitType exitType =
[type isEqualTo:@"cancelable"] ? kFlutterAppExitTypeCancelable : kFlutterAppExitTypeRequired;

[self requestApplicationTermination:[FlutterApplication sharedApplication]
[self requestApplicationTermination:[NSApplication sharedApplication]
exitType:exitType
result:result];
}
Expand All @@ -212,6 +206,7 @@ - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
- (void)requestApplicationTermination:(id)sender
exitType:(FlutterAppExitType)type
result:(nullable FlutterResult)result {
_shouldTerminate = YES;
switch (type) {
case kFlutterAppExitTypeCancelable: {
FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
Expand All @@ -238,6 +233,8 @@ - (void)requestApplicationTermination:(id)sender
NSDictionary* replyArgs = (NSDictionary*)decoded_reply;
if ([replyArgs[@"response"] isEqual:@"exit"]) {
_terminator(sender);
} else if ([replyArgs[@"response"] isEqual:@"cancel"]) {
_shouldTerminate = NO;
}
if (result != nil) {
result(replyArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#include "flutter/shell/platform/common/accessibility_bridge.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
#include "flutter/shell/platform/embedder/embedder.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include <memory>

#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
Expand Down Expand Up @@ -52,11 +51,14 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
* messages through the platform channel managed by the engine.
*/
@interface FlutterEngineTerminationHandler : NSObject

@property(nonatomic, readonly) BOOL shouldTerminate;

- (instancetype)initWithEngine:(FlutterEngine*)engine
terminator:(nullable FlutterTerminationCallback)terminator;
- (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)data
result:(FlutterResult)result;
- (void)requestApplicationTermination:(FlutterApplication*)sender
- (void)requestApplicationTermination:(NSApplication*)sender
exitType:(FlutterAppExitType)type
result:(nullable FlutterResult)result;
@end
Expand Down