Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 62cec3c

Browse files
authored
[macOS] FlutterView should not override platform view cursor (#52159)
This fixes an edge case that I've overlooked in #43101. It is possible to configure `FlutterViewController` tracking area with `FlutterMouseTrackingModeAlways` option, in which case the tracking area will call `[FlutterView cursorUpdate:]` unconditionally even if the mouse cursor is over platform view and the platform view has already handled the mouse update. The solution is to prevent the `FlutterView` from updating the cursor when the cursor is over a platform view. This can be accomplished by doing a hitTest inside `[FlutterView cursorUpdate:]`. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
1 parent 25b09e8 commit 62cec3c

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

shell/platform/darwin/macos/framework/Source/FlutterView.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ - (void)didUpdateMouseCursor:(NSCursor*)cursor {
106106
// - When context menu above FlutterView is closed. Context menu will change current cursor to arrow
107107
// and will not restore it back.
108108
- (void)cursorUpdate:(NSEvent*)event {
109+
// Make sure to not override cursor when over a platform view.
110+
NSView* hitTestView = [self hitTest:[self convertPoint:event.locationInWindow fromView:nil]];
111+
if (hitTestView != self) {
112+
return;
113+
}
109114
[_lastCursor set];
110115
// It is possible that there is a platform view with NSTrackingArea below flutter content.
111116
// This could override the mouse cursor as a result of mouse move event. There is no good way

shell/platform/darwin/macos/framework/Source/FlutterViewTest.mm

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,124 @@ - (BOOL)viewShouldAcceptFirstResponder:(NSView*)view {
3737
viewId:kImplicitViewId];
3838
EXPECT_EQ([view layer:view.layer shouldInheritContentsScale:3.0 fromWindow:view.window], YES);
3939
}
40+
41+
@interface TestFlutterView : FlutterView
42+
43+
@property(readwrite, nonatomic) NSView* (^onHitTest)(NSPoint point);
44+
45+
@end
46+
47+
@implementation TestFlutterView
48+
49+
@synthesize onHitTest;
50+
51+
- (NSView*)hitTest:(NSPoint)point {
52+
return self.onHitTest(point);
53+
}
54+
55+
- (void)reshaped {
56+
// Disable resize synchronization for testing.
57+
}
58+
59+
@end
60+
61+
@interface TestCursor : NSCursor
62+
@property(readwrite, nonatomic) BOOL setCalled;
63+
@end
64+
65+
@implementation TestCursor
66+
67+
- (void)set {
68+
self.setCalled = YES;
69+
}
70+
71+
@end
72+
73+
TEST(FlutterView, CursorUpdateDoesHitTest) {
74+
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
75+
id<MTLCommandQueue> queue = [device newCommandQueue];
76+
TestFlutterViewDelegate* delegate = [[TestFlutterViewDelegate alloc] init];
77+
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
78+
TestFlutterView* view = [[TestFlutterView alloc] initWithMTLDevice:device
79+
commandQueue:queue
80+
delegate:delegate
81+
threadSynchronizer:threadSynchronizer
82+
viewId:kImplicitViewId];
83+
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
84+
styleMask:NSBorderlessWindowMask
85+
backing:NSBackingStoreBuffered
86+
defer:NO];
87+
88+
TestCursor* cursor = [[TestCursor alloc] init];
89+
90+
window.contentView = view;
91+
__weak NSView* weakView = view;
92+
__block BOOL hitTestCalled = NO;
93+
__block NSPoint hitTestCoordinate = NSZeroPoint;
94+
view.onHitTest = ^NSView*(NSPoint point) {
95+
hitTestCalled = YES;
96+
hitTestCoordinate = point;
97+
return weakView;
98+
};
99+
NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
100+
location:NSMakePoint(100, 100)
101+
modifierFlags:0
102+
timestamp:0
103+
windowNumber:0
104+
context:nil
105+
eventNumber:0
106+
clickCount:0
107+
pressure:0];
108+
[view didUpdateMouseCursor:cursor];
109+
[view cursorUpdate:mouseEvent];
110+
111+
EXPECT_TRUE(hitTestCalled);
112+
// The hit test coordinate should be in the window coordinate system.
113+
EXPECT_TRUE(CGPointEqualToPoint(hitTestCoordinate, CGPointMake(100, 500)));
114+
EXPECT_TRUE(cursor.setCalled);
115+
}
116+
117+
TEST(FlutterView, CursorUpdateDoesNotOverridePlatformView) {
118+
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
119+
id<MTLCommandQueue> queue = [device newCommandQueue];
120+
TestFlutterViewDelegate* delegate = [[TestFlutterViewDelegate alloc] init];
121+
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
122+
TestFlutterView* view = [[TestFlutterView alloc] initWithMTLDevice:device
123+
commandQueue:queue
124+
delegate:delegate
125+
threadSynchronizer:threadSynchronizer
126+
viewId:kImplicitViewId];
127+
NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
128+
styleMask:NSBorderlessWindowMask
129+
backing:NSBackingStoreBuffered
130+
defer:NO];
131+
132+
TestCursor* cursor = [[TestCursor alloc] init];
133+
134+
NSView* platformView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
135+
136+
window.contentView = view;
137+
__block BOOL hitTestCalled = NO;
138+
__block NSPoint hitTestCoordinate = NSZeroPoint;
139+
view.onHitTest = ^NSView*(NSPoint point) {
140+
hitTestCalled = YES;
141+
hitTestCoordinate = point;
142+
return platformView;
143+
};
144+
NSEvent* mouseEvent = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
145+
location:NSMakePoint(100, 100)
146+
modifierFlags:0
147+
timestamp:0
148+
windowNumber:0
149+
context:nil
150+
eventNumber:0
151+
clickCount:0
152+
pressure:0];
153+
[view didUpdateMouseCursor:cursor];
154+
[view cursorUpdate:mouseEvent];
155+
156+
EXPECT_TRUE(hitTestCalled);
157+
// The hit test coordinate should be in the window coordinate system.
158+
EXPECT_TRUE(CGPointEqualToPoint(hitTestCoordinate, CGPointMake(100, 500)));
159+
EXPECT_FALSE(cursor.setCalled);
160+
}

0 commit comments

Comments
 (0)