mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Revert "[macOS] Make FlutterEngine support multiple views (#37976)" (flutter/engine#39536)
This commit is contained in:
parent
887f335da2
commit
9b1a97cba6
@ -32,9 +32,6 @@ extern const uint64_t kFlutterDefaultViewId;
|
||||
|
||||
/**
|
||||
* Coordinates a single instance of execution of a Flutter engine.
|
||||
*
|
||||
* A FlutterEngine can only be attached with one controller from the native
|
||||
* code.
|
||||
*/
|
||||
FLUTTER_DARWIN_EXPORT
|
||||
@interface FlutterEngine : NSObject <FlutterTextureRegistry, FlutterPluginRegistry>
|
||||
@ -79,9 +76,10 @@ FLUTTER_DARWIN_EXPORT
|
||||
- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint;
|
||||
|
||||
/**
|
||||
* The `FlutterViewController` of this engine, if any.
|
||||
* The default `FlutterViewController` associated with this engine, if any.
|
||||
*
|
||||
* This view is used by legacy APIs that assume a single view.
|
||||
* The default view always has ID kFlutterDefaultViewId, and is the view
|
||||
* operated by the APIs that do not have a view ID specified.
|
||||
*
|
||||
* Setting this field from nil to a non-nil view controller also updates
|
||||
* the view controller's engine and ID.
|
||||
|
||||
@ -36,18 +36,12 @@ FLUTTER_DARWIN_EXPORT
|
||||
@property(nonnull, readonly) id<FlutterTextureRegistry> textures;
|
||||
|
||||
/**
|
||||
* The default view displaying Flutter content.
|
||||
* The view displaying Flutter content. May return |nil|, for instance in a headless environment.
|
||||
*
|
||||
* This method may return |nil|, for instance in a headless environment.
|
||||
*
|
||||
* The default view is a special view operated by single-view APIs.
|
||||
* WARNING: If/when multiple Flutter views within the same application are supported (#30701), this
|
||||
* API will change.
|
||||
*/
|
||||
- (nullable NSView*)view;
|
||||
|
||||
/**
|
||||
* The `NSView` associated with the given view ID, if any.
|
||||
*/
|
||||
- (nullable NSView*)viewForId:(uint64_t)viewId;
|
||||
@property(nullable, readonly) NSView* view;
|
||||
|
||||
/**
|
||||
* Registers |delegate| to receive handleMethodCall:result: callbacks for the given |channel|.
|
||||
|
||||
@ -89,9 +89,7 @@ FLUTTER_DARWIN_EXPORT
|
||||
NS_DESIGNATED_INITIALIZER;
|
||||
- (nonnull instancetype)initWithCoder:(nonnull NSCoder*)nibNameOrNil NS_DESIGNATED_INITIALIZER;
|
||||
/**
|
||||
* Initializes this FlutterViewController with an existing `FlutterEngine`.
|
||||
*
|
||||
* The initialized view controller will add itself to the engine as part of this process.
|
||||
* Initializes this FlutterViewController with the specified `FlutterEngine`.
|
||||
*
|
||||
* This initializer is suitable for both the first Flutter view controller and
|
||||
* the following ones of the app.
|
||||
|
||||
@ -19,7 +19,7 @@ bool FlutterCompositor::CreateBackingStore(const FlutterBackingStoreConfig* conf
|
||||
// TODO(dkwingsmt): This class only supports single-view for now. As more
|
||||
// classes are gradually converted to multi-view, it should get the view ID
|
||||
// from somewhere.
|
||||
FlutterView* view = [view_provider_ viewForId:kFlutterDefaultViewId];
|
||||
FlutterView* view = [view_provider_ getView:kFlutterDefaultViewId];
|
||||
if (!view) {
|
||||
return false;
|
||||
}
|
||||
@ -37,7 +37,7 @@ bool FlutterCompositor::CreateBackingStore(const FlutterBackingStoreConfig* conf
|
||||
bool FlutterCompositor::Present(uint64_t view_id,
|
||||
const FlutterLayer** layers,
|
||||
size_t layers_count) {
|
||||
FlutterView* view = [view_provider_ viewForId:view_id];
|
||||
FlutterView* view = [view_provider_ getView:view_id];
|
||||
if (!view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -11,8 +11,6 @@
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h"
|
||||
#import "flutter/testing/testing.h"
|
||||
|
||||
extern const uint64_t kFlutterDefaultViewId;
|
||||
|
||||
@interface FlutterViewMockProvider : NSObject <FlutterViewProvider> {
|
||||
FlutterView* _defaultView;
|
||||
}
|
||||
@ -32,7 +30,7 @@ extern const uint64_t kFlutterDefaultViewId;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable FlutterView*)viewForId:(uint64_t)viewId {
|
||||
- (nullable FlutterView*)getView:(uint64_t)viewId {
|
||||
if (viewId == kFlutterDefaultViewId) {
|
||||
return _defaultView;
|
||||
}
|
||||
|
||||
@ -84,29 +84,6 @@ constexpr char kTextPlainFormat[] = "text/plain";
|
||||
|
||||
- (nullable FlutterViewController*)viewControllerForId:(uint64_t)viewId;
|
||||
|
||||
/**
|
||||
* An internal method that adds the view controller with the given ID.
|
||||
*
|
||||
* This method assigns the controller with the ID, puts the controller into the
|
||||
* map, and does assertions related to the default view ID.
|
||||
*/
|
||||
- (void)registerViewController:(FlutterViewController*)controller forId:(uint64_t)viewId;
|
||||
|
||||
/**
|
||||
* An internal method that removes the view controller with the given ID.
|
||||
*
|
||||
* This method clears the ID of the controller, removes the controller from the
|
||||
* map. This is an no-op if the view ID is not associated with any view
|
||||
* controllers.
|
||||
*/
|
||||
- (void)deregisterViewControllerForId:(uint64_t)viewId;
|
||||
|
||||
/**
|
||||
* Shuts down the engine if view requirement is not met, and headless execution
|
||||
* is not allowed.
|
||||
*/
|
||||
- (void)shutDownIfNeeded;
|
||||
|
||||
/**
|
||||
* Sends the list of user-preferred locales to the Flutter engine.
|
||||
*/
|
||||
@ -184,18 +161,10 @@ constexpr char kTextPlainFormat[] = "text/plain";
|
||||
}
|
||||
|
||||
- (NSView*)view {
|
||||
return [self viewForId:kFlutterDefaultViewId];
|
||||
}
|
||||
|
||||
- (NSView*)viewForId:(uint64_t)viewId {
|
||||
FlutterViewController* controller = [_flutterEngine viewControllerForId:viewId];
|
||||
if (controller == nil) {
|
||||
return nil;
|
||||
if (!_flutterEngine.viewController.viewLoaded) {
|
||||
[_flutterEngine.viewController loadView];
|
||||
}
|
||||
if (!controller.viewLoaded) {
|
||||
[controller loadView];
|
||||
}
|
||||
return controller.flutterView;
|
||||
return _flutterEngine.viewController.flutterView;
|
||||
}
|
||||
|
||||
- (void)addMethodCallDelegate:(nonnull id<FlutterPlugin>)delegate
|
||||
@ -245,11 +214,6 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
// when the engine is destroyed.
|
||||
std::unique_ptr<flutter::FlutterCompositor> _macOSCompositor;
|
||||
|
||||
// The information of all views attached to this engine mapped from IDs.
|
||||
//
|
||||
// It can't use NSDictionary, because the values need to be weak references.
|
||||
NSMapTable* _viewControllers;
|
||||
|
||||
// FlutterCompositor is copied and used in embedder.cc.
|
||||
FlutterCompositor _compositor;
|
||||
|
||||
@ -266,8 +230,6 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
|
||||
// A method channel for miscellaneous platform functionality.
|
||||
FlutterMethodChannel* _platformChannel;
|
||||
|
||||
int _nextViewId;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
|
||||
@ -287,14 +249,10 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
_semanticsEnabled = NO;
|
||||
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
|
||||
[_isResponseValid addObject:@YES];
|
||||
// kFlutterDefaultViewId is reserved for the default view.
|
||||
// All IDs above it are for regular views.
|
||||
_nextViewId = kFlutterDefaultViewId + 1;
|
||||
|
||||
_embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
|
||||
FlutterEngineGetProcAddresses(&_embedderAPI);
|
||||
|
||||
_viewControllers = [NSMapTable weakToWeakObjectsMapTable];
|
||||
_renderer = [[FlutterRenderer alloc] initWithFlutterEngine:self];
|
||||
|
||||
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
|
||||
@ -326,7 +284,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!_allowHeadlessExecution && [_viewControllers count] == 0) {
|
||||
if (!_allowHeadlessExecution && !_viewController) {
|
||||
NSLog(@"Attempted to run an engine with no view controller without headless mode enabled.");
|
||||
return NO;
|
||||
}
|
||||
@ -353,11 +311,8 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
|
||||
flutterArguments.update_semantics_callback = [](const FlutterSemanticsUpdate* update,
|
||||
void* user_data) {
|
||||
// TODO(dkwingsmt): This callback only supports single-view, therefore it
|
||||
// only operates on the default view. To support multi-view, we need a
|
||||
// way to pass in the ID (probably through FlutterSemanticsUpdate).
|
||||
FlutterEngine* engine = (__bridge FlutterEngine*)user_data;
|
||||
[[engine viewControllerForId:kFlutterDefaultViewId] updateSemantics:update];
|
||||
[engine.viewController updateSemantics:update];
|
||||
};
|
||||
flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String;
|
||||
flutterArguments.shutdown_dart_vm_when_done = true;
|
||||
@ -419,14 +374,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
}
|
||||
|
||||
[self sendUserLocales];
|
||||
|
||||
// Update window metric for all view controllers.
|
||||
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
|
||||
FlutterViewController* nextViewController;
|
||||
while ((nextViewController = [viewControllerEnumerator nextObject])) {
|
||||
[self updateWindowMetricsForViewController:nextViewController];
|
||||
}
|
||||
|
||||
[self updateWindowMetrics];
|
||||
[self updateDisplayConfig];
|
||||
// Send the initial user settings such as brightness and text scale factor
|
||||
// to the engine.
|
||||
@ -460,59 +408,28 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerViewController:(FlutterViewController*)controller forId:(uint64_t)viewId {
|
||||
NSAssert(controller != nil, @"The controller must not be nil.");
|
||||
NSAssert(![controller attached],
|
||||
@"The incoming view controller is already attached to an engine.");
|
||||
NSAssert([_viewControllers objectForKey:@(viewId)] == nil, @"The requested view ID is occupied.");
|
||||
[controller attachToEngine:self withId:viewId];
|
||||
NSAssert(controller.id == viewId, @"Failed to assign view ID.");
|
||||
[_viewControllers setObject:controller forKey:@(viewId)];
|
||||
}
|
||||
|
||||
- (void)deregisterViewControllerForId:(uint64_t)viewId {
|
||||
FlutterViewController* oldController = [self viewControllerForId:viewId];
|
||||
if (oldController != nil) {
|
||||
[oldController detachFromEngine];
|
||||
[_viewControllers removeObjectForKey:@(viewId)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)shutDownIfNeeded {
|
||||
if ([_viewControllers count] == 0 && !_allowHeadlessExecution) {
|
||||
[self shutDownEngine];
|
||||
}
|
||||
}
|
||||
|
||||
- (FlutterViewController*)viewControllerForId:(uint64_t)viewId {
|
||||
FlutterViewController* controller = [_viewControllers objectForKey:@(viewId)];
|
||||
NSAssert(controller == nil || controller.id == viewId,
|
||||
@"The stored controller has unexpected view ID.");
|
||||
return controller;
|
||||
}
|
||||
|
||||
- (void)setViewController:(FlutterViewController*)controller {
|
||||
FlutterViewController* currentController =
|
||||
[_viewControllers objectForKey:@(kFlutterDefaultViewId)];
|
||||
if (currentController == controller) {
|
||||
if (_viewController == controller) {
|
||||
// From nil to nil, or from non-nil to the same controller.
|
||||
return;
|
||||
}
|
||||
if (currentController == nil && controller != nil) {
|
||||
if (_viewController == nil && controller != nil) {
|
||||
// From nil to non-nil.
|
||||
NSAssert(controller.engine == nil,
|
||||
@"Failed to set view controller to the engine: "
|
||||
@"The given FlutterViewController is already attached to an engine %@. "
|
||||
@"If you wanted to create an FlutterViewController and set it to an existing engine, "
|
||||
@"you should use FlutterViewController#init(engine:, nibName, bundle:) instead.",
|
||||
@"you should create it with init(engine:, nibName, bundle:) instead.",
|
||||
controller.engine);
|
||||
[self registerViewController:controller forId:kFlutterDefaultViewId];
|
||||
} else if (currentController != nil && controller == nil) {
|
||||
NSAssert(currentController.id == kFlutterDefaultViewId,
|
||||
@"The default controller has an unexpected ID %llu", currentController.id);
|
||||
_viewController = controller;
|
||||
[_viewController attachToEngine:self withId:kFlutterDefaultViewId];
|
||||
} else if (_viewController != nil && controller == nil) {
|
||||
// From non-nil to nil.
|
||||
[self deregisterViewControllerForId:kFlutterDefaultViewId];
|
||||
[self shutDownIfNeeded];
|
||||
[_viewController detachFromEngine];
|
||||
_viewController = nil;
|
||||
if (!_allowHeadlessExecution) {
|
||||
[self shutDownEngine];
|
||||
}
|
||||
} else {
|
||||
// From non-nil to a different non-nil view controller.
|
||||
NSAssert(NO,
|
||||
@ -520,14 +437,10 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
@"The engine already has a default view controller %@. "
|
||||
@"If you wanted to make the default view render in a different window, "
|
||||
@"you should attach the current view controller to the window instead.",
|
||||
[_viewControllers objectForKey:@(kFlutterDefaultViewId)]);
|
||||
_viewController);
|
||||
}
|
||||
}
|
||||
|
||||
- (FlutterViewController*)viewController {
|
||||
return [self viewControllerForId:kFlutterDefaultViewId];
|
||||
}
|
||||
|
||||
- (FlutterCompositor*)createFlutterCompositor {
|
||||
_macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
|
||||
[[FlutterViewEngineProvider alloc] initWithEngine:self], _platformViewController);
|
||||
@ -572,17 +485,6 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
|
||||
#pragma mark - Framework-internal methods
|
||||
|
||||
- (void)addViewController:(FlutterViewController*)controller {
|
||||
[self registerViewController:controller forId:kFlutterDefaultViewId];
|
||||
}
|
||||
|
||||
- (void)removeViewController:(nonnull FlutterViewController*)viewController {
|
||||
NSAssert([viewController attached] && viewController.engine == self,
|
||||
@"The given view controller is not associated with this engine.");
|
||||
[self deregisterViewControllerForId:viewController.id];
|
||||
[self shutDownIfNeeded];
|
||||
}
|
||||
|
||||
- (BOOL)running {
|
||||
return _engine != nullptr;
|
||||
}
|
||||
@ -642,19 +544,11 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
return [[[NSProcessInfo processInfo] arguments] firstObject] ?: @"Flutter";
|
||||
}
|
||||
|
||||
- (void)updateWindowMetricsForViewController:(FlutterViewController*)viewController {
|
||||
if (viewController.id != kFlutterDefaultViewId) {
|
||||
// TODO(dkwingsmt): The embedder API only supports single-view for now. As
|
||||
// embedder APIs are converted to multi-view, this method should support any
|
||||
// views.
|
||||
- (void)updateWindowMetrics {
|
||||
if (!_engine || !self.viewController.viewLoaded) {
|
||||
return;
|
||||
}
|
||||
if (!_engine || !viewController || !viewController.viewLoaded) {
|
||||
return;
|
||||
}
|
||||
NSAssert([self viewControllerForId:viewController.id] == viewController,
|
||||
@"The provided view controller is not attached to this engine.");
|
||||
NSView* view = viewController.flutterView;
|
||||
NSView* view = self.viewController.flutterView;
|
||||
CGRect scaledBounds = [view convertRectToBacking:view.bounds];
|
||||
CGSize scaledSize = scaledBounds.size;
|
||||
double pixelRatio = view.bounds.size.width == 0 ? 1 : scaledSize.width / view.bounds.size.width;
|
||||
@ -685,14 +579,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
return;
|
||||
}
|
||||
_semanticsEnabled = enabled;
|
||||
|
||||
// Update all view controllers' bridges.
|
||||
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
|
||||
FlutterViewController* nextViewController;
|
||||
while ((nextViewController = [viewControllerEnumerator nextObject])) {
|
||||
[nextViewController notifySemanticsEnabledChanged];
|
||||
}
|
||||
|
||||
[_viewController notifySemanticsEnabledChanged];
|
||||
_embedderAPI.UpdateSemanticsEnabled(_engine, _semanticsEnabled);
|
||||
}
|
||||
|
||||
@ -708,6 +595,17 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
|
||||
#pragma mark - Private methods
|
||||
|
||||
- (FlutterViewController*)viewControllerForId:(uint64_t)viewId {
|
||||
// TODO(dkwingsmt): The engine only supports single-view, therefore it
|
||||
// only processes the default ID. After the engine supports multiple views,
|
||||
// this method should be able to return the view for any IDs.
|
||||
NSAssert(viewId == kFlutterDefaultViewId, @"Unexpected view ID %llu", viewId);
|
||||
if (viewId == kFlutterDefaultViewId) {
|
||||
return _viewController;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)sendUserLocales {
|
||||
if (!self.running) {
|
||||
return;
|
||||
@ -771,10 +669,8 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
}
|
||||
|
||||
- (void)engineCallbackOnPreEngineRestart {
|
||||
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
|
||||
FlutterViewController* nextViewController;
|
||||
while ((nextViewController = [viewControllerEnumerator nextObject])) {
|
||||
[nextViewController onPreEngineRestart];
|
||||
if (_viewController) {
|
||||
[_viewController onPreEngineRestart];
|
||||
}
|
||||
}
|
||||
|
||||
@ -786,11 +682,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
return;
|
||||
}
|
||||
|
||||
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
|
||||
FlutterViewController* nextViewController;
|
||||
while ((nextViewController = [viewControllerEnumerator nextObject])) {
|
||||
[nextViewController.flutterView shutdown];
|
||||
}
|
||||
[self.viewController.flutterView shutdown];
|
||||
|
||||
FlutterEngineResult result = _embedderAPI.Deinitialize(_engine);
|
||||
if (result != kSuccess) {
|
||||
@ -855,12 +747,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
|
||||
|
||||
- (void)onAccessibilityStatusChanged:(NSNotification*)notification {
|
||||
BOOL enabled = [notification.userInfo[kEnhancedUserInterfaceKey] boolValue];
|
||||
NSEnumerator* viewControllerEnumerator = [_viewControllers objectEnumerator];
|
||||
FlutterViewController* nextViewController;
|
||||
while ((nextViewController = [viewControllerEnumerator nextObject])) {
|
||||
[nextViewController onAccessibilityStatusChanged:enabled];
|
||||
}
|
||||
|
||||
[self.viewController onAccessibilityStatusChanged:enabled];
|
||||
self.semanticsEnabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
@ -614,69 +614,6 @@ TEST(EngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
|
||||
rasterThread.join();
|
||||
}
|
||||
|
||||
TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByController) {
|
||||
NSString* fixtures = @(flutter::testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
|
||||
|
||||
FlutterEngine* engine;
|
||||
FlutterViewController* viewController1;
|
||||
|
||||
@autoreleasepool {
|
||||
// Create FVC1.
|
||||
viewController1 = [[FlutterViewController alloc] initWithProject:project];
|
||||
EXPECT_EQ(viewController1.id, 0ull);
|
||||
|
||||
engine = viewController1.engine;
|
||||
engine.viewController = nil;
|
||||
|
||||
// Create FVC2 based on the same engine.
|
||||
FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
EXPECT_EQ(engine.viewController, viewController2);
|
||||
}
|
||||
// FVC2 is deallocated but FVC1 is retained.
|
||||
|
||||
EXPECT_EQ(engine.viewController, nil);
|
||||
|
||||
engine.viewController = viewController1;
|
||||
EXPECT_EQ(engine.viewController, viewController1);
|
||||
EXPECT_EQ(viewController1.id, 0ull);
|
||||
}
|
||||
|
||||
TEST_F(FlutterEngineTest, ManageControllersIfInitiatedByEngine) {
|
||||
// Don't create the engine with `CreateMockFlutterEngine`, because it adds
|
||||
// additional references to FlutterViewControllers, which is crucial to this
|
||||
// test case.
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
|
||||
project:nil
|
||||
allowHeadlessExecution:NO];
|
||||
FlutterViewController* viewController1;
|
||||
|
||||
@autoreleasepool {
|
||||
viewController1 = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
|
||||
EXPECT_EQ(viewController1.id, 0ull);
|
||||
EXPECT_EQ(engine.viewController, viewController1);
|
||||
|
||||
engine.viewController = nil;
|
||||
|
||||
FlutterViewController* viewController2 = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
EXPECT_EQ(viewController2.id, 0ull);
|
||||
EXPECT_EQ(engine.viewController, viewController2);
|
||||
}
|
||||
// FVC2 is deallocated but FVC1 is retained.
|
||||
|
||||
EXPECT_EQ(engine.viewController, nil);
|
||||
|
||||
engine.viewController = viewController1;
|
||||
EXPECT_EQ(engine.viewController, viewController1);
|
||||
EXPECT_EQ(viewController1.id, 0ull);
|
||||
}
|
||||
|
||||
} // namespace flutter::testing
|
||||
|
||||
// NOLINTEND(clang-analyzer-core.StackAddressEscape)
|
||||
|
||||
@ -48,39 +48,9 @@
|
||||
@property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard;
|
||||
|
||||
/**
|
||||
* Attach a view controller to the engine as its default controller.
|
||||
*
|
||||
* Practically, since FlutterEngine can only be attached with one controller,
|
||||
* the given controller, if successfully attached, will always have the default
|
||||
* view ID kFlutterDefaultViewId.
|
||||
*
|
||||
* The engine holds a weak reference to the attached view controller.
|
||||
*
|
||||
* If the given view controller is already attached to an engine, this call
|
||||
* throws an assertion.
|
||||
* Informs the engine that the associated view controller's view size has changed.
|
||||
*/
|
||||
- (void)addViewController:(nonnull FlutterViewController*)viewController;
|
||||
|
||||
/**
|
||||
* Dissociate the given view controller from this engine.
|
||||
*
|
||||
* Practically, since FlutterEngine can only be attached with one controller,
|
||||
* the given controller must be the default view controller.
|
||||
*
|
||||
* If the view controller is not associated with this engine, this call throws an
|
||||
* assertion.
|
||||
*/
|
||||
- (void)removeViewController:(nonnull FlutterViewController*)viewController;
|
||||
|
||||
/**
|
||||
* 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)updateWindowMetrics;
|
||||
|
||||
/**
|
||||
* Dispatches the given pointer event data to engine.
|
||||
|
||||
@ -57,7 +57,7 @@ typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
|
||||
/**
|
||||
* The text input plugin set by initialization.
|
||||
*/
|
||||
@property(nonatomic, weak) id<FlutterKeyboardViewDelegate> viewDelegate;
|
||||
@property(nonatomic) id<FlutterKeyboardViewDelegate> viewDelegate;
|
||||
|
||||
/**
|
||||
* The primary responders added by addPrimaryResponder.
|
||||
|
||||
@ -89,7 +89,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
#pragma mark - Embedder callback implementations.
|
||||
|
||||
- (FlutterMetalTexture)createTextureForView:(uint64_t)viewId size:(CGSize)size {
|
||||
FlutterView* view = [_viewProvider viewForId:viewId];
|
||||
FlutterView* view = [_viewProvider getView:viewId];
|
||||
NSAssert(view != nil, @"Can't create texture on a non-existent view 0x%llx.", viewId);
|
||||
if (view == nil) {
|
||||
// FlutterMetalTexture has texture `null`, therefore is discarded.
|
||||
@ -99,7 +99,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
|
||||
}
|
||||
|
||||
- (BOOL)present:(uint64_t)viewId texture:(const FlutterMetalTexture*)texture {
|
||||
FlutterView* view = [_viewProvider viewForId:viewId];
|
||||
FlutterView* view = [_viewProvider getView:viewId];
|
||||
if (view == nil) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
@ -9,54 +9,37 @@
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
|
||||
#include "flutter/testing/testing.h"
|
||||
|
||||
@interface RendererTestViewController : FlutterViewController
|
||||
- (void)loadMockFlutterView:(FlutterView*)mockView;
|
||||
@end
|
||||
|
||||
@implementation RendererTestViewController {
|
||||
FlutterView* _mockFlutterView;
|
||||
}
|
||||
|
||||
- (void)loadMockFlutterView:(FlutterView*)mockView {
|
||||
_mockFlutterView = mockView;
|
||||
[self loadView];
|
||||
}
|
||||
|
||||
- (nonnull FlutterView*)createFlutterViewWithMTLDevice:(id<MTLDevice>)device
|
||||
commandQueue:(id<MTLCommandQueue>)commandQueue {
|
||||
return _mockFlutterView;
|
||||
}
|
||||
@end
|
||||
|
||||
namespace flutter::testing {
|
||||
|
||||
namespace {
|
||||
// Returns an engine configured for the test fixture resource configuration.
|
||||
RendererTestViewController* CreateTestViewController() {
|
||||
FlutterEngine* CreateTestEngine() {
|
||||
NSString* fixtures = @(testing::GetFixturesPath());
|
||||
FlutterDartProject* project = [[FlutterDartProject alloc]
|
||||
initWithAssetsPath:fixtures
|
||||
ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
|
||||
RendererTestViewController* viewController =
|
||||
[[RendererTestViewController alloc] initWithProject:project];
|
||||
return viewController;
|
||||
return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
|
||||
}
|
||||
|
||||
void SetEngineDefaultView(FlutterEngine* engine, id flutterView) {
|
||||
id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
|
||||
OCMStub([mockFlutterViewController flutterView]).andReturn(flutterView);
|
||||
[engine setViewController:mockFlutterViewController];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(FlutterRenderer, PresentDelegatesToFlutterView) {
|
||||
RendererTestViewController* viewController = CreateTestViewController();
|
||||
FlutterEngine* engine = viewController.engine;
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
|
||||
|
||||
id viewMock = OCMClassMock([FlutterView class]);
|
||||
[viewController loadMockFlutterView:viewMock];
|
||||
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
|
||||
SetEngineDefaultView(engine, viewMock);
|
||||
|
||||
id surfaceManagerMock = OCMClassMock([FlutterSurfaceManager class]);
|
||||
OCMStub([viewMock surfaceManager]).andReturn(surfaceManagerMock);
|
||||
@ -78,12 +61,11 @@ TEST(FlutterRenderer, PresentDelegatesToFlutterView) {
|
||||
}
|
||||
|
||||
TEST(FlutterRenderer, TextureReturnedByFlutterView) {
|
||||
RendererTestViewController* viewController = CreateTestViewController();
|
||||
FlutterEngine* engine = viewController.engine;
|
||||
FlutterEngine* engine = CreateTestEngine();
|
||||
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
|
||||
|
||||
id viewMock = OCMClassMock([FlutterView class]);
|
||||
[viewController loadMockFlutterView:viewMock];
|
||||
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
|
||||
SetEngineDefaultView(engine, viewMock);
|
||||
|
||||
id surfaceManagerMock = OCMClassMock([FlutterSurfaceManager class]);
|
||||
OCMStub([viewMock surfaceManager]).andReturn(surfaceManagerMock);
|
||||
|
||||
@ -309,12 +309,11 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
@"The FlutterViewController is unexpectedly attached to "
|
||||
@"engine %@ before initialization.",
|
||||
controller.engine);
|
||||
[engine addViewController:controller];
|
||||
engine.viewController = controller;
|
||||
NSCAssert(controller.engine != nil,
|
||||
@"The FlutterViewController unexpectedly stays unattached after initialization. "
|
||||
@"In unit tests, this is likely because either the FlutterViewController or "
|
||||
@"the FlutterEngine is mocked. Please subclass these classes instead.",
|
||||
controller.engine, controller.id);
|
||||
@"the FlutterEngine is mocked. Please subclass these classes instead.");
|
||||
controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
|
||||
controller->_textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:controller];
|
||||
[controller initializeKeyboard];
|
||||
@ -356,6 +355,11 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
nibName:(nullable NSString*)nibName
|
||||
bundle:(nullable NSBundle*)nibBundle {
|
||||
NSAssert(engine != nil, @"Engine is required");
|
||||
NSAssert(engine.viewController == nil,
|
||||
@"The supplied FlutterEngine is already used with FlutterViewController "
|
||||
"instance. One instance of the FlutterEngine can only be attached to one "
|
||||
"FlutterViewController at a time. Set FlutterEngine.viewController "
|
||||
"to nil before attaching it to another FlutterViewController.");
|
||||
|
||||
self = [super initWithNibName:nibName bundle:nibBundle];
|
||||
if (self) {
|
||||
@ -408,9 +412,7 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if ([self attached]) {
|
||||
[_engine removeViewController:self];
|
||||
}
|
||||
_engine.viewController = nil;
|
||||
CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
|
||||
CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge void*)self);
|
||||
}
|
||||
@ -581,7 +583,8 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
- (void)initializeKeyboard {
|
||||
// TODO(goderbauer): Seperate keyboard/textinput stuff into ViewController specific and Engine
|
||||
// global parts. Move the global parts to FlutterEngine.
|
||||
_keyboardManager = [[FlutterKeyboardManager alloc] initWithViewDelegate:self];
|
||||
__weak FlutterViewController* weakSelf = self;
|
||||
_keyboardManager = [[FlutterKeyboardManager alloc] initWithViewDelegate:weakSelf];
|
||||
}
|
||||
|
||||
- (void)dispatchMouseEvent:(nonnull NSEvent*)event {
|
||||
@ -792,7 +795,7 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
|
||||
* Responds to view reshape by notifying the engine of the change in dimensions.
|
||||
*/
|
||||
- (void)viewDidReshape:(NSView*)view {
|
||||
[_engine updateWindowMetricsForViewController:self];
|
||||
[_engine updateWindowMetrics];
|
||||
}
|
||||
|
||||
#pragma mark - FlutterPluginRegistry
|
||||
|
||||
@ -22,8 +22,14 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable FlutterView*)viewForId:(uint64_t)viewId {
|
||||
return [_engine viewControllerForId:viewId].flutterView;
|
||||
- (nullable FlutterView*)getView:(uint64_t)viewId {
|
||||
// TODO(dkwingsmt): This class only supports the first view for now. After
|
||||
// FlutterEngine supports multi-view, it should get the view associated to the
|
||||
// ID.
|
||||
if (viewId == kFlutterDefaultViewId) {
|
||||
return _engine.viewController.flutterView;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTestUtils.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
|
||||
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h"
|
||||
@ -20,29 +19,23 @@ namespace flutter::testing {
|
||||
|
||||
TEST(FlutterViewEngineProviderUnittests, GetViewReturnsTheCorrectView) {
|
||||
FlutterViewEngineProvider* viewProvider;
|
||||
id mockEngine = CreateMockFlutterEngine(@"");
|
||||
id mockEngine = OCMClassMock([FlutterEngine class]);
|
||||
__block id mockFlutterViewController;
|
||||
OCMStub([mockEngine viewControllerForId:0])
|
||||
.ignoringNonObjectArgs()
|
||||
.andDo(^(NSInvocation* invocation) {
|
||||
uint64_t viewId;
|
||||
[invocation getArgument:&viewId atIndex:2];
|
||||
if (viewId == 0 /* kFlutterDefaultViewId */) {
|
||||
if (mockFlutterViewController != nil) {
|
||||
[invocation setReturnValue:&mockFlutterViewController];
|
||||
}
|
||||
}
|
||||
});
|
||||
OCMStub([mockEngine viewController]).andDo(^(NSInvocation* invocation) {
|
||||
if (mockFlutterViewController != nil) {
|
||||
[invocation setReturnValue:&mockFlutterViewController];
|
||||
}
|
||||
});
|
||||
viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:mockEngine];
|
||||
|
||||
// When the view controller is not set, the returned view is nil.
|
||||
EXPECT_EQ([viewProvider viewForId:0], nil);
|
||||
EXPECT_EQ([viewProvider getView:0], nil);
|
||||
|
||||
// When the view controller is set, the returned view is the controller's view.
|
||||
mockFlutterViewController = OCMStrictClassMock([FlutterViewController class]);
|
||||
id mockView = OCMStrictClassMock([FlutterView class]);
|
||||
OCMStub([mockFlutterViewController flutterView]).andReturn(mockView);
|
||||
EXPECT_EQ([viewProvider viewForId:0], mockView);
|
||||
EXPECT_EQ([viewProvider getView:0], mockView);
|
||||
}
|
||||
|
||||
} // namespace flutter::testing
|
||||
|
||||
@ -20,6 +20,6 @@ extern const uint64_t kFlutterDefaultViewId;
|
||||
*
|
||||
* Returns nil if the ID is invalid.
|
||||
*/
|
||||
- (nullable FlutterView*)viewForId:(uint64_t)id;
|
||||
- (nullable FlutterView*)getView:(uint64_t)id;
|
||||
|
||||
@end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user