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.
7 #include <X11/extensions/shape.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
13 #include "ui/events/test/events_test_utils_x11.h"
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"
43 const int kPointerDeviceId
= 1;
45 // Blocks till the window state hint, |hint|, is set or unset.
46 class WMStateWaiter
: public X11PropertyChangeWaiter
{
48 WMStateWaiter(XID window
,
51 : X11PropertyChangeWaiter(window
, "_NET_WM_STATE"),
53 wait_till_set_(wait_till_set
) {
55 const char* kAtomsToCache
[] = {
59 atom_cache_
.reset(new ui::X11AtomCache(gfx::GetXDisplay(), kAtomsToCache
));
62 ~WMStateWaiter() override
{}
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(
72 atom_cache_
->GetAtom(hint_
));
73 bool hint_set
= (it
!= hints
.end());
74 return hint_set
!= wait_till_set_
;
79 scoped_ptr
<ui::X11AtomCache
> atom_cache_
;
81 // The name of the hint to wait to get set or unset.
84 // Whether we are waiting for |hint| to be set or unset.
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
{
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
{}
123 DISALLOW_COPY_AND_ASSIGN(ShapedNonClientFrameView
);
126 class ShapedWidgetDelegate
: public WidgetDelegateView
{
128 ShapedWidgetDelegate() {
131 ~ShapedWidgetDelegate() override
{}
133 // WidgetDelegateView:
134 NonClientFrameView
* CreateNonClientFrameView(Widget
* widget
) override
{
135 return new ShapedNonClientFrameView
;
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
) {
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
));
171 // Returns true if one of |rects| contains point (x,y).
172 bool ShapeRectContainsPoint(const std::vector
<gfx::Rect
>& shape_rects
,
175 gfx::Point
point(x
, y
);
176 for (size_t i
= 0; i
< shape_rects
.size(); ++i
) {
177 if (shape_rects
[i
].Contains(point
))
183 // Flush the message loop.
184 void RunAllPendingInMessageLoop() {
185 base::RunLoop run_loop
;
186 run_loop
.RunUntilIdle();
191 class DesktopWindowTreeHostX11Test
: public ViewsTestBase
{
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();
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())
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
222 scoped_ptr
<Widget
> widget1
= CreateWidget(new ShapedWidgetDelegate());
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
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);
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
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,
275 EXPECT_TRUE(ShapeRectContainsPoint(shape_rects
,
276 maximized_bounds
.width() - 1,
280 // 2) Test setting the window shape via Widget::SetShape().
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);
290 SkRegion
* shape_region
= new SkRegion
;
291 shape_region
->setPath(shape2
, SkRegion(shape2
.getBounds().round()));
293 scoped_ptr
<Widget
> widget2(CreateWidget(NULL
));
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
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")))
333 scoped_ptr
<Widget
> widget
= CreateWidget(new ShapedWidgetDelegate());
334 XID xid
= widget
->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
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);
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
[] = {
351 "_NET_WM_STATE_FULLSCREEN",
354 Display
* display
= gfx::GetXDisplay();
355 ui::X11AtomCache
atom_cache(display
, kAtomsToCache
);
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
,
372 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FULLSCREEN", false);
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
) {
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
);
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
[] = {
402 "_NET_WM_STATE_HIDDEN",
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
);
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
,
425 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_HIDDEN", true);
428 EXPECT_FALSE(widget
.GetNativeWindow()->IsVisible());
430 // Show from minimized by sending _NET_WM_STATE_FOCUSED
432 const char* kAtomsToCache
[] = {
434 "_NET_WM_STATE_FOCUSED",
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
);
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
,
457 WMStateWaiter
waiter(xid
, "_NET_WM_STATE_FOCUSED", true);
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();
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
);
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
{
492 MouseEventRecorder() {}
493 ~MouseEventRecorder() override
{}
495 void Reset() { mouse_events_
.clear(); }
497 const std::vector
<ui::MouseEvent
>& mouse_events() const {
498 return mouse_events_
;
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
{
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
;
536 capture_widget
->GetNativeWindow()->SetCapture();
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
) {
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);
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);
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
);