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

Add platform channel System.exitApplication and System.requestAppExit support #39836

Merged
merged 15 commits into from
Mar 3, 2023
Merged
8 changes: 8 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2550,6 +2550,7 @@ ORIGIN: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm + ../../
ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h + ../../../flutter/LICENSE
Expand All @@ -2561,6 +2562,9 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibil
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm + ../../../flutter/LICENSE
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 @@ -5083,6 +5087,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.mm
FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.h
FILE: ../../../flutter/shell/platform/darwin/ios/rendering_api_selection.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Headers/FlutterMacOS.h
Expand All @@ -5095,6 +5100,9 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/Accessibilit
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMacTest.mm
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
37 changes: 37 additions & 0 deletions lib/ui/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,43 @@ enum AppLifecycleState {
detached,
}

/// The possible responses to a request to exit the application.
///
/// The request is typically responded to by a [WidgetsBindingObserver].
// TODO(gspencergoog): Insert doc references here to AppLifecycleListener and to
// the actual function called on WidgetsBindingObserver once those have landed
// in the framework. https://github.com/flutter/flutter/issues/121721
enum AppExitResponse {
/// Exiting the application can proceed.
exit,
/// Cancel the exit: do not exit the application.
cancel,
}

/// The type of application exit to perform when calling
/// `ServicesBinding.exitApplication`.
// TODO(gspencergoog): Insert doc references here to
// ServicesBinding.exitApplication that has landed in the framework.
// https://github.com/flutter/flutter/issues/121721
enum AppExitType {
/// Requests that the application start an orderly exit, sending a request
gspencergoog marked this conversation as resolved.
Show resolved Hide resolved
/// back to the framework through the [WidgetsBinding]. If that responds
/// with [AppExitResponse.exit], then proceed with the same steps as a
/// [required] exit. If that responds with [AppExitResponse.cancel], then the
/// exit request is canceled and the application continues executing normally.
cancelable,

/// A non-cancelable orderly exit request. The engine will shut down the
/// engine and call the native UI toolkit's exit API.
///
/// If you need an even faster and more dangerous exit, then call `dart:io`'s
/// `exit()` directly, and even the native toolkit's exit API won't be called.
/// This is quite dangerous, though, since it's possible that the engine will
/// crash because it hasn't been properly shut down, causing the app to crash
/// on exit.
required,
cbracken marked this conversation as resolved.
Show resolved Hide resolved
}

/// A representation of distances for each of the four edges of a rectangle,
/// used to encode the view insets and padding that applications should place
/// around their user interface, as exposed by [FlutterView.viewInsets] and
Expand Down
10 changes: 10 additions & 0 deletions lib/web_ui/lib/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,16 @@ enum AppLifecycleState {
detached,
}

enum AppExitResponse {
exit,
cancel,
}

enum AppExitType {
cancelable,
required,
}

