diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5ea50c1f38d6e..0a1366d51e455 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -2744,9 +2744,11 @@ 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/FlutterAppDelegateTest.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_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 @@ -5488,9 +5490,11 @@ 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/FlutterAppDelegateTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegateTest.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_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 diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index ff209817409e1..4bef2c68b1942 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -169,6 +169,7 @@ executable("flutter_desktop_darwin_unittests") { sources = [ "framework/Source/AccessibilityBridgeMacTest.mm", + "framework/Source/FlutterAppDelegateTest.mm", "framework/Source/FlutterAppLifecycleDelegateTest.mm", "framework/Source/FlutterChannelKeyResponderTest.mm", "framework/Source/FlutterCompositorTest.mm", diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h b/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h index 5130b487e25ae..f4d17817519b0 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterAppDelegate.h @@ -56,13 +56,13 @@ FLUTTER_DARWIN_EXPORT /** * The application menu in the menu bar. */ -@property(weak, nonatomic) IBOutlet NSMenu* applicationMenu; +@property(weak, nonatomic, nullable) IBOutlet NSMenu* applicationMenu; /** * The primary application window containing a FlutterViewController. This is * primarily intended for use in single-window applications. */ -@property(weak, nonatomic) IBOutlet NSWindow* mainFlutterWindow; +@property(weak, nonatomic, nullable) IBOutlet NSWindow* mainFlutterWindow; @end diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h b/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h index 50ad8d3c5f001..9f704c2cf0867 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h @@ -91,7 +91,17 @@ FLUTTER_DARWIN_EXPORT * Called when the |FlutterAppDelegate| gets the applicationDidUnhide * notification. */ -- (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABLE(macos(10.9)); +- (void)handleDidChangeOcclusionState:(NSNotification*)notification; + +/** + * Called when the |FlutterAppDelegate| gets the application:openURLs: + * callback. + * + * Implementers should return YES if they handle the URLs, otherwise NO. + * Delegates will be called in order of registration, and once a delegate + * returns YES, no further delegates will reiceve this callback. + */ +- (BOOL)handleOpenURLs:(NSArray*)urls; /** * Called when the |FlutterAppDelegate| gets the applicationWillTerminate diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm index 782266419edba..02ab46b29829c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegate.mm @@ -9,6 +9,7 @@ #include "flutter/fml/logging.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" #include "flutter/shell/platform/embedder/embedder.h" @interface FlutterAppDelegate () @@ -44,11 +45,11 @@ - (void)applicationWillFinishLaunching:(NSNotification*)notification { #pragma mark - Delegate handling - (void)addApplicationLifecycleDelegate:(NSObject*)delegate { - [[self lifecycleRegistrar] addDelegate:delegate]; + [self.lifecycleRegistrar addDelegate:delegate]; } - (void)removeApplicationLifecycleDelegate:(NSObject*)delegate { - [[self lifecycleRegistrar] removeDelegate:delegate]; + [self.lifecycleRegistrar removeDelegate:delegate]; } #pragma mark Private Methods @@ -62,6 +63,17 @@ - (NSString*)applicationName { return applicationName; } +#pragma mark NSApplicationDelegate + +- (void)application:(NSApplication*)application openURLs:(NSArray*)urls { + for (NSObject* delegate in self.lifecycleRegistrar.delegates) { + if ([delegate respondsToSelector:@selector(handleOpenURLs:)] && + [delegate handleOpenURLs:urls]) { + return; + } + } +} + - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication* _Nonnull)sender { // If the framework has already told us to terminate, terminate immediately. if ([self terminationHandler] == nil || [[self terminationHandler] shouldTerminate]) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppDelegateTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegateTest.mm new file mode 100644 index 0000000000000..b58e5e78f94f1 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppDelegateTest.mm @@ -0,0 +1,71 @@ +// 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/FlutterAppDelegate.h" + +#import "flutter/testing/testing.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +@interface AppDelegateNoopFlutterAppLifecycleDelegate : NSObject +@property(nonatomic, copy, nullable) NSArray* receivedURLs; +@end + +@implementation AppDelegateNoopFlutterAppLifecycleDelegate +@end + +@interface AppDelegateTestFlutterAppLifecycleDelegate : NSObject +@property(nonatomic, copy, nullable) NSArray* receivedURLs; +@end + +@implementation AppDelegateTestFlutterAppLifecycleDelegate + +- (BOOL)handleOpenURLs:(NSArray*)urls { + self.receivedURLs = [urls copy]; + return YES; +} + +@end + +namespace flutter::testing { + +TEST(FlutterAppDelegateTest, DoesNotCallDelegatesWithoutHandler) { + FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; + AppDelegateNoopFlutterAppLifecycleDelegate* noopDelegate = + [[AppDelegateNoopFlutterAppLifecycleDelegate alloc] init]; + [appDelegate addApplicationLifecycleDelegate:noopDelegate]; + + [appDelegate application:NSApplication.sharedApplication openURLs:@[]]; + // No EXPECT, since the test is that the call doesn't throw due to calling without checking that + // the method is implemented. +} + +TEST(FlutterAppDelegateTest, ReceivesOpenURLs) { + FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; + AppDelegateTestFlutterAppLifecycleDelegate* delegate = + [[AppDelegateTestFlutterAppLifecycleDelegate alloc] init]; + [appDelegate addApplicationLifecycleDelegate:delegate]; + + NSArray* URLs = @[ [NSURL URLWithString:@"https://flutter.dev"] ]; + [appDelegate application:NSApplication.sharedApplication openURLs:URLs]; + + EXPECT_EQ([delegate receivedURLs], URLs); +} + +TEST(FlutterAppDelegateTest, OperURLsStopsAfterHandled) { + FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; + AppDelegateTestFlutterAppLifecycleDelegate* firstDelegate = + [[AppDelegateTestFlutterAppLifecycleDelegate alloc] init]; + AppDelegateTestFlutterAppLifecycleDelegate* secondDelegate = + [[AppDelegateTestFlutterAppLifecycleDelegate alloc] init]; + [appDelegate addApplicationLifecycleDelegate:firstDelegate]; + [appDelegate addApplicationLifecycleDelegate:secondDelegate]; + + NSArray* URLs = @[ [NSURL URLWithString:@"https://flutter.dev"] ]; + [appDelegate application:NSApplication.sharedApplication openURLs:URLs]; + + EXPECT_EQ([firstDelegate receivedURLs], URLs); + EXPECT_EQ([secondDelegate receivedURLs], nil); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm index 4b552b94ca1a0..ba53217544e22 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate.mm @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h" #include #include @@ -12,14 +13,8 @@ #include "flutter/fml/logging.h" #include "flutter/fml/paths.h" -@interface FlutterAppLifecycleRegistrar () -@end - @implementation FlutterAppLifecycleRegistrar { NSMutableArray* _notificationUnsubscribers; - - // Weak references to registered plugins. - NSPointerArray* _delegates; } - (void)addObserverFor:(NSString*)name selector:(SEL)selector { @@ -87,7 +82,7 @@ - (void)addDelegate:(NSObject*)delegate { - (void)removeDelegate:(NSObject*)delegate { NSUInteger index = [[_delegates allObjects] indexOfObject:delegate]; - if (index >= 0) { + if (index != NSNotFound) { [_delegates removePointerAtIndex:index]; } } @@ -101,9 +96,6 @@ - (void)removeDelegate:(NSObject*)delegate { #define DISTRIBUTE_NOTIFICATION(SELECTOR) \ -(void)handle##SELECTOR : (NSNotification*)notification { \ for (NSObject * delegate in _delegates) { \ - if (!delegate) { \ - continue; \ - } \ if ([delegate respondsToSelector:@selector(handle##SELECTOR:)]) { \ [delegate handle##SELECTOR:notification]; \ } \ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h new file mode 100644 index 0000000000000..18b8e44422871 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterAppLifecycleDelegate_Internal.h @@ -0,0 +1,18 @@ +// 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 SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ +#define SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ + +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterAppLifecycleDelegate.h" + +@interface FlutterAppLifecycleRegistrar () +/** + * Registered delegates. Exposed to allow FlutterAppDelegate to share the delegate list for + * handling non-notification delegation. + */ +@property(nonatomic, strong) NSPointerArray* delegates; +@end + +#endif // SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERAPPLIFECYCLEDELEGATE_INTERNAL_H_ diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index a48ed892d4827..7d4a6e6e0670c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -1232,7 +1232,7 @@ - (void)handleWillResignActive:(NSNotification*)notification { * Called when the |FlutterAppDelegate| gets the applicationDidUnhide * notification. */ -- (void)handleDidChangeOcclusionState:(NSNotification*)notification API_AVAILABLE(macos(10.9)) { +- (void)handleDidChangeOcclusionState:(NSNotification*)notification { NSApplicationOcclusionState occlusionState = [[NSApplication sharedApplication] occlusionState]; if (occlusionState & NSApplicationOcclusionStateVisible) { _visible = YES;