Skip to content

Commit

Permalink
Add platform channel System.exitApplication and `System.requestAppE…
Browse files Browse the repository at this point in the history
…xit` support (#39836)

Add platform channel `System.exitApplication` and  `System.requestAppExit` support
  • Loading branch information
gspencergoog authored Mar 3, 2023
1 parent 706fd9c commit 55b6631
Show file tree
Hide file tree
Showing 15 changed files with 470 additions and 13 deletions.
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 @@ -1690,6 +1690,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
/// 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,
}

/// 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 @@ -251,6 +251,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
@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.
// 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

0 comments on commit 55b6631

Please sign in to comment.