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:
Greg Spencer 2023-03-03 11:13:00 -08:00 committed by GitHub
parent bf56d3e712
commit ddfbc85928
15 changed files with 470 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -251,6 +251,16 @@ enum AppLifecycleState {
detached,
}
enum AppExitResponse {
exit,
cancel,
}
enum AppExitType {
cancelable,
required,
}
abstract class ViewPadding {
const factory ViewPadding._(
{required double left,

View File

@ -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",

View File

@ -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_

View File

@ -3,6 +3,7 @@
// found in the LICENSE file.
#import "FlutterAppDelegate.h"
#import "FlutterApplication.h"
#import "FlutterBinaryMessenger.h"
#import "FlutterChannels.h"
#import "FlutterCodecs.h"

View File

@ -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 {

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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);
}

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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"