mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add platform channel System.exitApplication and System.requestAppExit support (flutter/engine#39836)
Add platform channel `System.exitApplication` and `System.requestAppExit` support
This commit is contained in:
parent
bf56d3e712
commit
ddfbc85928
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -251,6 +251,16 @@ enum AppLifecycleState {
|
||||
detached,
|
||||
}
|
||||
|
||||
enum AppExitResponse {
|
||||
exit,
|
||||
cancel,
|
||||
}
|
||||
|
||||
enum AppExitType {
|
||||
cancelable,
|
||||
required,
|
||||
}
|
||||
|
||||
abstract class ViewPadding {
|
||||
const factory ViewPadding._(
|
||||
{required double left,
|
||||
|
||||
@ -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",
|
||||
@ -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",
|
||||
|
||||
@ -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_
|
||||
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#import "FlutterAppDelegate.h"
|
||||
#import "FlutterApplication.h"
|
||||
#import "FlutterBinaryMessenger.h"
|
||||
#import "FlutterChannels.h"
|
||||
#import "FlutterCodecs.h"
|
||||
|
||||
@ -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 ()
|
||||
|
||||
@ -15,8 +21,16 @@
|
||||
|
||||
@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.
|
||||
@ -28,6 +42,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@ -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_
|
||||
@ -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
|
||||
@ -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_
|
||||
@ -10,6 +10,12 @@
|
||||
#include <vector>
|
||||
|
||||
#include "flutter/shell/platform/common/engine_switches.h"
|
||||
#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"
|
||||
@ -18,10 +24,11 @@
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
|
||||
const uint64_t kFlutterDefaultViewId = 0;
|
||||
|
||||
NSString* const kFlutterPlatformChannel = @"flutter/platform";
|
||||
|
||||
/**
|
||||
* Constructs and returns a FlutterLocale struct corresponding to |locale|, which must outlive
|
||||
* the returned struct.
|
||||
@ -152,6 +159,84 @@ constexpr char kTextPlainFormat[] = "text/plain";
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation FlutterEngineTerminationHandler {
|
||||
FlutterEngine* _engine;
|
||||
FlutterTerminationCallback _terminator;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEngine:(FlutterEngine*)engine
|
||||
terminator:(FlutterTerminationCallback)terminator {
|
||||
self = [super init];
|
||||
_engine = 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];
|
||||
}
|
||||
};
|
||||
FlutterAppDelegate* appDelegate =
|
||||
(FlutterAppDelegate*)[[FlutterApplication sharedApplication] delegate];
|
||||
appDelegate.terminationHandler = self;
|
||||
return self;
|
||||
}
|
||||
|
||||
// This is called by the method call handler in the engine when the application
|
||||
// requests termination itself.
|
||||
- (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)arguments
|
||||
result:(FlutterResult)result {
|
||||
NSString* type = arguments[@"type"];
|
||||
// Ignore the "exitCode" value in the arguments because AppKit doesn't have
|
||||
// any good way to set the process exit code other than calling exit(), and
|
||||
// that bypasses all of the native applicationShouldExit shutdown events,
|
||||
// etc., which we don't want to skip.
|
||||
|
||||
FlutterAppExitType exitType =
|
||||
[type isEqualTo:@"cancelable"] ? kFlutterAppExitTypeCancelable : kFlutterAppExitTypeRequired;
|
||||
|
||||
[self requestApplicationTermination:[FlutterApplication sharedApplication]
|
||||
exitType:exitType
|
||||
result:result];
|
||||
}
|
||||
|
||||
// This is called by the FlutterAppDelegate whenever any termination request is
|
||||
// received.
|
||||
- (void)requestApplicationTermination:(id)sender
|
||||
exitType:(FlutterAppExitType)type
|
||||
result:(nullable FlutterResult)result {
|
||||
switch (type) {
|
||||
case kFlutterAppExitTypeCancelable: {
|
||||
FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
|
||||
FlutterMethodCall* methodCall =
|
||||
[FlutterMethodCall methodCallWithMethodName:@"System.requestAppExit" arguments:nil];
|
||||
[_engine sendOnChannel:kFlutterPlatformChannel
|
||||
message:[codec encodeMethodCall:methodCall]
|
||||
binaryReply:^(NSData* _Nullable reply) {
|
||||
NSDictionary* replyArgs = [codec decodeEnvelope:reply];
|
||||
if ([replyArgs[@"response"] isEqual:@"exit"]) {
|
||||
NSAssert(_terminator, @"terminator shouldn't be nil");
|
||||
_terminator(sender);
|
||||
}
|
||||
if (result != nil) {
|
||||
result(replyArgs);
|
||||
}
|
||||
}];
|
||||
break;
|
||||
}
|
||||
case kFlutterAppExitTypeRequired:
|
||||
NSAssert(_terminator, @"terminator shouldn't be nil");
|
||||
_terminator(sender);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
/**
|
||||
* `FlutterPluginRegistrar` implementation handling a single plugin.
|
||||
*/
|
||||
@ -290,6 +375,8 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
_semanticsEnabled = NO;
|
||||
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
|
||||
[_isResponseValid addObject:@YES];
|
||||
_terminationHandler = [[FlutterEngineTerminationHandler alloc] initWithEngine:self
|
||||
terminator:nil];
|
||||
// kFlutterDefaultViewId is reserved for the default view.
|
||||
// All IDs above it are for regular views.
|
||||
_nextViewId = kFlutterDefaultViewId + 1;
|
||||
@ -881,6 +968,8 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
result(nil);
|
||||
} else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
|
||||
result(@{@"value" : @([self clipboardHasStrings])});
|
||||
} else if ([call.method isEqualToString:@"System.exitApplication"]) {
|
||||
[[self terminationHandler] handleRequestAppExitMethodCall:call.arguments result:result];
|
||||
} else {
|
||||
result(FlutterMethodNotImplemented);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
@ -12,6 +13,7 @@
|
||||
#include "flutter/lib/ui/window/platform_message.h"
|
||||
#include "flutter/shell/platform/common/accessibility_bridge.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"
|
||||
@ -407,7 +409,7 @@ TEST_F(FlutterEngineTest, NativeCallbacks) {
|
||||
ASSERT_TRUE(latch_called);
|
||||
}
|
||||
|
||||
TEST(FlutterEngine, Compositor) {
|
||||
TEST_F(FlutterEngineTest, Compositor) {
|
||||
NSString* fixtures = @(flutter::testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
@ -446,7 +448,7 @@ TEST(FlutterEngine, Compositor) {
|
||||
[engine shutDownEngine];
|
||||
} // namespace flutter::testing
|
||||
|
||||
TEST(FlutterEngine, DartEntrypointArguments) {
|
||||
TEST_F(FlutterEngineTest, DartEntrypointArguments) {
|
||||
NSString* fixtures = @(flutter::testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
@ -541,7 +543,7 @@ TEST_F(FlutterEngineTest, MessengerCleanupConnectionWorks) {
|
||||
EXPECT_EQ(record, 21);
|
||||
}
|
||||
|
||||
TEST(FlutterEngine, HasStringsWhenPasteboardEmpty) {
|
||||
TEST_F(FlutterEngineTest, HasStringsWhenPasteboardEmpty) {
|
||||
id engineMock = CreateMockFlutterEngine(nil);
|
||||
|
||||
// Call hasStrings and expect it to be false.
|
||||
@ -559,7 +561,7 @@ TEST(FlutterEngine, HasStringsWhenPasteboardEmpty) {
|
||||
EXPECT_FALSE(valueAfterClear);
|
||||
}
|
||||
|
||||
TEST(FlutterEngine, HasStringsWhenPasteboardFull) {
|
||||
TEST_F(FlutterEngineTest, HasStringsWhenPasteboardFull) {
|
||||
id engineMock = CreateMockFlutterEngine(@"some string");
|
||||
|
||||
// Call hasStrings and expect it to be true.
|
||||
@ -619,7 +621,7 @@ TEST_F(FlutterEngineTest, ResponseFromBackgroundThread) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
|
||||
TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
|
||||
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
|
||||
[threadSynchronizer shutdown];
|
||||
|
||||
@ -695,6 +697,55 @@ TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByEngine) {
|
||||
EXPECT_EQ(viewController1.id, 0ull);
|
||||
}
|
||||
|
||||
TEST_F(FlutterEngineTest, HandlesTerminationRequest) {
|
||||
id engineMock = CreateMockFlutterEngine(nil);
|
||||
__block NSString* nextResponse = @"exit";
|
||||
__block BOOL triedToTerminate = FALSE;
|
||||
FlutterEngineTerminationHandler* terminationHandler =
|
||||
[[FlutterEngineTerminationHandler alloc] initWithEngine:engineMock
|
||||
terminator:^(id sender) {
|
||||
triedToTerminate = TRUE;
|
||||
// Don't actually terminate, of course.
|
||||
}];
|
||||
OCMStub([engineMock terminationHandler]).andReturn(terminationHandler);
|
||||
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
|
||||
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
|
||||
[engineMock binaryMessenger])
|
||||
.andReturn(binaryMessengerMock);
|
||||
OCMStub([engineMock sendOnChannel:@"flutter/platform"
|
||||
message:[OCMArg any]
|
||||
binaryReply:[OCMArg any]])
|
||||
.andDo((^(NSInvocation* invocation) {
|
||||
[invocation retainArguments];
|
||||
FlutterBinaryReply callback;
|
||||
[invocation getArgument:&callback atIndex:4];
|
||||
NSDictionary* responseDict = @{@"response" : nextResponse};
|
||||
NSData* returnedMessage =
|
||||
[[FlutterJSONMethodCodec sharedInstance] encodeSuccessEnvelope:responseDict];
|
||||
callback(returnedMessage);
|
||||
}));
|
||||
__block NSString* calledAfterTerminate = @"";
|
||||
FlutterResult appExitResult = ^(id result) {
|
||||
NSDictionary* resultDict = result;
|
||||
calledAfterTerminate = resultDict[@"response"];
|
||||
};
|
||||
FlutterMethodCall* methodExitApplication =
|
||||
[FlutterMethodCall methodCallWithMethodName:@"System.exitApplication"
|
||||
arguments:@{@"type" : @"cancelable"}];
|
||||
|
||||
triedToTerminate = FALSE;
|
||||
nextResponse = @"exit";
|
||||
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
|
||||
EXPECT_STREQ([calledAfterTerminate UTF8String], "exit");
|
||||
EXPECT_TRUE(triedToTerminate);
|
||||
|
||||
triedToTerminate = FALSE;
|
||||
nextResponse = @"cancel";
|
||||
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
|
||||
EXPECT_STREQ([calledAfterTerminate UTF8String], "cancel");
|
||||
EXPECT_FALSE(triedToTerminate);
|
||||
}
|
||||
|
||||
} // namespace flutter::testing
|
||||
|
||||
// NOLINTEND(clang-analyzer-core.StackAddressEscape)
|
||||
|
||||
@ -8,11 +8,59 @@
|
||||
|
||||
#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"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#pragma mark - Typedefs
|
||||
|
||||
typedef void (^FlutterTerminationCallback)(id _Nullable sender);
|
||||
|
||||
#pragma mark - Enumerations
|
||||
|
||||
/**
|
||||
* An enum for defining the different request types allowed when requesting an
|
||||
* application exit.
|
||||
*
|
||||
* Must match the entries in the `AppExitType` enum in the Dart code.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, FlutterAppExitType) {
|
||||
kFlutterAppExitTypeCancelable = 0,
|
||||
kFlutterAppExitTypeRequired = 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* An enum for defining the different responses the framework can give to an
|
||||
* application exit request from the engine.
|
||||
*
|
||||
* Must match the entries in the `AppExitResponse` enum in the Dart code.
|
||||
*/
|
||||
typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
|
||||
kFlutterAppExitResponseCancel = 0,
|
||||
kFlutterAppExitResponseExit = 1,
|
||||
};
|
||||
|
||||
#pragma mark - FlutterEngineTerminationHandler
|
||||
|
||||
/**
|
||||
* A handler interface for handling application termination that the
|
||||
* FlutterAppDelegate can use to coordinate an application exit by sending
|
||||
* messages through the platform channel managed by the engine.
|
||||
*/
|
||||
@interface FlutterEngineTerminationHandler : NSObject
|
||||
- (instancetype)initWithEngine:(FlutterEngine*)engine
|
||||
terminator:(nullable FlutterTerminationCallback)terminator;
|
||||
- (void)handleRequestAppExitMethodCall:(NSDictionary<NSString*, id>*)data
|
||||
result:(FlutterResult)result;
|
||||
- (void)requestApplicationTermination:(FlutterApplication*)sender
|
||||
exitType:(FlutterAppExitType)type
|
||||
result:(nullable FlutterResult)result;
|
||||
@end
|
||||
|
||||
@interface FlutterEngine ()
|
||||
|
||||
/**
|
||||
@ -52,6 +100,11 @@
|
||||
*/
|
||||
@property(nonatomic, readonly) std::vector<std::string> switches;
|
||||
|
||||
/**
|
||||
* Provides the |FlutterEngineTerminationHandler| to be used for this engine.
|
||||
*/
|
||||
@property(nonatomic, readonly) FlutterEngineTerminationHandler* terminationHandler;
|
||||
|
||||
/**
|
||||
* Attach a view controller to the engine as its default controller.
|
||||
*
|
||||
@ -64,7 +117,7 @@
|
||||
* If the given view controller is already attached to an engine, this call
|
||||
* throws an assertion.
|
||||
*/
|
||||
- (void)addViewController:(nonnull FlutterViewController*)viewController;
|
||||
- (void)addViewController:(FlutterViewController*)viewController;
|
||||
|
||||
/**
|
||||
* Dissociate the given view controller from this engine.
|
||||
@ -75,17 +128,17 @@
|
||||
* If the view controller is not associated with this engine, this call throws an
|
||||
* assertion.
|
||||
*/
|
||||
- (void)removeViewController:(nonnull FlutterViewController*)viewController;
|
||||
- (void)removeViewController:(FlutterViewController*)viewController;
|
||||
|
||||
/**
|
||||
* The `FlutterViewController` associated with the given view ID, if any.
|
||||
* The |FlutterViewController| associated with the given view ID, if any.
|
||||
*/
|
||||
- (nullable FlutterViewController*)viewControllerForId:(uint64_t)viewId;
|
||||
|
||||
/**
|
||||
* Informs the engine that the specified view controller's window metrics have changed.
|
||||
*/
|
||||
- (void)updateWindowMetricsForViewController:(nonnull FlutterViewController*)viewController;
|
||||
- (void)updateWindowMetricsForViewController:(FlutterViewController*)viewController;
|
||||
|
||||
/**
|
||||
* Dispatches the given pointer event data to engine.
|
||||
@ -127,3 +180,5 @@
|
||||
withData:(fml::MallocMapping)data;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#include "flutter/shell/platform/common/text_editing_delta.h"
|
||||
#include "flutter/shell/platform/common/text_input_model.h"
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
|
||||
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user