abstract class ViewPadding {
const factory ViewPadding._(
{required double left,
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/darwin/macos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ _framework_binary_subpath = "Versions/A/$_flutter_framework_name"
# the Flutter engine source root.
_flutter_framework_headers = [
"framework/Headers/FlutterAppDelegate.h",
"framework/Headers/FlutterApplication.h",
"framework/Headers/FlutterDartProject.h",
"framework/Headers/FlutterEngine.h",
"framework/Headers/FlutterMacOS.h",
Expand All @@ -57,6 +58,7 @@ source_set("flutter_framework_source") {
"framework/Source/AccessibilityBridgeMac.h",
"framework/Source/AccessibilityBridgeMac.mm",
"framework/Source/FlutterAppDelegate.mm",
"framework/Source/FlutterApplication.mm",
"framework/Source/FlutterBackingStore.h",
"framework/Source/FlutterBackingStore.mm",
"framework/Source/FlutterChannelKeyResponder.h",
Expand Down
47 changes: 47 additions & 0 deletions shell/platform/darwin/macos/framework/Headers/FlutterApplication.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FLUTTERAPPLICATION_H_
#define FLUTTER_FLUTTERAPPLICATION_H_

#import <Cocoa/Cocoa.h>

/**
* A Flutter-specific subclass of NSApplication that overrides |terminate| and
* provides an additional |terminateApplication| method so that Flutter can
* handle requests for termination in an asynchronous fashion.
*
* When a call to |terminate| comes in, either from the OS through a Quit menu
* item, through the Quit item in the dock context menu, or from the application
* itself, a request is sent to the Flutter framework. If that request is
* granted, this subclass will (in |terminateApplication|) call
* |NSApplication|'s version of |terminate| to proceed with terminating the
* application normally by calling |applicationShouldTerminate|, etc.
*
* If the termination request is denied by the framework, then the application
* will continue to execute normally, as if no |terminate| call were made.
*
* The |FlutterAppDelegate| always returns |NSTerminateNow| from
* |applicationShouldTerminate|, since it has already decided by that point that
* it should terminate.
*
* In order for this class to be used in place of |NSApplication|, the
* "NSPrincipalClass" entry in the Info.plist for the application must be set to
* "FlutterApplication". If it is not, then the application will not be given
* the chance to deny a termination request, and calls to requestAppExit on the
* engine (from the framework, typically) will simply exit the application
* without ceremony.
*
* If the |NSApp| global isn't of type |FlutterApplication|, a log message will
* be printed once in debug mode when the application is first accessed through
* the singleton's |sharedApplication|, describing how to fix this.
*
* Flutter applications are *not* required to inherit from this class.
* Developers of custom |NSApplication| subclasses should copy and paste code as
* necessary from FlutterApplication.mm.
*/
@interface FlutterApplication : NSApplication
gspencergoog marked this conversation as resolved.
Show resolved Hide resolved
@end

#endif // FLUTTER_FLUTTERAPPLICATION_H_
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#import "FlutterAppDelegate.h"
#import "FlutterApplication.h"
#import "FlutterBinaryMessenger.h"
#import "FlutterChannels.h"
#import "FlutterCodecs.h"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
// found in the LICENSE file.

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

#import <AppKit/AppKit.h>

#include "flutter/fml/logging.h"
#include "flutter/shell/platform/embedder/embedder.h"

@interface FlutterAppDelegate ()

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

@implementation FlutterAppDelegate

// TODO(stuartmorgan): Implement application lifecycle forwarding to plugins here, as is done
// TODO(gspencergoog): Implement application lifecycle forwarding to plugins here, as is done
// on iOS. Currently macOS plugins don't have access to lifecycle messages.
gspencergoog marked this conversation as resolved.
Show resolved Hide resolved
// https://github.com/flutter/flutter/issues/30735

- (instancetype)init {
if (self = [super init]) {
_terminationHandler = nil;
}
return self;
}

- (void)applicationWillFinishLaunching:(NSNotification*)notification {
// Update UI elements to match the application name.
Expand All @@ -28,6 +42,13 @@ - (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 Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
#define FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_

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

@interface FlutterAppDelegate ()

/**
* Holds a weak reference to the termination handler owned by the engine.
* Called by the |FlutterApplication| when termination is requested by the OS.
*/
@property(readwrite, nullable, weak) FlutterEngineTerminationHandler* terminationHandler;

@end

#endif // FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_
90 changes: 90 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterApplication.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// 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"

#include "flutter/shell/platform/embedder/embedder.h"
#import "shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
#import "shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
#import "shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_
#define FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_

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

/**
* Define |terminateApplication| for internal use.
*/
@interface FlutterApplication ()

/**
* FlutterApplication's implementation of |terminate| doesn't terminate the
* application: that is left up to the engine, which will call this function if
* it decides that termination request is granted, which will start the regular
* Cocoa flow for terminating the application, calling
* |applicationShouldTerminate|, etc.
*
* @param(sender) The id of the object requesting the termination, or nil.
*/
- (void)terminateApplication:(id)sender;
@end

#endif // FLUTTER_FLUTTERAPPLICATION_INTERNAL_H_
Loading