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
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,7 @@ - (void)dealloc {

- (void)resignAndRemoveFromSuperview {
if (self.superview != nil) {
// With accessiblity enabled TextInputPlugin is inside _client, so take the
// nextResponder from the _client.
NSResponder* nextResponder = _client != nil ? _client.nextResponder : self.nextResponder;
[self.window makeFirstResponder:nextResponder];
[self.window makeFirstResponder:_flutterViewController.flutterView];
[self removeFromSuperview];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,41 @@ - (bool)testSelectorsAreForwardedToFramework {
ASSERT_FALSE(window.firstResponder == viewController.textInputPlugin);
}

TEST(FlutterTextInputPluginTest, FirstResponderIsCorrect) {
FlutterEngine* engine = CreateTestEngine();
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
nibName:nil
bundle:nil];
[viewController loadView];

NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO];
window.contentView = viewController.view;

ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);

[window makeFirstResponder:viewController.flutterView];

[viewController.textInputPlugin
handleMethodCall:[FlutterMethodCall methodCallWithMethodName:@"TextInput.show" arguments:@[]]
result:^(id){
}];

ASSERT_TRUE(window.firstResponder == viewController.textInputPlugin);

ASSERT_FALSE(viewController.flutterView.acceptsFirstResponder);

[viewController.textInputPlugin
handleMethodCall:[FlutterMethodCall methodCallWithMethodName:@"TextInput.hide" arguments:@[]]
result:^(id){
}];

ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);
ASSERT_TRUE(window.firstResponder == viewController.flutterView);
}

