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

Commit 8c612c3

Browse files
committed
Consolidate functionality, fix test
1 parent 868ecc2 commit 8c612c3

File tree

6 files changed

+161
-124
lines changed

6 files changed

+161
-124
lines changed

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

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
#import <AppKit/AppKit.h>
88

99
#include "flutter/fml/logging.h"
10-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h"
10+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
1111
#include "flutter/shell/platform/embedder/embedder.h"
1212

1313
@interface FlutterAppDelegate ()
@@ -19,9 +19,7 @@ - (NSString*)applicationName;
1919

2020
@end
2121

22-
@implementation FlutterAppDelegate {
23-
FlutterEngineTerminationHandler* _terminationHandler;
24-
}
22+
@implementation FlutterAppDelegate
2523

2624
- (instancetype)init {
2725
if (self = [super init]) {
@@ -58,22 +56,4 @@ - (NSString*)applicationName {
5856
return applicationName;
5957
}
6058

61-
- (void)setTerminationRequestHandler:(FlutterEngineTerminationHandler*)handler {
62-
_terminationHandler = handler;
63-
}
64-
65-
- (void)requestApplicationTermination:(NSApplication*)application
66-
exitType:(FlutterAppExitType)type {
67-
if (![application isKindOfClass:[FlutterApplication class]]) {
68-
[application terminate:application];
69-
return;
70-
};
71-
FlutterApplication* flutterApp = static_cast<FlutterApplication*>(application);
72-
if (_terminationHandler) {
73-
[_terminationHandler requestApplicationTermination:flutterApp exitType:type result:nil];
74-
} else {
75-
[flutterApp terminateApplication:application];
76-
}
77-
}
78-
7959
@end

shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@
1010
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
1111

1212
@interface FlutterAppDelegate ()
13-
/**
14-
* Allows the engine to add a handler for termination requests
15-
*/
16-
- (void)setTerminationRequestHandler:(FlutterEngineTerminationHandler*)handler;
1713

1814
/**
19-
* Allows the delegate to respond to an attempt to terminate the application.
15+
* Holds a weak reference to the termination handler owned by the engine.
16+
* Called by the |FlutterApplication| when termination is requested by the OS.
2017
*/
21-
- (void)requestApplicationTermination:(NSApplication*)application exitType:(FlutterAppExitType)type;
18+
@property(readwrite, weak) FlutterEngineTerminationHandler* terminationHandler;
19+
2220
@end
2321

2422
#endif // FLUTTER_FLUTTERAPPDELEGATE_INTERNAL_H_

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

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
#include "flutter/shell/platform/embedder/embedder.h"
88
#import "shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
9-
#import "shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h"
9+
#import "shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
10+
#import "shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
1011

1112
// An NSApplication subclass that implements overrides necessary for some
1213
// Flutter features, like application lifecycle handling.
@@ -21,18 +22,17 @@ + (NSApplication*)sharedApplication {
2122
// +sharedApplication initializes the global NSApp, so if we're delivering
2223
// something other than a FlutterApplication, warn the developer once.
2324
#ifndef FLUTTER_RELEASE
24-
static dispatch_once_t onceToken = 0;
25-
dispatch_once(&onceToken, ^{
26-
if (![NSApp isKindOfClass:[FlutterApplication class]]) {
27-
NSLog(@"NSApp should be of type %s, not %s. "
28-
"Some application lifecycle requests (e.g. ServicesBinding.exitApplication) "
29-
"and notifications will be unavailable.\n"
30-
"Modify the application's NSPrincipleClass to be %s"
31-
"in the Info.plist to fix this.",
32-
[[self className] UTF8String], [[NSApp className] UTF8String],
33-
[[self className] UTF8String]);
34-
}
35-
});
25+
static bool notified = false;
26+
if (!notified && ![NSApp isKindOfClass:[FlutterApplication class]]) {
27+
NSLog(@"NSApp should be of type %s, not %s. "
28+
"Some application lifecycle requests (e.g. ServicesBinding.exitApplication) "
29+
"and notifications will be unavailable.\n"
30+
"Modify the application's NSPrincipleClass to be %s"
31+
"in the Info.plist to fix this.",
32+
[[self className] UTF8String], [[NSApp className] UTF8String],
33+
[[self className] UTF8String]);
34+
notified = true;
35+
}
3636
#endif // !FLUTTER_RELEASE
3737
return app;
3838
}
@@ -62,9 +62,13 @@ + (NSApplication*)sharedApplication {
6262
// it if it is OK to terminate. When that method channel call returns with a
6363
// result, the application either terminates or continues running.
6464
- (void)terminate:(id)sender {
65-
FlutterAppDelegate* appDelegate = static_cast<FlutterAppDelegate*>([NSApp delegate]);
66-
67-
[appDelegate requestApplicationTermination:self exitType:kFlutterAppExitTypeCancelable];
65+
FlutterEngineTerminationHandler* terminationHandler = [static_cast<FlutterAppDelegate*>([NSApp delegate]) terminationHandler];
66+
if (terminationHandler) {
67+
[terminationHandler requestApplicationTermination:self exitType:kFlutterAppExitTypeCancelable result:nil];
68+
} else {
69+
// If there's no termination handler, then just terminate.
70+
[super terminate:sender];
71+
}
6872
// Return, don't exit. The application delegate is responsible for exiting on
6973
// its own by calling |-terminateApplication|.
7074
}

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

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

55
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
6+
#include "FlutterCodecs.h"
67
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
78

89
#include <algorithm>
@@ -14,7 +15,7 @@
1415

1516
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h"
1617
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterApplication.h"
17-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_internal.h"
18+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h"
1819
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
1920
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
2021
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
@@ -160,19 +161,28 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result;
160161

161162
@implementation FlutterEngineTerminationHandler {
162163
FlutterEngine* _engine;
164+
FlutterTerminationCallback _terminator;
163165
}
164166

165-
- (instancetype)initWithEngine:(FlutterEngine*)engine {
167+
- (instancetype)initWithEngine:(FlutterEngine*)engine
168+
terminator:(FlutterTerminationCallback)terminator {
166169
self = [super init];
167170
_engine = engine;
171+
_terminator = terminator ? terminator : ^(id sender) {
172+
// Default to actually terminating the application. The terminator exists to
173+
// allow tests to override it so that an actual exit doesn't occur.
174+
[[FlutterApplication sharedApplication] terminateApplication:sender];
175+
};
176+
FlutterAppDelegate* appDelegate =
177+
(FlutterAppDelegate*)[[FlutterApplication sharedApplication] delegate];
178+
appDelegate.terminationHandler = self;
168179
return self;
169180
}
170181

171182
// This is called by the method call handler in the engine when the application
172183
// requests termination itself.
173184
- (void)requestAppExit:(NSDictionary<NSString*, id>*)arguments result:(FlutterResult)result {
174185
NSString* type = arguments[@"type"];
175-
176186
// Ignore the "exitCode" value in the arguments because AppKit doesn't have
177187
// any good way to set the process exit code other than calling exit(), and
178188
// that bypasses all of the native applicationShouldExit shutdown events,
@@ -193,26 +203,26 @@ - (void)requestApplicationTermination:(id)sender
193203
result:(nullable FlutterResult)result {
194204
switch (type) {
195205
case kFlutterAppExitTypeCancelable: {
196-
[_engine
197-
sendOnChannel:kFlutterPlatformChannel
198-
message:[[FlutterJSONMethodCodec sharedInstance]
199-
encodeMethodCall:[FlutterMethodCall
200-
methodCallWithMethodName:@"System.requestAppExit"
201-
arguments:@{}]]
202-
binaryReply:^(NSData* _Nullable reply) {
203-
NSDictionary* replyArgs =
204-
[[FlutterJSONMethodCodec sharedInstance] decodeEnvelope:reply];
205-
if ([replyArgs[@"response"] isEqual:@"exit"]) {
206-
[[FlutterApplication sharedApplication] terminateApplication:sender];
207-
}
208-
if (result != nil) {
209-
result(replyArgs);
210-
}
211-
}];
206+
FlutterJSONMethodCodec* codec = [FlutterJSONMethodCodec sharedInstance];
207+
FlutterMethodCall* methodCall =
208+
[FlutterMethodCall methodCallWithMethodName:@"System.requestAppExit" arguments:nil];
209+
[_engine sendOnChannel:kFlutterPlatformChannel
210+
message:[codec encodeMethodCall:methodCall]
211+
binaryReply:^(NSData* _Nullable reply) {
212+
NSDictionary* replyArgs = [codec decodeEnvelope:reply];
213+
if ([replyArgs[@"response"] isEqual:@"exit"]) {
214+
NSAssert(_terminator, @"terminator shouldn't be nil");
215+
_terminator(sender);
216+
}
217+
if (result != nil) {
218+
result(replyArgs);
219+
}
220+
}];
212221
break;
213222
}
214223
case kFlutterAppExitTypeRequired:
215-
[[FlutterApplication sharedApplication] terminateApplication:sender];
224+
NSAssert(_terminator, @"terminator shouldn't be nil");
225+
_terminator(sender);
216226
break;
217227
}
218228
}
@@ -339,9 +349,6 @@ @implementation FlutterEngine {
339349
// A method channel for miscellaneous platform functionality.
340350
FlutterMethodChannel* _platformChannel;
341351

342-
// A delegate used to receive application termination events.
343-
FlutterEngineTerminationHandler* _terminateHelper;
344-
345352
int _nextViewId;
346353
}
347354

@@ -362,10 +369,8 @@ - (instancetype)initWithName:(NSString*)labelPrefix
362369
_semanticsEnabled = NO;
363370
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
364371
[_isResponseValid addObject:@YES];
365-
_terminateHelper = [[FlutterEngineTerminationHandler alloc] initWithEngine:self];
366-
FlutterAppDelegate* appDelegate =
367-
(FlutterAppDelegate*)[[FlutterApplication sharedApplication] delegate];
368-
[appDelegate setTerminationRequestHandler:_terminateHelper];
372+
_terminationHandler = [[FlutterEngineTerminationHandler alloc] initWithEngine:self
373+
terminator:nil];
369374
// kFlutterDefaultViewId is reserved for the default view.
370375
// All IDs above it are for regular views.
371376
_nextViewId = kFlutterDefaultViewId + 1;
@@ -958,7 +963,7 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
958963
} else if ([call.method isEqualToString:@"Clipboard.hasStrings"]) {
959964
result(@{@"value" : @([self clipboardHasStrings])});
960965
} else if ([call.method isEqualToString:@"System.exitApplication"]) {
961-
[_terminateHelper requestAppExit:call.arguments result:result];
966+
[[self terminationHandler] requestAppExit:call.arguments result:result];
962967
} else {
963968
result(FlutterMethodNotImplemented);
964969
}

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

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "OCMock/OCMArg.h"
6+
#include "OCMock/OCMock.h"
57
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
68
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
9+
#include "gtest/gtest.h"
710

811
#include <functional>
912
#include <thread>
@@ -407,7 +410,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
407410
ASSERT_TRUE(latch_called);
408411
}
409412

410-
TEST(FlutterEngineTest, Compositor) {
413+
TEST_F(FlutterEngineTest, Compositor) {
411414
NSString* fixtures = @(flutter::testing::GetFixturesPath());
412415
FlutterDartProject* project = [[FlutterDartProject alloc]
413416
initWithAssetsPath:fixtures
@@ -446,7 +449,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
446449
[engine shutDownEngine];
447450
} // namespace flutter::testing
448451

449-
TEST(FlutterEngineTest, DartEntrypointArguments) {
452+
TEST_F(FlutterEngineTest, DartEntrypointArguments) {
450453
NSString* fixtures = @(flutter::testing::GetFixturesPath());
451454
FlutterDartProject* project = [[FlutterDartProject alloc]
452455
initWithAssetsPath:fixtures
@@ -541,7 +544,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
541544
EXPECT_EQ(record, 21);
542545
}
543546

544-
TEST(FlutterEngineTest, HasStringsWhenPasteboardEmpty) {
547+
TEST_F(FlutterEngineTest, HasStringsWhenPasteboardEmpty) {
545548
id engineMock = CreateMockFlutterEngine(nil);
546549

547550
// Call hasStrings and expect it to be false.
@@ -559,7 +562,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
559562
EXPECT_FALSE(valueAfterClear);
560563
}
561564

562-
TEST(FlutterEngineTest, HasStringsWhenPasteboardFull) {
565+
TEST_F(FlutterEngineTest, HasStringsWhenPasteboardFull) {
563566
id engineMock = CreateMockFlutterEngine(@"some string");
564567

565568
// Call hasStrings and expect it to be true.
@@ -619,7 +622,7 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
619622
}
620623
}
621624

622-
TEST(EngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
625+
TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
623626
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
624627
[threadSynchronizer shutdown];
625628

@@ -695,17 +698,54 @@ - (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable
695698
EXPECT_EQ(viewController1.id, 0ull);
696699
}
697700

698-
TEST(FlutterEngineTerminationHandlerTest, HandlesTerminationRequest) {
701+
TEST_F(FlutterEngineTest, HandlesTerminationRequest) {
699702
id engineMock = CreateMockFlutterEngine(nil);
700-
701-
__block FlutterAppExitResponse calledAfterTerminate = kFlutterAppExitResponseCancel;
703+
__block NSString* nextResponse = @"exit";
704+
__block BOOL triedToTerminate = FALSE;
705+
FlutterEngineTerminationHandler* terminationHandler =
706+
[[FlutterEngineTerminationHandler alloc] initWithEngine:engineMock
707+
terminator:^(id sender) {
708+
triedToTerminate = TRUE;
709+
// Don't actually terminate, of course.
710+
}];
711+
OCMStub([engineMock terminationHandler]).andReturn(terminationHandler);
712+
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
713+
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
714+
[engineMock binaryMessenger])
715+
.andReturn(binaryMessengerMock);
716+
OCMStub([engineMock sendOnChannel:@"flutter/platform"
717+
message:[OCMArg any]
718+
binaryReply:[OCMArg any]])
719+
.andDo((^(NSInvocation* invocation) {
720+
[invocation retainArguments];
721+
FlutterBinaryReply callback;
722+
[invocation getArgument:&callback atIndex:4];
723+
NSDictionary* responseDict = @{@"response" : nextResponse};
724+
NSData* returnedMessage =
725+
[[FlutterJSONMethodCodec sharedInstance] encodeSuccessEnvelope:responseDict];
726+
callback(returnedMessage);
727+
}));
728+
__block NSString* calledAfterTerminate = @"";
702729
FlutterResult appExitResult = ^(id result) {
703-
NSLog(@"Response: %@", result);
730+
NSDictionary* resultDict = result;
731+
calledAfterTerminate = resultDict[@"response"];
704732
};
705-
FlutterMethodCall* methodCallAfterClear =
706-
[FlutterMethodCall methodCallWithMethodName:@"System.exitApplication" arguments:nil];
707-
[engineMock handleMethodCall:methodCallAfterClear result:appExitResult];
708-
EXPECT_EQ(kFlutterAppExitResponseCancel, calledAfterTerminate);
733+
FlutterMethodCall* methodExitApplication =
734+
[FlutterMethodCall methodCallWithMethodName:@"System.exitApplication"
735+
arguments:@{@"type" : @"cancelable"}];
736+
737+
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
738+
739+
triedToTerminate = FALSE;
740+
nextResponse = @"exit";
741+
EXPECT_STREQ([calledAfterTerminate UTF8String], "exit");
742+
EXPECT_TRUE(triedToTerminate);
743+
744+
triedToTerminate = FALSE;
745+
nextResponse = @"cancel";
746+
[engineMock handleMethodCall:methodExitApplication result:appExitResult];
747+
EXPECT_STREQ([calledAfterTerminate UTF8String], "cancel");
748+
EXPECT_FALSE(triedToTerminate);
709749
}
710750

711751
} // namespace flutter::testing

0 commit comments

Comments
 (0)