Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,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
Expand Down
1 change: 1 addition & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,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",
Expand Down
86 changes: 69 additions & 17 deletions shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@

#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/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"

static NSString* kUIBackgroundMode = @"UIBackgroundModes";
static NSString* kRemoteNotificationCapabitiliy = @"remote-notification";
static NSString* kBackgroundFetchCapatibility = @"fetch";

@interface FlutterAppDelegate ()
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);
@end

@implementation FlutterAppDelegate {
FlutterPluginAppLifeCycleDelegate* _lifeCycleDelegate;
}
Expand All @@ -26,6 +32,7 @@ - (instancetype)init {

- (void)dealloc {
[_lifeCycleDelegate release];
[_rootFlutterViewControllerGetter release];
[super dealloc];
}

Expand All @@ -41,10 +48,13 @@ - (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 {
if (_rootFlutterViewControllerGetter != nil) {
return _rootFlutterViewControllerGetter();
}
UIViewController* rootViewController = _window.rootViewController;
if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
return (FlutterViewController*)rootViewController;
}
return nil;
}
Expand Down Expand Up @@ -121,10 +131,54 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center
}
}

static BOOL IsDeepLinkingEnabled(NSDictionary* infoDictionary) {
NSNumber* isEnabled = [infoDictionary objectForKey:@"FlutterDeepLinkingEnabled"];
if (isEnabled) {
return [isEnabled boolValue];
} else {
return NO;
}
}

- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
infoPlistGetter:(NSDictionary* (^)())infoPlistGetter {
if ([_lifeCycleDelegate application:application openURL:url options:options]) {
return YES;
} else if (!IsDeepLinkingEnabled(infoPlistGetter())) {
return NO;
} else {
FlutterViewController* flutterViewController = [self rootFlutterViewController];
if (flutterViewController) {
[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];
}
}];
return YES;
} else {
FML_LOG(ERROR) << "Attempting to open an URL without a Flutter RootViewController.";
return NO;
}
}
}

- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return [_lifeCycleDelegate application:application openURL:url options:options];
return [self application:application
openURL:url
options:options
infoPlistGetter:^NSDictionary*() {
return [[NSBundle mainBundle] infoDictionary];
}];
}

- (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
Expand Down Expand Up @@ -175,27 +229,25 @@ - (BOOL)application:(UIApplication*)application
#pragma mark - FlutterPluginRegistry methods. All delegating to the rootViewController

- (NSObject<FlutterPluginRegistrar>*)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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>

#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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, where do we mock FlutterDeepLinkingEnabled in this test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't, that's why it was failing. Fixed it.

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<UIApplicationOpenURLOptionsKey, id>* options = @{};
BOOL result = [appDelegate application:[UIApplication sharedApplication]
openURL:url
options:options
infoPlistGetter:^NSDictionary*() {
return @{@"FlutterDeepLinkingEnabled" : @(YES)};
}];
XCTAssertTrue(result);
OCMVerify([navigationChannel invokeMethod:@"pushRoute" arguments:url.path]);
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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;

@interface FlutterAppDelegate (Test)
@property(nonatomic, copy) FlutterViewController* (^rootFlutterViewControllerGetter)(void);

- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options
infoPlistGetter:(NSDictionary* (^)())infoPlistGetter;

@end
13 changes: 13 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,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 {
Expand Down
14 changes: 14 additions & 0 deletions shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,18 @@ - (void)testPlatformViewsControllerRenderingSoftware {

XCTAssertEqual(renderingApi, flutter::IOSRenderingAPI::kSoftware);
}

- (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
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/ios/rendering_api_selection.h"

@class FlutterBinaryMessengerRelay;

// Category to add test-only visibility.
@interface FlutterEngine (Test) <FlutterBinaryMessenger>
- (void)setBinaryMessenger:(FlutterBinaryMessengerRelay*)binaryMessenger;
- (flutter::IOSRenderingAPI)platformViewsRenderingAPI;
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;
@end