Revert "[macOS] Make FlutterEngine support multiple views (#37976)" (flutter/engine#39536)

This commit is contained in:
Tong Mu 2023-02-10 10:18:41 -08:00 committed by GitHub
parent 887f335da2
commit 9b1a97cba6
15 changed files with 95 additions and 329 deletions

View File

@ -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.

View File

@ -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|.

View File

@ -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.

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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