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

Commit 8679b67

Browse files
authored
[macOS] Handle termination in FlutterAppDelegate (#40929)
Moves application termination handling to FlutterAppDelegate. Previously, we required macOS applications using Flutter to ensure their main application class was FlutterApplication. Instead, we now do all handling in FlutterAppDelegate and FlutterEngine. There are two termination workflows to consider: * Termination requested from framework side: In this case, then engine receives a `System.exitApplication` method call, and starts the app termination workflow via `[FlutterEngine requestApplicationTermination:exitType:result]`. * Termination requested from macOS (e.g. Cmd-Q): In this case, `FlutterAppDelegate`'s `applicationShouldTerminate:` handler is invoked by AppKit, and the delegate starts the app termination workflow via `[FlutterEngine requestApplicationTermination:exitType:result]`. In either case, at this point, if the request is not cancellable, the app immediately exits. If it is cancellable, the embedder sends a `System.requestAppExit` method channel invocation to the framework, which responds with either `exit` or `cancel`. In the case of `exit` we immediately exit, otherwise we do nothing and the app continues running. This is a minor refactoring of the original approach we took in: #39836 This does not remove the FlutterApplication class, since the framework migration from NSApplication to FlutterApplication still depends on it. A followup patch with replace the migration with a reverse migration will land, then FlutterApplication will be removed. Issue: flutter/flutter#30735 No new tests since this refactors existing behaviour while retaining the same app semantics as before.
1 parent cdadefe commit 8679b67

File tree

7 files changed

+29
-131
lines changed

7 files changed

+29
-131
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2617,7 +2617,6 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil
26172617
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE
26182618
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE
26192619
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm + ../../../flutter/LICENSE
2620-
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h + ../../../flutter/LICENSE
26212620
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE
26222621
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE
26232622
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h + ../../../flutter/LICENSE
@@ -5212,7 +5211,6 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit
52125211
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm
52135212
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h
52145213
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm
5215-
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h
52165214
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h
52175215
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm
52185216
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification {
4242
}
4343
}
4444

45-
// This always returns NSTerminateNow, since by the time we get here, the
46-
// application has already been asked if it should terminate or not, and if not,
47-
// then termination never gets this far.
48-
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender {
49-
return NSTerminateNow;
50-
}
51-
5245
#pragma mark Private Methods
5346

5447
- (NSString*)applicationName {
@@ -60,4 +53,22 @@ - (NSString*)applicationName {
6053
return applicationName;
6154
}
6255

56+
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnull)sender {
57+
// If the framework has already told us to terminate, terminate immediately.
58+
if ([[self terminationHandler] shouldTerminate]) {
59+
return NSTerminateNow;
60+
}
61+
62+
// Send a termination request to the framework.
63+
FlutterEngineTerminationHandler* terminationHandler = [self terminationHandler];
64+
[terminationHandler requestApplicationTermination:sender
65+
exitType:kFlutterAppExitTypeCancelable
66+
result:nil];
67+
68+
// Cancel termination to allow the framework to handle the request asynchronously. When the
69+
// termination request returns from the app, if termination is desired, this method will be
70+
// reinvoked with self.terminationHandler.shouldTerminate set to YES.
71+
return NSTerminateCancel;
72+
}
73+
6374
@end

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

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,88 +3,6 @@
33
// found in the LICENSE file.
44

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

8-
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
9-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
10-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
11-
#include "flutter/shell/platform/embedder/embedder.h"
12-
13-
// An NSApplication subclass that implements overrides necessary for some
14-
// Flutter features, like application lifecycle handling.
157
@implementation FlutterApplication
16-
17-
// Initialize NSApplication using the custom subclass. Check whether NSApp was
18-
// already initialized using another class, because that would break some
19-
// things. Warn about the mismatch only once, and only in debug builds.
20-
+ (NSApplication*)sharedApplication {
21-
NSApplication* app = [super sharedApplication];
22-
23-
// +sharedApplication initializes the global NSApp, so if we're delivering
24-
// something other than a FlutterApplication, warn the developer once.
25-
#ifndef FLUTTER_RELEASE
26-
static dispatch_once_t onceToken = 0;
27-
dispatch_once(&onceToken, ^{
28-
if (![app respondsToSelector:@selector(terminateApplication:)]) {
29-
NSLog(@"NSApp should be of type %s, not %s.\n"
30-
"System requests for the application to terminate will not be sent to "
31-
"the Flutter framework, so the framework will be unable to cancel "
32-
"those requests.\n"
33-
"Modify the application's NSPrincipleClass to be %s in the "
34-
"Info.plist to fix this.",
35-
[[self className] UTF8String], [[NSApp className] UTF8String],
36-
[[self className] UTF8String]);
37-
}
38-
});
39-
#endif // !FLUTTER_RELEASE
40-
return app;
41-
}
42-
43-
// |terminate| is the entry point for orderly "quit" operations in Cocoa. This
44-
// includes the application menu's Quit menu item and keyboard equivalent, the
45-
// application's dock icon menu's Quit menu item, "quit" (not "force quit") in
46-
// the Activity Monitor, and quits triggered by user logout and system restart
47-
// and shutdown.
48-
//
49-
// We override the normal |terminate| implementation. Our implementation, which
50-
// is specific to the asynchronous nature of Flutter, works by asking the
51-
// application delegate to terminate using its |requestApplicationTermination|
52-
// method instead of going through |applicationShouldTerminate|.
53-
//
54-
// The standard |applicationShouldTerminate| is not used because returning
55-
// NSTerminateLater from that function moves the run loop into a modal dialog
56-
// mode (NSModalPanelRunLoopMode), which stops the main run loop from processing
57-
// messages like, for instance, the response to the method channel call, and
58-
// code paths leading to it must be redirected to |requestApplicationTermination|.
59-
//
60-
// |requestApplicationTermination| differs from the standard
61-
// |applicationShouldTerminate| in that no special event loop is run in the case
62-
// that immediate termination is not possible (e.g., if dialog boxes allowing
63-
// the user to cancel have to be shown, or data needs to be saved). Instead,
64-
// requestApplicationTermination sends a method channel call to the framework asking
65-
// it if it is OK to terminate. When that method channel call returns with a
66-
// result, the application either terminates or continues running.
67-
- (void)terminate:(id)sender {
68-
FlutterAppDelegate* delegate = [self delegate];
69-
if (!delegate || ![delegate respondsToSelector:@selector(terminationHandler)] ||
70-
[delegate terminationHandler] == nil) {
71-
// If there's no termination handler, then just terminate.
72-
[super terminate:sender];
73-
}
74-
FlutterEngineTerminationHandler* terminationHandler =
75-
[static_cast<FlutterAppDelegate*>([self delegate]) terminationHandler];
76-
[terminationHandler requestApplicationTermination:sender
77-
exitType:kFlutterAppExitTypeCancelable
78-
result:nil];
79-
// Return, don't exit. The application delegate is responsible for exiting on
80-
// its own by calling |terminateApplication|.
81-
}
82-
83-
// Starts the regular Cocoa application termination flow, so that plugins will
84-
// get the appropriate notifications after the application has already decided
85-
// to quit. This is called after the application has decided that
86-
// it's OK to terminate.
87-
- (void)terminateApplication:(id)sender {
88-
[super terminate:sender];
89-
}
908
@end

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