TEST(FlutterTextInputPluginTest, HasZeroSizeAndClipsToBounds) {
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
Expand Down
12 changes: 9 additions & 3 deletions shell/platform/darwin/macos/framework/Source/FlutterView.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@ typedef int64_t FlutterViewId;
constexpr FlutterViewId kFlutterImplicitViewId = 0ll;

/**
* Listener for view resizing.
* Delegate for FlutterView.
*/
@protocol FlutterViewReshapeListener <NSObject>
@protocol FlutterViewDelegate <NSObject>
/**
* Called when the view's backing store changes size.
*/
- (void)viewDidReshape:(nonnull NSView*)view;

/**
* Called to determine whether the view should accept first responder status.
*/
- (BOOL)viewShouldAcceptFirstResponder:(nonnull NSView*)view;

@end

/**
Expand All @@ -43,7 +49,7 @@ constexpr FlutterViewId kFlutterImplicitViewId = 0ll;
*/
- (nullable instancetype)initWithMTLDevice:(nonnull id<MTLDevice>)device
commandQueue:(nonnull id<MTLCommandQueue>)commandQueue
reshapeListener:(nonnull id<FlutterViewReshapeListener>)reshapeListener
delegate:(nonnull id<FlutterViewDelegate>)delegate
threadSynchronizer:(nonnull FlutterThreadSynchronizer*)threadSynchronizer
viewId:(int64_t)viewId NS_DESIGNATED_INITIALIZER;

Expand Down
14 changes: 8 additions & 6 deletions shell/platform/darwin/macos/framework/Source/FlutterView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

@interface FlutterView () <FlutterSurfaceManagerDelegate> {
int64_t _viewId;
__weak id<FlutterViewReshapeListener> _reshapeListener;
__weak id<FlutterViewDelegate> _viewDelegate;
FlutterThreadSynchronizer* _threadSynchronizer;
FlutterSurfaceManager* _surfaceManager;
}
Expand All @@ -22,7 +22,7 @@ @implementation FlutterView

- (instancetype)initWithMTLDevice:(id<MTLDevice>)device
commandQueue:(id<MTLCommandQueue>)commandQueue
reshapeListener:(id<FlutterViewReshapeListener>)reshapeListener
delegate:(id<FlutterViewDelegate>)delegate
threadSynchronizer:(FlutterThreadSynchronizer*)threadSynchronizer
viewId:(int64_t)viewId {
self = [super initWithFrame:NSZeroRect];
Expand All @@ -31,7 +31,7 @@ - (instancetype)initWithMTLDevice:(id<MTLDevice>)device
[self setBackgroundColor:[NSColor blackColor]];
[self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawDuringViewResize];
_viewId = viewId;
_reshapeListener = reshapeListener;
_viewDelegate = delegate;
_threadSynchronizer = threadSynchronizer;
_surfaceManager = [[FlutterSurfaceManager alloc] initWithDevice:device
commandQueue:commandQueue
Expand All @@ -54,7 +54,7 @@ - (void)reshaped {
[_threadSynchronizer beginResizeForView:_viewId
size:scaledSize
notify:^{
[_reshapeListener viewDidReshape:self];
[_viewDelegate viewDidReshape:self];
}];
}

Expand Down Expand Up @@ -89,7 +89,9 @@ - (BOOL)acceptsFirstMouse:(NSEvent*)event {
}

- (BOOL)acceptsFirstResponder {
return YES;
// This is to ensure that FlutterView does not take first responder status from TextInputPlugin
// on mouse clicks.
return [_viewDelegate viewShouldAcceptFirstResponder:self];
}

- (void)cursorUpdate:(NSEvent*)event {
Expand All @@ -104,7 +106,7 @@ - (void)cursorUpdate:(NSEvent*)event {
- (void)viewDidChangeBackingProperties {
[super viewDidChangeBackingProperties];
// Force redraw
[_reshapeListener viewDidReshape:self];
[_viewDelegate viewDidReshape:self];
}

- (BOOL)layer:(CALayer*)layer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ - (void)setBackgroundColor:(NSColor*)color;
/**
* Private interface declaration for FlutterViewController.
*/
@interface FlutterViewController () <FlutterViewReshapeListener>
@interface FlutterViewController () <FlutterViewDelegate>

/**
* The tracking area used to generate hover events, if enabled.
Expand Down Expand Up @@ -831,7 +831,7 @@ - (nonnull FlutterView*)createFlutterViewWithMTLDevice:(id<MTLDevice>)device
commandQueue:(id<MTLCommandQueue>)commandQueue {
return [[FlutterView alloc] initWithMTLDevice:device
commandQueue:commandQueue
reshapeListener:self
delegate:self
threadSynchronizer:_threadSynchronizer
viewId:_viewId];
}
Expand All @@ -851,15 +851,24 @@ - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package {
return [FlutterDartProject lookupKeyForAsset:asset fromPackage:package];
}

#pragma mark - FlutterViewReshapeListener
#pragma mark - FlutterViewDelegate

/**
* Responds to view reshape by notifying the engine of the change in dimensions.
*/
- (void)viewDidReshape:(NSView*)view {
FML_DCHECK(view == _flutterView);
[_engine updateWindowMetricsForViewController:self];
}

- (BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
FML_DCHECK(view == _flutterView);
// Only allow FlutterView to become first responder if TextInputPlugin is
// not active. Otherwise a mouse event inside FlutterView would cause the
// TextInputPlugin to lose first responder status.
return !_textInputPlugin.isFirstResponder;
}

#pragma mark - FlutterPluginRegistry

- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
Expand Down
12 changes: 8 additions & 4 deletions shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,29 @@

constexpr int64_t kImplicitViewId = 0ll;

@interface TestReshapeListener : NSObject <FlutterViewReshapeListener>
@interface TestFlutterViewDelegate : NSObject <FlutterViewDelegate>

@end

@implementation TestReshapeListener
@implementation TestFlutterViewDelegate

- (void)viewDidReshape:(nonnull NSView*)view {
}

- (BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
return YES;
}

@end

TEST(FlutterView, ShouldInheritContentsScaleReturnsYes) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> queue = [device newCommandQueue];
TestReshapeListener* listener = [[TestReshapeListener alloc] init];
TestFlutterViewDelegate* delegate = [[TestFlutterViewDelegate alloc] init];
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
FlutterView* view = [[FlutterView alloc] initWithMTLDevice:device
commandQueue:queue
reshapeListener:listener
delegate:delegate
threadSynchronizer:threadSynchronizer
viewId:kImplicitViewId];
EXPECT_EQ([view layer:view.layer shouldInheritContentsScale:3.0 fromWindow:view.window], YES);
Expand Down