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

Commit 55b6631

Browse files
authored
Add platform channel System.exitApplication and System.requestAppExit support (#39836)
Add platform channel `System.exitApplication` and `System.requestAppExit` support
1 parent 706fd9c commit 55b6631

15 files changed

Lines changed: 470 additions & 13 deletions

ci/licenses_golden/licenses_flutter

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2550,6 +2550,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm + ../../
25502550
ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h + ../../../flutter/LICENSE
25512551
ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm + ../../../flutter/LICENSE
25522552
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h + ../../../flutter/LICENSE
2553+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h + ../../../flutter/LICENSE
25532554
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h + ../../../flutter/LICENSE
25542555
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h + ../../../flutter/LICENSE
25552556
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h + ../../../flutter/LICENSE
@@ -2561,6 +2562,9 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil
25612562
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm + ../../../flutter/LICENSE
25622563
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm + ../../../flutter/LICENSE
25632564
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm + ../../../flutter/LICENSE
2565+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE
2566+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm + ../../../flutter/LICENSE
2567+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h + ../../../flutter/LICENSE
25642568
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h + ../../../flutter/LICENSE
25652569
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm + ../../../flutter/LICENSE
25662570
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h + ../../../flutter/LICENSE
@@ -5083,6 +5087,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm
50835087
FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h
50845088
FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm
50855089
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h
5090+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h
50865091
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h
50875092
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h
50885093
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h
@@ -5095,6 +5100,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit
50955100
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm
50965101
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm
50975102
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm
5103+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h
5104+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication.mm
5105+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h
50985106
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h
50995107
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm
51005108
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h

lib/ui/platform_dispatcher.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1690,6 +1690,43 @@ enum AppLifecycleState {
16901690
detached,
16911691
}
16921692

1693+
/// The possible responses to a request to exit the application.
1694+
///
1695+
/// The request is typically responded to by a [WidgetsBindingObserver].
1696+
// TODO(gspencergoog): Insert doc references here to AppLifecycleListener and to
1697+
// the actual function called on WidgetsBindingObserver once those have landed
1698+
// in the framework. https://github.com/flutter/flutter/issues/121721
1699+
enum AppExitResponse {
1700+
/// Exiting the application can proceed.
1701+
exit,
1702+
/// Cancel the exit: do not exit the application.
1703+
cancel,
1704+
}
1705+
1706+
/// The type of application exit to perform when calling
1707+
/// `ServicesBinding.exitApplication`.
1708+
// TODO(gspencergoog): Insert doc references here to
1709+
// ServicesBinding.exitApplication that has landed in the framework.
1710+
// https://github.com/flutter/flutter/issues/121721
1711+
enum AppExitType {
1712+
/// Requests that the application start an orderly exit, sending a request
1713+
/// back to the framework through the [WidgetsBinding]. If that responds
1714+
/// with [AppExitResponse.exit], then proceed with the same steps as a
1715+
/// [required] exit. If that responds with [AppExitResponse.cancel], then the
1716+
/// exit request is canceled and the application continues executing normally.
1717+
cancelable,
1718+
1719+
/// A non-cancelable orderly exit request. The engine will shut down the
1720+
/// engine and call the native UI toolkit's exit API.
1721+
///
1722+
/// If you need an even faster and more dangerous exit, then call `dart:io`'s
1723+
/// `exit()` directly, and even the native toolkit's exit API won't be called.
1724+
/// This is quite dangerous, though, since it's possible that the engine will
1725+
/// crash because it hasn't been properly shut down, causing the app to crash
1726+
/// on exit.
1727+
required,
1728+
}
1729+
16931730
/// A representation of distances for each of the four edges of a rectangle,
16941731
/// used to encode the view insets and padding that applications should place
16951732
/// around their user interface, as exposed by [FlutterView.viewInsets] and

lib/web_ui/lib/platform_dispatcher.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,16 @@ enum AppLifecycleState {
251251
detached,
252252
}
253253

254+
enum AppExitResponse {
255+
exit,
256+
cancel,
257+
}
258+
259+
enum AppExitType {
260+
cancelable,
261+
required,
262+
}
263+
254264
abstract class ViewPadding {
255265
const factory ViewPadding._(
256266
{required double left,

shell/platform/darwin/macos/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ _framework_binary_subpath = "Versions/A/$_flutter_framework_name"
3838
# the Flutter engine source root.
3939
_flutter_framework_headers = [
4040
"framework/Headers/FlutterAppDelegate.h",
41+
"framework/Headers/FlutterApplication.h",
4142
"framework/Headers/FlutterDartProject.h",
4243
"framework/Headers/FlutterEngine.h",
4344
"framework/Headers/FlutterMacOS.h",
@@ -57,6 +58,7 @@ source_set("flutter_framework_source") {
5758
"framework/Source/AccessibilityBridgeMac.h",
5859
"framework/Source/AccessibilityBridgeMac.mm",
5960
"framework/Source/FlutterAppDelegate.mm",
61+
"framework/Source/FlutterApplication.mm",
6062
"framework/Source/FlutterBackingStore.h",
6163
"framework/Source/FlutterBackingStore.mm",
6264
"framework/Source/FlutterChannelKeyResponder.h",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERAPPLICATION_H_
6+
#define FLUTTER_FLUTTERAPPLICATION_H_
7+
8+
#import <Cocoa/Cocoa.h>
9+
10+
/**
11+
* A Flutter-specific subclass of NSApplication that overrides |terminate| and
12+
* provides an additional |terminateApplication| method so that Flutter can
13+
* handle requests for termination in an asynchronous fashion.
14+
*
15+
* When a call to |terminate| comes in, either from the OS through a Quit menu
16+
* item, through the Quit item in the dock context menu, or from the application
17+
* itself, a request is sent to the Flutter framework. If that request is
18+
* granted, this subclass will (in |terminateApplication|) call
19+
* |NSApplication|'s version of |terminate| to proceed with terminating the
20+
* application normally by calling |applicationShouldTerminate|, etc.
21+
*
22+
* If the termination request is denied by the framework, then the application
23+
* will continue to execute normally, as if no |terminate| call were made.
24+
*
25+
* The |FlutterAppDelegate| always returns |NSTerminateNow| from
26+
* |applicationShouldTerminate|, since it has already decided by that point that
27+
* it should terminate.
28+
*
29+
* In order for this class to be used in place of |NSApplication|, the
30+
* "NSPrincipalClass" entry in the Info.plist for the application must be set to
31+
* "FlutterApplication". If it is not, then the application will not be given
32+
* the chance to deny a termination request, and calls to requestAppExit on the
33+
* engine (from the framework, typically) will simply exit the application
34+
* without ceremony.
35+
*
36+
* If the |NSApp| global isn't of type |FlutterApplication|, a log message will
37+
* be printed once in debug mode when the application is first accessed through
38+
* the singleton's |sharedApplication|, describing how to fix this.
39+
*
40+
* Flutter applications are *not* required to inherit from this class.
41+
* Developers of custom |NSApplication| subclasses should copy and paste code as
42+
* necessary from FlutterApplication.mm.
43+
*/
44+
@interface FlutterApplication : NSApplication
45+
@end
46+
47+
#endif // FLUTTER_FLUTTERAPPLICATION_H_

shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h

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

55
#import "FlutterAppDelegate.h"
6+
#import "FlutterApplication.h"
67
#import "FlutterBinaryMessenger.h"
78
#import "FlutterChannels.h"
89
#import "FlutterCodecs.h"

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
// found in the LICENSE file.
44

55
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
6+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
7+
8+
#import <AppKit/AppKit.h>
9+
10+
#include "flutter/fml/logging.h"
11+
#include "flutter/shell/platform/embedder/embedder.h"
612

713
@interface FlutterAppDelegate ()
814

@@ -15,8 +21,16 @@ - (NSString*)applicationName;
1521

1622
@implementation FlutterAppDelegate
1723

18-
// TODO(stuartmorgan): Implement application lifecycle forwarding to plugins here, as is done
24+
// TODO(gspencergoog): Implement application lifecycle forwarding to plugins here, as is done
1925
// on iOS. Currently macOS plugins don't have access to lifecycle messages.
26+
// https://github.com/flutter/flutter/issues/30735
27+
28+
- (instancetype)init {
29+
if (self = [super init]) {
30+
_terminationHandler = nil;
31+
}
32+
return self;
33+
}
2034

2135
- (void)applicationWillFinishLaunching:(NSNotification*)notification {
2236
// Update UI elements to match the application name.
@@ -28,6 +42,13 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification {
2842
}
2943
}
3044

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+
3152
#pragma mark Private Methods
3253

3354
- (NSString*)applicationName {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
6+
#define FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
7+
8+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
9+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
10+
11+
@interface FlutterAppDelegate ()
12+
13+
/**
14+
* Holds a weak reference to the termination handler owned by the engine.
15+
* Called by the |FlutterApplication| when termination is requested by the OS.
16+
*/
17+
@property(readwrite, nullable, weak) FlutterEngineTerminationHandler* terminationHandler;
18+
19+
@end
20+
21+
#endif // FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
6+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterApplication_Internal.h"
7+
8+
#include "flutter/shell/platform/embedder/embedder.h"
9+
#import "shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
10+
#import "shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
11+
#import "shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
12+
13+
// An NSApplication subclass that implements overrides necessary for some
14+
// Flutter features, like application lifecycle handling.
15+
@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+
}
90+
@end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_
6+
#define FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_
7+
8+
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
9+
10+
/**
11+
* Define |terminateApplication| for internal use.
12+
*/
13+
@interface FlutterApplication ()
14+
15+
/**
16+
* FlutterApplication's implementation of |terminate| doesn't terminate the
17+
* application: that is left up to the engine, which will call this function if
18+
* it decides that termination request is granted, which will start the regular
19+
* Cocoa flow for terminating the application, calling
20+
* |applicationShouldTerminate|, etc.
21+
*
22+
* @param(sender) The id of the object requesting the termination, or nil.
23+
*/
24+
- (void)terminateApplication:(id)sender;
25+
@end
26+
27+
#endif // FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_

0 commit comments

Comments
 (0)