Lines changed: 0 additions & 27 deletions
This file was deleted.

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

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
#include "flutter/shell/platform/embedder/embedder.h"
1414

1515
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
16-
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
1716
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
18-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h"
1917
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
2018
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
2119
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
@@ -176,15 +174,11 @@ - (instancetype)initWithEngine:(FlutterEngine*)engine
176174
_terminator = terminator ? terminator : ^(id sender) {
177175
// Default to actually terminating the application. The terminator exists to
178176
// allow tests to override it so that an actual exit doesn't occur.
179-
FlutterApplication* flutterApp = [FlutterApplication sharedApplication];
180-
if (flutterApp && [flutterApp respondsToSelector:@selector(terminateApplication:)]) {
181-
[[FlutterApplication sharedApplication] terminateApplication:sender];
182-
} else if (flutterApp) {
183-
[flutterApp terminate:sender];
184-
}
177+
NSApplication* flutterApp = [NSApplication sharedApplication];
178+
[flutterApp terminate:sender];
185179
};
186180
FlutterAppDelegate* appDelegate =
187-
(FlutterAppDelegate*)[[FlutterApplication sharedApplication] delegate];
181+
(FlutterAppDelegate*)[[NSApplication sharedApplication] delegate];
188182
appDelegate.terminationHandler = self;
189183
return self;
190184
}
@@ -202,7 +196,7 @@ - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
202196
FlutterAppExitType exitType =
203197
[type isEqualTo:@"cancelable"] ? kFlutterAppExitTypeCancelable : kFlutterAppExitTypeRequired;
204198

205-
[self requestApplicationTermination:[FlutterApplication sharedApplication]
199+
[self requestApplicationTermination:[NSApplication sharedApplication]
206200
exitType:exitType
207201
result:result];
208202
}
@@ -212,6 +206,7 @@ - (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
212206
- (void)requestApplicationTermination:(id)sender
213207
exitType:(FlutterAppExitType)type
214208
result:(nullable FlutterResult)result {
209+
_shouldTerminate = YES;
215210
switch (type) {
216211
case kFlutterAppExitTypeCancelable: {
217212
FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
@@ -238,6 +233,8 @@ - (void)requestApplicationTermination:(id)sender
238233
NSDictionary* replyArgs = (NSDictionary*)decoded_reply;
239234
if ([replyArgs[@"response"] isEqual:@"exit"]) {
240235
_terminator(sender);
236+
} else if ([replyArgs[@"response"] isEqual:@"cancel"]) {
237+
_shouldTerminate = NO;
241238
}
242239
if (result != nil) {
243240
result(replyArgs);

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include "flutter/shell/platform/common/accessibility_bridge.h"
1515
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
1616
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
17-
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
1817
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
1918
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
2019
#include "flutter/shell/platform/embedder/embedder.h"

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
#include <memory>
1010

11-
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
1211
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
1312
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
1413
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
@@ -52,11 +51,14 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
5251
* messages through the platform channel managed by the engine.
5352
*/
5453
@interface FlutterEngineTerminationHandler : NSObject
54+
55+
@property(nonatomic, readonly) BOOL shouldTerminate;
56+
5557
- (instancetype)initWithEngine:(FlutterEngine*)engine
5658
terminator:(nullable FlutterTerminationCallback)terminator;
5759
- (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)data
5860
result:(FlutterResult)result;
59-
- (void)requestApplicationTermination:(FlutterApplication*)sender
61+
- (void)requestApplicationTermination:(NSApplication*)sender
6062
exitType:(FlutterAppExitType)type
6163
result:(nullable FlutterResult)result;
6264
@end

0 commit comments

Comments
 (0)