Index: ui/views/cocoa/bridged_native_widget_interactive_uitest.mm |
diff --git a/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm b/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm |
index b0962fb9a27116e7ee142f1f5933445a873b23ff..f7c4329f176d9cfec32e52da974ed8ed3528e85d 100644 |
--- a/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm |
+++ b/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm |
@@ -9,9 +9,14 @@ |
#import "base/mac/mac_util.h" |
#import "base/mac/sdk_forward_declarations.h" |
#import "ui/base/test/nswindow_fullscreen_notification_waiter.h" |
+#include "ui/base/hit_test.h" |
+#import "ui/base/test/windowed_nsnotification_observer.h" |
+#import "ui/events/test/cocoa_test_event_utils.h" |
#include "ui/views/test/widget_test.h" |
+#include "ui/views/window/native_frame_view.h" |
namespace views { |
+namespace test { |
class BridgedNativeWidgetUITest : public test::WidgetTest { |
public: |
@@ -144,4 +149,191 @@ TEST_F(BridgedNativeWidgetUITest, FullscreenEnterAndExit) { |
EXPECT_EQ(restored_bounds, widget_->GetRestoredBounds()); |
} |
+namespace { |
+ |
+// This is used to wait for reposted events to be seen. We can't just use |
+// RunPendingMessages() because CGEventPost might not be synchronous. |
+class HitTestBridgedNativeWidget : public BridgedNativeWidget { |
+ public: |
+ explicit HitTestBridgedNativeWidget(NativeWidgetMac* widget) |
+ : BridgedNativeWidget(widget) {} |
+ |
+ // BridgedNativeWidget: |
+ bool ShouldRepostPendingLeftMouseDown(NSPoint location_in_window) override { |
+ bool draggable_before = [ns_view() mouseDownCanMoveWindow]; |
+ bool should_repost = BridgedNativeWidget::ShouldRepostPendingLeftMouseDown( |
+ location_in_window); |
+ bool draggable_after = [ns_view() mouseDownCanMoveWindow]; |
+ |
+ if (run_loop_ && draggable_before && !draggable_after) |
+ run_loop_->Quit(); |
+ |
+ return should_repost; |
+ } |
+ |
+ void WaitForIsDraggableChange() { |
+ base::RunLoop run_loop; |
+ run_loop_ = &run_loop; |
+ run_loop.Run(); |
+ run_loop_ = nullptr; |
+ } |
+ |
+ private: |
+ base::RunLoop* run_loop_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(HitTestBridgedNativeWidget); |
+}; |
+ |
+// This is used to return a customized result to NonClientHitTest. |
+class HitTestNonClientFrameView : public NativeFrameView { |
+ public: |
+ explicit HitTestNonClientFrameView(Widget* widget) |
+ : NativeFrameView(widget), hit_test_result_(HTNOWHERE) {} |
+ |
+ // NonClientFrameView overrides: |
+ int NonClientHitTest(const gfx::Point& point) override { |
+ return hit_test_result_; |
+ } |
+ |
+ void set_hit_test_result(int component) { hit_test_result_ = component; } |
+ |
+ private: |
+ int hit_test_result_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(HitTestNonClientFrameView); |
+}; |
+ |
+void WaitForEvent(NSUInteger mask) { |
+ // Pointer because the handler block captures local variables by copying. |
+ base::RunLoop run_loop; |
+ base::RunLoop* run_loop_ref = &run_loop; |
+ id monitor = [NSEvent |
+ addLocalMonitorForEventsMatchingMask:mask |
+ handler:^NSEvent*(NSEvent* ns_event) { |
+ run_loop_ref->Quit(); |
+ return ns_event; |
+ }]; |
+ run_loop.Run(); |
+ [NSEvent removeMonitor:monitor]; |
+} |
+ |
+} // namespace |
+ |
+// This is used to inject test versions of NativeFrameView and |
+// BridgedNativeWidget. |
+class HitTestNativeWidgetMac : public NativeWidgetMac { |
+ public: |
+ HitTestNativeWidgetMac(internal::NativeWidgetDelegate* delegate, |
+ NativeFrameView* native_frame_view) |
+ : NativeWidgetMac(delegate), native_frame_view_(native_frame_view) { |
+ NativeWidgetMac::bridge_.reset(new HitTestBridgedNativeWidget(this)); |
+ } |
+ |
+ HitTestBridgedNativeWidget* bridge() { |
+ return static_cast<HitTestBridgedNativeWidget*>( |
+ NativeWidgetMac::bridge_.get()); |
+ } |
+ |
+ // internal::NativeWidgetPrivate: |
+ NonClientFrameView* CreateNonClientFrameView() override { |
+ return native_frame_view_; |
+ } |
+ |
+ private: |
+ // Owned by Widget. |
+ NativeFrameView* native_frame_view_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(HitTestNativeWidgetMac); |
+}; |
+ |
+TEST_F(BridgedNativeWidgetUITest, HitTest) { |
+ Widget widget; |
+ HitTestNonClientFrameView* frame_view = |
+ new HitTestNonClientFrameView(&widget); |
+ test::HitTestNativeWidgetMac* native_widget = |
+ new test::HitTestNativeWidgetMac(&widget, frame_view); |
+ Widget::InitParams init_params = |
+ CreateParams(Widget::InitParams::TYPE_WINDOW); |
+ init_params.native_widget = native_widget; |
+ init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
+ init_params.bounds = gfx::Rect(100, 200, 400, 300); |
+ widget.Init(init_params); |
+ widget.Show(); |
+ |
+ // Dragging the window should work. |
+ frame_view->set_hit_test_result(HTCAPTION); |
+ { |
+ EXPECT_EQ(100, [widget.GetNativeWindow() frame].origin.x); |
+ |
+ NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow( |
+ NSMakePoint(10, 10), widget.GetNativeWindow()); |
+ CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]); |
+ native_widget->bridge()->WaitForIsDraggableChange(); |
+ |
+ base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer( |
+ [[WindowedNSNotificationObserver alloc] |
+ initForNotification:NSWindowDidMoveNotification]); |
+ NSEvent* mouse_drag = cocoa_test_event_utils::MouseEventAtPointInWindow( |
+ NSMakePoint(110, 110), NSLeftMouseDragged, widget.GetNativeWindow(), 0); |
+ CGEventPost(kCGSessionEventTap, [mouse_drag CGEvent]); |
+ [ns_observer wait]; |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ |
+ NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow( |
+ NSMakePoint(10, 10), NSLeftMouseUp, widget.GetNativeWindow(), 0); |
+ CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]); |
+ WaitForEvent(NSLeftMouseUpMask); |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ } |
+ |
+ // Mouse-downs on the window controls should not be intercepted. |
+ { |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ |
+ base::scoped_nsobject<WindowedNSNotificationObserver> ns_observer( |
+ [[WindowedNSNotificationObserver alloc] |
+ initForNotification:NSWindowDidMiniaturizeNotification]); |
+ |
+ // Position this on the minimize button. |
+ NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow( |
+ NSMakePoint(30, 290), widget.GetNativeWindow()); |
+ CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]); |
+ |
+ NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow( |
+ NSMakePoint(30, 290), NSLeftMouseUp, widget.GetNativeWindow(), 0); |
+ EXPECT_FALSE([widget.GetNativeWindow() isMiniaturized]); |
+ CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]); |
+ [ns_observer wait]; |
+ EXPECT_TRUE([widget.GetNativeWindow() isMiniaturized]); |
+ [widget.GetNativeWindow() deminiaturize:nil]; |
+ |
+ // Position unchanged. |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ } |
+ |
+ // Non-draggable areas should do nothing. |
+ frame_view->set_hit_test_result(HTCLIENT); |
+ { |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ |
+ NSEvent* mouse_down = cocoa_test_event_utils::LeftMouseDownAtPointInWindow( |
+ NSMakePoint(10, 10), widget.GetNativeWindow()); |
+ CGEventPost(kCGSessionEventTap, [mouse_down CGEvent]); |
+ WaitForEvent(NSLeftMouseDownMask); |
+ |
+ NSEvent* mouse_drag = cocoa_test_event_utils::MouseEventAtPointInWindow( |
+ NSMakePoint(110, 110), NSLeftMouseDragged, widget.GetNativeWindow(), 0); |
+ CGEventPost(kCGSessionEventTap, [mouse_drag CGEvent]); |
+ WaitForEvent(NSLeftMouseDraggedMask); |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ |
+ NSEvent* mouse_up = cocoa_test_event_utils::MouseEventAtPointInWindow( |
+ NSMakePoint(110, 110), NSLeftMouseUp, widget.GetNativeWindow(), 0); |
+ CGEventPost(kCGSessionEventTap, [mouse_up CGEvent]); |
+ WaitForEvent(NSLeftMouseUpMask); |
+ EXPECT_EQ(200, [widget.GetNativeWindow() frame].origin.x); |
+ } |
+} |
+ |
+} // namespace test |
} // namespace views |