Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / views / widget / desktop_aura / desktop_window_tree_host_x11_unittest.cc
blobeda707d5012eece48f4a67c0b6442ee1b6b68247
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <vector>
7 #include <X11/extensions/shape.h>
8 #include <X11/Xlib.h>
10 // Get rid of X11 macros which conflict with gtest.
11 // It is necessary to include this header before the rest so that Bool can be
12 // undefined.
13 #include "ui/events/test/events_test_utils_x11.h"
14 #undef Bool
15 #undef None
17 #include "base/command_line.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/run_loop.h"
20 #include "ui/aura/window.h"
21 #include "ui/aura/window_tree_host.h"
22 #include "ui/base/hit_test.h"
23 #include "ui/base/x/x11_util.h"
24 #include "ui/events/devices/x11/touch_factory_x11.h"
25 #include "ui/events/platform/x11/x11_event_source.h"
26 #include "ui/events/test/platform_event_source_test_api.h"
27 #include "ui/gfx/geometry/point.h"
28 #include "ui/gfx/geometry/rect.h"
29 #include "ui/gfx/path.h"
30 #include "ui/gfx/switches.h"
31 #include "ui/gfx/x/x11_atom_cache.h"
32 #include "ui/views/test/views_test_base.h"
33 #include "ui/views/test/x11_property_change_waiter.h"
34 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
35 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"
36 #include "ui/views/widget/widget_delegate.h"
37 #include "ui/views/window/non_client_view.h"
39 namespace views {
41 namespace {
43 const int kPointerDeviceId = 1;
45 // Blocks till the window state hint, |hint|, is set or unset.
46 class WMStateWaiter : public X11PropertyChangeWaiter {
47 public:
48 WMStateWaiter(XID window,
49 const char* hint,
50 bool wait_till_set)
51 : X11PropertyChangeWaiter(window, "_NET_WM_STATE"),
52 hint_(hint),
53 wait_till_set_(wait_till_set) {
55 const char* kAtomsToCache[] = {
56 hint,
57 NULL
59 atom_cache_.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache));
62 ~WMStateWaiter() override {}
64 private:
65 // X11PropertyChangeWaiter:
66 bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) override {
67 std::vector<Atom> hints;
68 if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) {
69 std::vector<Atom>::iterator it = std::find(
70 hints.begin(),
71 hints.end(),
72 atom_cache_->GetAtom(hint_));
73 bool hint_set = (it != hints.end());
74 return hint_set != wait_till_set_;
76 return true;
79 scoped_ptr<ui::X11AtomCache> atom_cache_;
81 // The name of the hint to wait to get set or unset.
82 const char* hint_;
84 // Whether we are waiting for |hint| to be set or unset.
85 bool wait_till_set_;
87 DISALLOW_COPY_AND_ASSIGN(WMStateWaiter);
90 // A NonClientFrameView with a window mask with the bottom right corner cut out.
91 class ShapedNonClientFrameView : public NonClientFrameView {
92 public:
93 explicit ShapedNonClientFrameView() {
96 ~ShapedNonClientFrameView() override {}
98 // NonClientFrameView:
99 gfx::Rect GetBoundsForClientView() const override { return bounds(); }
100 gfx::Rect GetWindowBoundsForClientBounds(
101 const gfx::Rect& client_bounds) const override {
102 return client_bounds;
104 int NonClientHitTest(const gfx::Point& point) override { return HTNOWHERE; }
105 void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) override {
106 int right = size.width();
107 int bottom = size.height();
109 window_mask->moveTo(0, 0);
110 window_mask->lineTo(0, bottom);
111 window_mask->lineTo(right, bottom);
112 window_mask->lineTo(right, 10);
113 window_mask->lineTo(right - 10, 10);
114 window_mask->lineTo(right - 10, 0);
115 window_mask->close();
117 void ResetWindowControls() override {}
118 void UpdateWindowIcon() override {}
119 void UpdateWindowTitle() override {}
120 void SizeConstraintsChanged() override {}
122 private:
123 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView);
126 class ShapedWidgetDelegate : public WidgetDelegateView {
127 public:
128 ShapedWidgetDelegate() {
131 ~ShapedWidgetDelegate() override {}
133 // WidgetDelegateView:
134 NonClientFrameView* CreateNonClientFrameView(Widget* widget) override {
135 return new ShapedNonClientFrameView;
138 private:
139 DISALLOW_COPY_AND_ASSIGN(ShapedWidgetDelegate);
142 // Creates a widget of size 100x100.
143 scoped_ptr<Widget> CreateWidget(WidgetDelegate* delegate) {
144 scoped_ptr<Widget> widget(new Widget);
145 Widget::InitParams params(Widget::InitParams::TYPE_WINDOW);
146 params.delegate = delegate;
147 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
148 params.remove_standard_frame = true;
149 params.native_widget = new DesktopNativeWidgetAura(widget.get());
150 params.bounds = gfx::Rect(100, 100, 100, 100);
151 widget->Init(params);
152 return widget.Pass();
155 // Returns the list of rectangles which describe |xid|'s bounding region via the
156 // X shape extension.
157 std::vector<gfx::Rect> GetShapeRects(XID xid) {
158 int dummy;
159 int shape_rects_size;
160 gfx::XScopedPtr<XRectangle[]> shape_rects(XShapeGetRectangles(
161 gfx::GetXDisplay(), xid, ShapeBounding, &shape_rects_size, &dummy));
163 std::vector<gfx::Rect> shape_vector;
164 for (int i = 0; i < shape_rects_size; ++i) {
165 const XRectangle& rect = shape_rects[i];
166 shape_vector.push_back(gfx::Rect(rect.x, rect.y, rect.width, rect.height));
168 return shape_vector;
171 // Returns true if one of |rects| contains point (x,y).
172 bool ShapeRectContainsPoint(const std::vector<gfx::Rect>& shape_rects,
173 int x,
174 int y) {
175 gfx::Point point(x, y);
176 for (size_t i = 0; i < shape_rects.size(); ++i) {
177 if (shape_rects[i].Contains(point))
178 return true;
180 return false;
183 // Flush the message loop.
184 void RunAllPendingInMessageLoop() {
185 base::RunLoop run_loop;
186 run_loop.RunUntilIdle();
189 } // namespace
191 class DesktopWindowTreeHostX11Test : public ViewsTestBase {
192 public:
193 DesktopWindowTreeHostX11Test() {
195 ~DesktopWindowTreeHostX11Test() override {}
197 void SetUp() override {
198 ViewsTestBase::SetUp();
200 // Make X11 synchronous for our display connection. This does not force the
201 // window manager to behave synchronously.
202 XSynchronize(gfx::GetXDisplay(), True);
205 void TearDown() override {
206 XSynchronize(gfx::GetXDisplay(), False);
207 ViewsTestBase::TearDown();
210 private:
211 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11Test);
214 // Tests that the shape is properly set on the x window.
215 TEST_F(DesktopWindowTreeHostX11Test, Shape) {
216 if (!ui::IsShapeExtensionAvailable())
217 return;
219 // 1) Test setting the window shape via the NonClientFrameView. This technique
220 // is used to get rounded corners on Chrome windows when not using the native
221 // window frame.
222 scoped_ptr<Widget> widget1 = CreateWidget(new ShapedWidgetDelegate());
223 widget1->Show();
224 ui::X11EventSource::GetInstance()->DispatchXEvents();
226 XID xid1 = widget1->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
227 std::vector<gfx::Rect> shape_rects = GetShapeRects(xid1);
228 ASSERT_FALSE(shape_rects.empty());
230 // The widget was supposed to be 100x100, but the WM might have ignored this
231 // suggestion.
232 int widget_width = widget1->GetWindowBoundsInScreen().width();
233 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 15, 5));
234 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 5));
235 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, widget_width - 5, 15));
236 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, widget_width + 5, 15));
238 // Changing widget's size should update the shape.
239 widget1->SetBounds(gfx::Rect(100, 100, 200, 200));
240 ui::X11EventSource::GetInstance()->DispatchXEvents();
242 if (widget1->GetWindowBoundsInScreen().width() == 200) {
243 shape_rects = GetShapeRects(xid1);
244 ASSERT_FALSE(shape_rects.empty());
245 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 85, 5));
246 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 5));
247 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 185, 5));
248 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 195, 5));
249 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 195, 15));
250 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 205, 15));
253 if (ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"))) {
254 // The shape should be changed to a rectangle which fills the entire screen
255 // when |widget1| is maximized.
257 WMStateWaiter waiter(xid1, "_NET_WM_STATE_MAXIMIZED_VERT", true);
258 widget1->Maximize();
259 waiter.Wait();
262 // Ensure that the task which is posted when a window is resized is run.
263 RunAllPendingInMessageLoop();
265 // xvfb does not support Xrandr so we cannot check the maximized window's
266 // bounds.
267 gfx::Rect maximized_bounds;
268 ui::GetOuterWindowBounds(xid1, &maximized_bounds);
270 shape_rects = GetShapeRects(xid1);
271 ASSERT_FALSE(shape_rects.empty());
272 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
273 maximized_bounds.width() - 1,
274 5));
275 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects,
276 maximized_bounds.width() - 1,
277 15));
280 // 2) Test setting the window shape via Widget::SetShape().
281 gfx::Path shape2;
282 shape2.moveTo(10, 0);
283 shape2.lineTo(10, 10);
284 shape2.lineTo(0, 10);
285 shape2.lineTo(0, 100);
286 shape2.lineTo(100, 100);
287 shape2.lineTo(100, 0);
288 shape2.close();
290 SkRegion* shape_region = new SkRegion;
291 shape_region->setPath(shape2, SkRegion(shape2.getBounds().round()));
293 scoped_ptr<Widget> widget2(CreateWidget(NULL));
294 widget2->Show();
295 widget2->SetShape(shape_region);
296 ui::X11EventSource::GetInstance()->DispatchXEvents();
298 XID xid2 = widget2->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
299 shape_rects = GetShapeRects(xid2);
300 ASSERT_FALSE(shape_rects.empty());
301 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
302 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
303 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
304 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
306 // Changing the widget's size should not affect the shape.
307 widget2->SetBounds(gfx::Rect(100, 100, 200, 200));
308 shape_rects = GetShapeRects(xid2);
309 ASSERT_FALSE(shape_rects.empty());
310 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 5, 5));
311 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
312 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
313 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 105, 15));
315 // Setting the shape to NULL resets the shape back to the entire
316 // window bounds.
317 widget2->SetShape(NULL);
318 shape_rects = GetShapeRects(xid2);
319 ASSERT_FALSE(shape_rects.empty());
320 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 5, 5));
321 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 15, 5));
322 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 95, 15));
323 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects, 105, 15));
324 EXPECT_FALSE(ShapeRectContainsPoint(shape_rects, 500, 500));
327 // Test that the widget ignores changes in fullscreen state initiated by the
328 // window manager (e.g. via a window manager accelerator key).
329 TEST_F(DesktopWindowTreeHostX11Test, WindowManagerTogglesFullscreen) {
330 if (!ui::WmSupportsHint(ui::GetAtom("_NET_WM_STATE_FULLSCREEN")))
331 return;
333 scoped_ptr<Widget> widget = CreateWidget(new ShapedWidgetDelegate());
334 XID xid = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
335 widget->Show();
336 ui::X11EventSource::GetInstance()->DispatchXEvents();
338 gfx::Rect initial_bounds = widget->GetWindowBoundsInScreen();
340 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", true);
341 widget->SetFullscreen(true);
342 waiter.Wait();
344 EXPECT_TRUE(widget->IsFullscreen());
346 // Emulate the window manager exiting fullscreen via a window manager
347 // accelerator key. It should not affect the widget's fullscreen state.
349 const char* kAtomsToCache[] = {
350 "_NET_WM_STATE",
351 "_NET_WM_STATE_FULLSCREEN",
352 NULL
354 Display* display = gfx::GetXDisplay();
355 ui::X11AtomCache atom_cache(display, kAtomsToCache);
357 XEvent xclient;
358 memset(&xclient, 0, sizeof(xclient));
359 xclient.type = ClientMessage;
360 xclient.xclient.window = xid;
361 xclient.xclient.message_type = atom_cache.GetAtom("_NET_WM_STATE");
362 xclient.xclient.format = 32;
363 xclient.xclient.data.l[0] = 0;
364 xclient.xclient.data.l[1] = atom_cache.GetAtom("_NET_WM_STATE_FULLSCREEN");
365 xclient.xclient.data.l[2] = 0;
366 xclient.xclient.data.l[3] = 1;
367 xclient.xclient.data.l[4] = 0;
368 XSendEvent(display, DefaultRootWindow(display), False,
369 SubstructureRedirectMask | SubstructureNotifyMask,
370 &xclient);
372 WMStateWaiter waiter(xid, "_NET_WM_STATE_FULLSCREEN", false);
373 waiter.Wait();
375 EXPECT_TRUE(widget->IsFullscreen());
377 // Calling Widget::SetFullscreen(false) should clear the widget's fullscreen
378 // state and clean things up.
379 widget->SetFullscreen(false);
380 EXPECT_FALSE(widget->IsFullscreen());
381 EXPECT_EQ(initial_bounds.ToString(),
382 widget->GetWindowBoundsInScreen().ToString());
385 // Tests that the minimization information is propagated to the content window.
386 TEST_F(DesktopWindowTreeHostX11Test, ToggleMinimizePropogateToContentWindow) {
387 Widget widget;
388 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
389 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
390 params.native_widget = new DesktopNativeWidgetAura(&widget);
391 widget.Init(params);
392 widget.Show();
393 ui::X11EventSource::GetInstance()->DispatchXEvents();
395 XID xid = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
396 Display* display = gfx::GetXDisplay();
398 // Minimize by sending _NET_WM_STATE_HIDDEN
400 const char* kAtomsToCache[] = {
401 "_NET_WM_STATE",
402 "_NET_WM_STATE_HIDDEN",
403 NULL
406 ui::X11AtomCache atom_cache(display, kAtomsToCache);
408 std::vector< ::Atom> atom_list;
409 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_HIDDEN"));
410 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
412 XEvent xevent;
413 memset(&xevent, 0, sizeof(xevent));
414 xevent.type = PropertyNotify;
415 xevent.xproperty.type = PropertyNotify;
416 xevent.xproperty.send_event = 1;
417 xevent.xproperty.display = display;
418 xevent.xproperty.window = xid;
419 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
420 xevent.xproperty.state = 0;
421 XSendEvent(display, DefaultRootWindow(display), False,
422 SubstructureRedirectMask | SubstructureNotifyMask,
423 &xevent);
425 WMStateWaiter waiter(xid, "_NET_WM_STATE_HIDDEN", true);
426 waiter.Wait();
428 EXPECT_FALSE(widget.GetNativeWindow()->IsVisible());
430 // Show from minimized by sending _NET_WM_STATE_FOCUSED
432 const char* kAtomsToCache[] = {
433 "_NET_WM_STATE",
434 "_NET_WM_STATE_FOCUSED",
435 NULL
438 ui::X11AtomCache atom_cache(display, kAtomsToCache);
440 std::vector< ::Atom> atom_list;
441 atom_list.push_back(atom_cache.GetAtom("_NET_WM_STATE_FOCUSED"));
442 ui::SetAtomArrayProperty(xid, "_NET_WM_STATE", "ATOM", atom_list);
444 XEvent xevent;
445 memset(&xevent, 0, sizeof(xevent));
446 xevent.type = PropertyNotify;
447 xevent.xproperty.type = PropertyNotify;
448 xevent.xproperty.send_event = 1;
449 xevent.xproperty.display = display;
450 xevent.xproperty.window = xid;
451 xevent.xproperty.atom = atom_cache.GetAtom("_NET_WM_STATE");
452 xevent.xproperty.state = 0;
453 XSendEvent(display, DefaultRootWindow(display), False,
454 SubstructureRedirectMask | SubstructureNotifyMask,
455 &xevent);
457 WMStateWaiter waiter(xid, "_NET_WM_STATE_FOCUSED", true);
458 waiter.Wait();
460 EXPECT_TRUE(widget.GetNativeWindow()->IsVisible());
463 TEST_F(DesktopWindowTreeHostX11Test, ChildWindowDestructionDuringTearDown) {
464 Widget parent_widget;
465 Widget::InitParams parent_params =
466 CreateParams(Widget::InitParams::TYPE_WINDOW);
467 parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
468 parent_params.native_widget = new DesktopNativeWidgetAura(&parent_widget);
469 parent_widget.Init(parent_params);
470 parent_widget.Show();
471 ui::X11EventSource::GetInstance()->DispatchXEvents();
473 Widget child_widget;
474 Widget::InitParams child_params =
475 CreateParams(Widget::InitParams::TYPE_WINDOW);
476 child_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
477 child_params.native_widget = new DesktopNativeWidgetAura(&child_widget);
478 child_params.parent = parent_widget.GetNativeWindow();
479 child_widget.Init(child_params);
480 child_widget.Show();
481 ui::X11EventSource::GetInstance()->DispatchXEvents();
483 // Sanity check that the two widgets each have their own XID.
484 ASSERT_NE(parent_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget(),
485 child_widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget());
486 Widget::CloseAllSecondaryWidgets();
487 EXPECT_TRUE(DesktopWindowTreeHostX11::GetAllOpenWindows().empty());
490 class MouseEventRecorder : public ui::EventHandler {
491 public:
492 MouseEventRecorder() {}
493 ~MouseEventRecorder() override {}
495 void Reset() { mouse_events_.clear(); }
497 const std::vector<ui::MouseEvent>& mouse_events() const {
498 return mouse_events_;
501 private:
502 // ui::EventHandler:
503 void OnMouseEvent(ui::MouseEvent* mouse) override {
504 mouse_events_.push_back(*mouse);
507 std::vector<ui::MouseEvent> mouse_events_;
509 DISALLOW_COPY_AND_ASSIGN(MouseEventRecorder);
512 class DesktopWindowTreeHostX11HighDPITest
513 : public DesktopWindowTreeHostX11Test {
514 public:
515 DesktopWindowTreeHostX11HighDPITest()
516 : event_source_(ui::PlatformEventSource::GetInstance()) {}
517 ~DesktopWindowTreeHostX11HighDPITest() override {}
519 void DispatchSingleEventToWidget(XEvent* event, Widget* widget) {
520 DCHECK_EQ(GenericEvent, event->type);
521 XIDeviceEvent* device_event =
522 static_cast<XIDeviceEvent*>(event->xcookie.data);
523 device_event->event =
524 widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
525 event_source_.Dispatch(event);
528 void PretendCapture(views::Widget* capture_widget) {
529 DesktopWindowTreeHostX11* capture_host = nullptr;
530 if (capture_widget) {
531 capture_host = static_cast<DesktopWindowTreeHostX11*>(
532 capture_widget->GetNativeWindow()->GetHost());
534 DesktopWindowTreeHostX11::g_current_capture = capture_host;
535 if (capture_widget)
536 capture_widget->GetNativeWindow()->SetCapture();
539 private:
540 void SetUp() override {
541 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
542 command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor, "2");
543 std::vector<int> pointer_devices;
544 pointer_devices.push_back(kPointerDeviceId);
545 ui::TouchFactory::GetInstance()->SetPointerDeviceForTest(pointer_devices);
547 DesktopWindowTreeHostX11Test::SetUp();
550 ui::test::PlatformEventSourceTestAPI event_source_;
551 DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostX11HighDPITest);
554 TEST_F(DesktopWindowTreeHostX11HighDPITest, LocatedEventDispatchWithCapture) {
555 Widget first;
556 Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
557 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
558 params.native_widget = new DesktopNativeWidgetAura(&first);
559 params.bounds = gfx::Rect(0, 0, 50, 50);
560 first.Init(params);
561 first.Show();
563 Widget second;
564 params = CreateParams(Widget::InitParams::TYPE_WINDOW);
565 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
566 params.native_widget = new DesktopNativeWidgetAura(&second);
567 params.bounds = gfx::Rect(50, 50, 50, 50);
568 second.Init(params);
569 second.Show();
571 ui::X11EventSource::GetInstance()->DispatchXEvents();
573 MouseEventRecorder first_recorder, second_recorder;
574 first.GetNativeWindow()->AddPreTargetHandler(&first_recorder);
575 second.GetNativeWindow()->AddPreTargetHandler(&second_recorder);
577 // Dispatch an event on |first|. Verify it gets the event.
578 ui::ScopedXI2Event event;
579 event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSEWHEEL,
580 gfx::Point(50, 50), ui::EF_NONE);
581 DispatchSingleEventToWidget(event, &first);
582 ASSERT_EQ(1u, first_recorder.mouse_events().size());
583 EXPECT_EQ(ui::ET_MOUSEWHEEL, first_recorder.mouse_events()[0].type());
584 EXPECT_EQ(gfx::Point(25, 25).ToString(),
585 first_recorder.mouse_events()[0].location().ToString());
586 ASSERT_EQ(0u, second_recorder.mouse_events().size());
588 first_recorder.Reset();
589 second_recorder.Reset();
591 // Set a capture on |second|, and dispatch the same event to |first|. This
592 // event should reach |second| instead.
593 PretendCapture(&second);
594 event.InitGenericButtonEvent(kPointerDeviceId, ui::ET_MOUSEWHEEL,
595 gfx::Point(50, 50), ui::EF_NONE);
596 DispatchSingleEventToWidget(event, &first);
598 ASSERT_EQ(0u, first_recorder.mouse_events().size());
599 ASSERT_EQ(1u, second_recorder.mouse_events().size());
600 EXPECT_EQ(ui::ET_MOUSEWHEEL, second_recorder.mouse_events()[0].type());
601 EXPECT_EQ(gfx::Point(-25, -25).ToString(),
602 second_recorder.mouse_events()[0].location().ToString());
604 PretendCapture(nullptr);
605 first.GetNativeWindow()->RemovePreTargetHandler(&first_recorder);
606 second.GetNativeWindow()->RemovePreTargetHandler(&second_recorder);
609 } // namespace views