Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[macOS] Forward mouseDown/Up to view controller #40241

Merged
merged 1 commit into from
Mar 11, 2023
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 @@ -278,6 +278,34 @@ - (NSArray*)accessibilityChildren {
return @[ _flutterView ];
}

- (void)mouseDown:(NSEvent*)event {
// Work around an AppKit bug where mouseDown/mouseUp are not called on the view controller if the
// view is the content view of an NSPopover AND macOS's Reduced Transparency accessibility setting
// is enabled.
//
// This simply calls mouseDown on the next responder in the responder chain as the default
// implementation on NSResponder is documented to do.
//
// See: https://github.com/flutter/flutter/issues/115015
// See: http://www.openradar.me/FB12050037
// See: https://developer.apple.com/documentation/appkit/nsresponder/1524634-mousedown
[self.nextResponder mouseDown:event];
}

- (void)mouseUp:(NSEvent*)event {
// Work around an AppKit bug where mouseDown/mouseUp are not called on the view controller if the
// view is the content view of an NSPopover AND macOS's Reduced Transparency accessibility setting
// is enabled.
//
// This simply calls mouseUp on the next responder in the responder chain as the default
// implementation on NSResponder is documented to do.
//
// See: https://github.com/flutter/flutter/issues/115015
// See: http://www.openradar.me/FB12050037
// See: https://developer.apple.com/documentation/appkit/nsresponder/1535349-mouseup
[self.nextResponder mouseUp:event];
}

@end

#pragma mark - FlutterViewController implementation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
#import "flutter/testing/testing.h"

#pragma mark - Test Helper Classes

// A wrap to convert FlutterKeyEvent to a ObjC class.
@interface KeyEventWrapper : NSObject
@property(nonatomic) FlutterKeyEvent* data;
Expand All @@ -36,13 +38,31 @@ - (void)dealloc {
}
@end

// A FlutterViewController subclass for testing that mouseDown/mouseUp get called when
// mouse events are sent to the associated view.
@interface MouseEventFlutterViewController : FlutterViewController
@property(nonatomic, assign) BOOL mouseDownCalled;
@property(nonatomic, assign) BOOL mouseUpCalled;
@end

@implementation MouseEventFlutterViewController
- (void)mouseDown:(NSEvent*)event {
self.mouseDownCalled = YES;
}

- (void)mouseUp:(NSEvent*)event {
self.mouseUpCalled = YES;
}
@end

@interface FlutterViewControllerTestObjC : NSObject
- (bool)testKeyEventsAreSentToFramework;
- (bool)testKeyEventsArePropagatedIfNotHandled;
- (bool)testKeyEventsAreNotPropagatedIfHandled;
- (bool)testFlagsChangedEventsArePropagatedIfNotHandled;
- (bool)testKeyboardIsRestartedOnEngineRestart;
- (bool)testTrackpadGesturesAreSentToFramework;
- (bool)testMouseDownUpEventsSentToNextResponder;
- (bool)testModifierKeysAreSynthesizedOnMouseMove;
- (bool)testViewWillAppearCalledMultipleTimes;
- (bool)testFlutterViewIsConfigured;
Expand All @@ -52,6 +72,8 @@ + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
userData:(nullable void*)userData;
@end

#pragma mark - Static helper functions

using namespace ::flutter::testing::keycodes;

namespace flutter::testing {
Expand Down Expand Up @@ -108,6 +130,8 @@ id MockGestureEvent(NSEventType type, NSEventPhase phase, double magnification,

} // namespace

#pragma mark - gtest tests

TEST(FlutterViewController, HasViewThatHidesOtherViewsInAccessibility) {
FlutterViewController* viewControllerMock = CreateMockViewController();

Expand Down Expand Up @@ -196,6 +220,10 @@ id MockGestureEvent(NSEventType type, NSEventPhase phase, double magnification,
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testTrackpadGesturesAreSentToFramework]);
}

TEST(FlutterViewControllerTest, TestMouseDownUpEventsSentToNextResponder) {
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testMouseDownUpEventsSentToNextResponder]);
}

TEST(FlutterViewControllerTest, TestModifierKeysAreSynthesizedOnMouseMove) {
ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testModifierKeysAreSynthesizedOnMouseMove]);
}
Expand All @@ -210,6 +238,8 @@ id MockGestureEvent(NSEventType type, NSEventPhase phase, double magnification,

} // namespace flutter::testing

#pragma mark - FlutterViewControllerTestObjC

@implementation FlutterViewControllerTestObjC

- (bool)testKeyEventsAreSentToFramework {
Expand Down Expand Up @@ -802,6 +832,51 @@ - (bool)testViewWillAppearCalledMultipleTimes {
return true;
}

static void SwizzledNoop(id self, SEL _cmd) {}

// Verify workaround an AppKit bug where mouseDown/mouseUp are not called on the view controller if
// the view is the content view of an NSPopover AND macOS's Reduced Transparency accessibility
// setting is enabled.
//
// See: https://github.com/flutter/flutter/issues/115015
// See: http://www.openradar.me/FB12050037
// See: https://developer.apple.com/documentation/appkit/nsresponder/1524634-mousedown
- (bool)testMouseDownUpEventsSentToNextResponder {
// The root cause of the above bug is NSResponder mouseDown/mouseUp methods that don't correctly
// walk the responder chain calling the appropriate method on the next responder under certain
// conditions. Simulate this by swizzling out the default implementations and replacing them with
// no-ops.
Method mouseDown = class_getInstanceMethod([NSResponder class], @selector(mouseDown:));
Method mouseUp = class_getInstanceMethod([NSResponder class], @selector(mouseUp:));
IMP noopImp = (IMP)SwizzledNoop;
IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
IMP origMouseUp = method_setImplementation(mouseUp, noopImp);

// Verify that mouseDown/mouseUp trigger mouseDown/mouseUp calls on FlutterViewController.
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
MouseEventFlutterViewController* viewController =
[[MouseEventFlutterViewController alloc] initWithEngine:engineMock nibName:@"" bundle:nil];
FlutterView* view = (FlutterView*)[viewController view];

EXPECT_FALSE(viewController.mouseDownCalled);
EXPECT_FALSE(viewController.mouseUpCalled);

NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
[view mouseDown:mouseEvent];
EXPECT_TRUE(viewController.mouseDownCalled);
EXPECT_FALSE(viewController.mouseUpCalled);

viewController.mouseDownCalled = NO;
[view mouseUp:mouseEvent];
EXPECT_FALSE(viewController.mouseDownCalled);
EXPECT_TRUE(viewController.mouseUpCalled);

// Restore the original NSResponder mouseDown/mouseUp implementations.
method_setImplementation(mouseDown, origMouseDown);
method_setImplementation(mouseUp, origMouseUp);
return true;
}

- (bool)testModifierKeysAreSynthesizedOnMouseMove {
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
// Need to return a real renderer to allow view controller to load.
Expand Down