From fad38fdd31f72d68d54fbebfcd0fe842aa36fca8 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Thu, 1 Oct 2020 09:14:59 -0700 Subject: [PATCH 1/7] Started passing URLs as routes down to the Framework. --- .../framework/Source/FlutterAppDelegate.mm | 26 +++++++++++++++++-- .../ios/framework/Source/FlutterEngine.mm | 13 ++++++++++ .../framework/Source/FlutterEngine_Internal.h | 1 + 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index d1834c3a41d4a..5dd229f72971a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -4,9 +4,10 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" -#include "flutter/fml/logging.h" +#import "flutter/fml/logging.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" static NSString* kUIBackgroundMode = @"UIBackgroundModes"; @@ -124,7 +125,28 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary*)options { - return [_lifeCycleDelegate application:application openURL:url options:options]; + if ([_lifeCycleDelegate application:application openURL:url options:options]) { + return YES; + } else { + UIViewController* rootViewController = _window.rootViewController; + if ([rootViewController isKindOfClass:[FlutterViewController class]]) { + FlutterViewController* flutterViewController = (FlutterViewController*)rootViewController; + [flutterViewController.engine + waitForFirstFrame:3.0 + callback:^(BOOL didTimeout) { + if (didTimeout) { + FML_LOG(ERROR) + << "Timeout waiting for the first frame when launching an URL."; + } else { + [flutterViewController.engine.navigationChannel invokeMethod:@"pushRoute" + arguments:url.path]; + } + }]; + } else { + FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController."; + } + return YES; + } } - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index cbd93e978fd6a..f4f071d2fc4e8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -883,6 +883,19 @@ - (void)onLocaleUpdated:(NSNotification*)notification { [self.localizationChannel invokeMethod:@"setLocale" arguments:localeData]; } +- (void)waitForFirstFrame:(NSTimeInterval)timeout + callback:(void (^_Nonnull)(BOOL didTimeout))callback { + dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0); + dispatch_async(queue, ^{ + fml::TimeDelta waitTime = fml::TimeDelta::FromMilliseconds(timeout * 1000); + BOOL didTimeout = + self.shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded; + dispatch_async(dispatch_get_main_queue(), ^{ + callback(didTimeout); + }); + }); +} + @end @implementation FlutterEngineRegistrar { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 54a2c75472ac9..13fdfa0dab04e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -48,6 +48,7 @@ - (void)notifyLowMemory; - (flutter::PlatformViewIOS*)iosPlatformView; +- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERENGINE_INTERNAL_H_ From 1fa2fd2b785b6a06f40ece8e8065a0ed24216531 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Thu, 1 Oct 2020 16:51:25 -0700 Subject: [PATCH 2/7] made sure to return NO if we have no flutterviewcontroller --- .../platform/darwin/ios/framework/Source/FlutterAppDelegate.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index 5dd229f72971a..1a845a55af80e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -142,10 +142,11 @@ - (BOOL)application:(UIApplication*)application arguments:url.path]; } }]; + return YES; } else { FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController."; + return NO; } - return YES; } } From e4d598c1c854dd84221ab242197307910630924d Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 23 Oct 2020 15:00:36 -0700 Subject: [PATCH 3/7] added test for wait for first frame --- .../ios/framework/Source/FlutterEngineTest.mm | 13 +++++++++++++ .../ios/framework/Source/FlutterEngine_Test.h | 1 + 2 files changed, 14 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 656f6f55b3f13..cfbb8cbdb87b3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -100,4 +100,17 @@ - (void)testRunningInitialRouteSendsNavigationMessage { message:encodedSetInitialRouteMethod]); } +- (void)testWaitForFirstFrameTimeout { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"]; + [engine run]; + XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"]; + [engine waitForFirstFrame:0.1 + callback:^(BOOL didTimeout) { + if (timeoutFirstFrame) { + [timeoutFirstFrame fulfill]; + } + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index 7be2f68d77b50..dde370f72cbf4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -7,4 +7,5 @@ // Category to add test-only visibility. @interface FlutterEngine (Test) - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger; +- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback; @end From 51e56d6632a4a975d7a9c2b96374ece97cd055d2 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 23 Oct 2020 15:15:45 -0700 Subject: [PATCH 4/7] added test for launching urls --- .../framework/Source/FlutterAppDelegate.mm | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index 1a845a55af80e..f2b3eac1254dd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -42,10 +42,10 @@ - (BOOL)application:(UIApplication*)application // Returns the key window's rootViewController, if it's a FlutterViewController. // Otherwise, returns nil. -+ (FlutterViewController*)rootFlutterViewController { - UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; - if ([viewController isKindOfClass:[FlutterViewController class]]) { - return (FlutterViewController*)viewController; +- (FlutterViewController*)rootFlutterViewController { + UIViewController* rootViewController = _window.rootViewController; + if ([rootViewController isKindOfClass:[FlutterViewController class]]) { + return (FlutterViewController*)rootViewController; } return nil; } @@ -128,9 +128,8 @@ - (BOOL)application:(UIApplication*)application if ([_lifeCycleDelegate application:application openURL:url options:options]) { return YES; } else { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController isKindOfClass:[FlutterViewController class]]) { - FlutterViewController* flutterViewController = (FlutterViewController*)rootViewController; + FlutterViewController* flutterViewController = [self rootFlutterViewController]; + if (flutterViewController) { [flutterViewController.engine waitForFirstFrame:3.0 callback:^(BOOL didTimeout) { @@ -198,27 +197,25 @@ - (BOOL)application:(UIApplication*)application #pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController - (NSObject*)registrarForPlugin:(NSString*)pluginKey { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController isKindOfClass:[FlutterViewController class]]) { - return - [[(FlutterViewController*)rootViewController pluginRegistry] registrarForPlugin:pluginKey]; + FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; + if (flutterRootViewController) { + return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey]; } return nil; } - (BOOL)hasPlugin:(NSString*)pluginKey { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController isKindOfClass:[FlutterViewController class]]) { - return [[(FlutterViewController*)rootViewController pluginRegistry] hasPlugin:pluginKey]; + FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; + if (flutterRootViewController) { + return [[flutterRootViewController pluginRegistry] hasPlugin:pluginKey]; } return false; } - (NSObject*)valuePublishedByPlugin:(NSString*)pluginKey { - UIViewController* rootViewController = _window.rootViewController; - if ([rootViewController isKindOfClass:[FlutterViewController class]]) { - return [[(FlutterViewController*)rootViewController pluginRegistry] - valuePublishedByPlugin:pluginKey]; + FlutterViewController* flutterRootViewController = [self rootFlutterViewController]; + if (flutterRootViewController) { + return [[flutterRootViewController pluginRegistry] valuePublishedByPlugin:pluginKey]; } return nil; } From 5441cec12aa7528d545452bef82fa32cefde29d8 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 23 Oct 2020 15:51:19 -0700 Subject: [PATCH 5/7] added test for openURL --- shell/platform/darwin/ios/BUILD.gn | 1 + .../framework/Source/FlutterAppDelegate.mm | 9 ++++ .../Source/FlutterAppDelegateTest.mm | 41 +++++++++++++++++++ .../Source/FlutterAppDelegate_Test.h | 6 +++ .../ios/framework/Source/FlutterEngine_Test.h | 2 + 5 files changed, 59 insertions(+) create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm create mode 100644 shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 793c97be93701..c0caf9f94d588 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -206,6 +206,7 @@ shared_library("ios_test_flutter") { "//build/config:symbol_visibility_hidden", ] sources = [ + "framework/Source/FlutterAppDelegateTest.mm", "framework/Source/FlutterBinaryMessengerRelayTest.mm", "framework/Source/FlutterDartProjectTest.mm", "framework/Source/FlutterEngineTest.mm", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index f2b3eac1254dd..37ab9c46c6f38 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -7,6 +7,7 @@ #import "flutter/fml/logging.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPluginAppLifeCycleDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" @@ -14,6 +15,10 @@ static NSString* kRemoteNotificationCapabitiliy = @"remote-notification"; static NSString* kBackgroundFetchCapatibility = @"fetch"; +@interface FlutterAppDelegate () +@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void); +@end + @implementation FlutterAppDelegate { FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate; } @@ -27,6 +32,7 @@ - (instancetype)init { - (void)dealloc { [_lifeCycleDelegate release]; + [_rootFlutterViewControllerGetter release]; [super dealloc]; } @@ -43,6 +49,9 @@ - (BOOL)application:(UIApplication*)application // Returns the key window's rootViewController, if it's a FlutterViewController. // Otherwise, returns nil. - (FlutterViewController*)rootFlutterViewController { + if (_rootFlutterViewControllerGetter != nil) { + return _rootFlutterViewControllerGetter(); + } UIViewController* rootViewController = _window.rootViewController; if ([rootViewController isKindOfClass:[FlutterViewController class]]) { return (FlutterViewController*)rootViewController; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm new file mode 100644 index 0000000000000..69dc0e016a2ac --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm @@ -0,0 +1,41 @@ +// 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 +#import + +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h" + +FLUTTER_ASSERT_ARC + +@interface FlutterAppDelegateTest : XCTestCase +@end + +@implementation FlutterAppDelegateTest + +- (void)testLaunchUrl { + FlutterAppDelegate* appDelegate = [[FlutterAppDelegate alloc] init]; + FlutterViewController* viewController = OCMClassMock([FlutterViewController class]); + FlutterEngine* engine = OCMClassMock([FlutterEngine class]); + FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]); + OCMStub([engine navigationChannel]).andReturn(navigationChannel); + OCMStub([viewController engine]).andReturn(engine); + OCMStub([engine waitForFirstFrame:3.0 callback:([OCMArg invokeBlockWithArgs:@(NO), nil])]); + appDelegate.rootFlutterViewControllerGetter = ^{ + return viewController; + }; + NSURL* url = [NSURL URLWithString:@"http://example.com"]; + NSDictionary* options = @{}; + BOOL result = [appDelegate application:[UIApplication sharedApplication] + openURL:url + options:options]; + XCTAssertTrue(result); + OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:url.path]); +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h new file mode 100644 index 0000000000000..b0eb5d06bffe4 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h @@ -0,0 +1,6 @@ + +@class FlutterViewController; + +@interface FlutterAppDelegate (Test) +@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void); +@end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index dde370f72cbf4..748cc8c642d8b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -4,6 +4,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h" +@class FlutterBinaryMessengerRelay; + // Category to add test-only visibility. @interface FlutterEngine (Test) - (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger; From fc2023ba024fded7d0c72a07f2ae89e329150c40 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 23 Oct 2020 16:00:54 -0700 Subject: [PATCH 6/7] added missing license entries --- ci/licenses_golden/licenses_flutter | 2 ++ .../darwin/ios/framework/Source/FlutterAppDelegate_Test.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 50db6317a5012..b5a3e2b08390d 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -926,6 +926,8 @@ FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/flutter_sta FILE: ../../../flutter/shell/platform/darwin/ios/framework/Flutter.podspec FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h diff --git a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h index b0eb5d06bffe4..1b10384058db5 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h @@ -1,3 +1,6 @@ +// 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. @class FlutterViewController; From 43e792f11e8c891ebc3fe1cf011198d3a15645b4 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 23 Oct 2020 16:26:46 -0700 Subject: [PATCH 7/7] fixed licenses file --- ci/licenses_golden/licenses_flutter | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b5a3e2b08390d..46097e6e73e77 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -926,8 +926,6 @@ FILE: ../../../flutter/shell/platform/darwin/common/framework/Source/flutter_sta FILE: ../../../flutter/shell/platform/darwin/ios/framework/Flutter.podspec FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/Flutter.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterAppDelegate.h -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm -FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterCallbackCache.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterDartProject.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -938,6 +936,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin FILE: ../../../flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Info.plist FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelay.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterBinaryMessengerRelayTest.mm