1// Copyright (c) 2012 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.
4
5#include "ash/wm/system_gesture_event_filter.h"
6
7#include <vector>
8
9#include "ash/accelerators/accelerator_controller.h"
10#include "ash/display/display_manager.h"
11#include "ash/shelf/shelf.h"
12#include "ash/shelf/shelf_model.h"
13#include "ash/shell.h"
14#include "ash/system/tray/system_tray_delegate.h"
15#include "ash/test/ash_test_base.h"
16#include "ash/test/display_manager_test_api.h"
17#include "ash/test/shell_test_api.h"
18#include "ash/test/test_shelf_delegate.h"
19#include "ash/wm/gestures/long_press_affordance_handler.h"
20#include "ash/wm/window_state.h"
21#include "ash/wm/window_util.h"
22#include "base/time/time.h"
23#include "base/timer/timer.h"
24#include "ui/aura/env.h"
25#include "ui/aura/test/test_window_delegate.h"
26#include "ui/aura/test/test_windows.h"
27#include "ui/aura/window_event_dispatcher.h"
28#include "ui/base/hit_test.h"
29#include "ui/events/event.h"
30#include "ui/events/event_handler.h"
31#include "ui/events/event_utils.h"
32#include "ui/events/gestures/gesture_configuration.h"
33#include "ui/events/test/event_generator.h"
34#include "ui/events/test/test_event_handler.h"
35#include "ui/gfx/screen.h"
36#include "ui/gfx/size.h"
37#include "ui/views/widget/widget.h"
38#include "ui/views/widget/widget_delegate.h"
39#include "ui/views/window/non_client_view.h"
40#include "ui/views/window/window_button_order_provider.h"
41
42namespace ash {
43namespace test {
44
45namespace {
46
47class ResizableWidgetDelegate : public views::WidgetDelegateView {
48 public:
49  ResizableWidgetDelegate() {}
50  virtual ~ResizableWidgetDelegate() {}
51
52 private:
53  virtual bool CanResize() const OVERRIDE { return true; }
54  virtual bool CanMaximize() const OVERRIDE { return true; }
55  virtual bool CanMinimize() const OVERRIDE { return true; }
56  virtual void DeleteDelegate() OVERRIDE { delete this; }
57
58  DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
59};
60
61// Support class for testing windows with a maximum size.
62class MaxSizeNCFV : public views::NonClientFrameView {
63 public:
64  MaxSizeNCFV() {}
65 private:
66  virtual gfx::Size GetMaximumSize() const OVERRIDE {
67    return gfx::Size(200, 200);
68  }
69  virtual gfx::Rect GetBoundsForClientView() const OVERRIDE {
70    return gfx::Rect();
71  };
72
73  virtual gfx::Rect GetWindowBoundsForClientBounds(
74      const gfx::Rect& client_bounds) const OVERRIDE {
75    return gfx::Rect();
76  };
77
78  // This function must ask the ClientView to do a hittest.  We don't do this in
79  // the parent NonClientView because that makes it more difficult to calculate
80  // hittests for regions that are partially obscured by the ClientView, e.g.
81  // HTSYSMENU.
82  virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
83    return HTNOWHERE;
84  }
85  virtual void GetWindowMask(const gfx::Size& size,
86                             gfx::Path* window_mask) OVERRIDE {}
87  virtual void ResetWindowControls() OVERRIDE {}
88  virtual void UpdateWindowIcon() OVERRIDE {}
89  virtual void UpdateWindowTitle() OVERRIDE {}
90  virtual void SizeConstraintsChanged() OVERRIDE {}
91
92  DISALLOW_COPY_AND_ASSIGN(MaxSizeNCFV);
93};
94
95class MaxSizeWidgetDelegate : public views::WidgetDelegateView {
96 public:
97  MaxSizeWidgetDelegate() {}
98  virtual ~MaxSizeWidgetDelegate() {}
99
100 private:
101  virtual bool CanResize() const OVERRIDE { return true; }
102  virtual bool CanMaximize() const OVERRIDE { return false; }
103  virtual void DeleteDelegate() OVERRIDE { delete this; }
104  virtual views::NonClientFrameView* CreateNonClientFrameView(
105      views::Widget* widget) OVERRIDE {
106    return new MaxSizeNCFV;
107  }
108
109  DISALLOW_COPY_AND_ASSIGN(MaxSizeWidgetDelegate);
110};
111
112} // namespace
113
114class SystemGestureEventFilterTest : public AshTestBase {
115 public:
116  SystemGestureEventFilterTest() : AshTestBase() {}
117  virtual ~SystemGestureEventFilterTest() {}
118
119  LongPressAffordanceHandler* GetLongPressAffordance() {
120    ShellTestApi shell_test(Shell::GetInstance());
121    return shell_test.system_gesture_event_filter()->
122        long_press_affordance_.get();
123  }
124
125  base::OneShotTimer<LongPressAffordanceHandler>*
126  GetLongPressAffordanceTimer() {
127    return &GetLongPressAffordance()->timer_;
128  }
129
130  aura::Window* GetLongPressAffordanceTarget() {
131    return GetLongPressAffordance()->tap_down_target_;
132  }
133
134  views::View* GetLongPressAffordanceView() {
135    return reinterpret_cast<views::View*>(
136        GetLongPressAffordance()->view_.get());
137  }
138
139  // Overridden from AshTestBase:
140  virtual void SetUp() OVERRIDE {
141    // TODO(jonross): TwoFingerDragDelayed() and ThreeFingerGestureStopsDrag()
142    // both use hardcoded touch points, assuming that they target empty header
143    // space. Window control order now reflects configuration files and can
144    // change. The tests should be improved to dynamically decide touch points.
145    // To address this we specify the originally expected window control
146    // positions to be consistent across tests.
147    std::vector<views::FrameButton> leading;
148    std::vector<views::FrameButton> trailing;
149    trailing.push_back(views::FRAME_BUTTON_MINIMIZE);
150    trailing.push_back(views::FRAME_BUTTON_MAXIMIZE);
151    trailing.push_back(views::FRAME_BUTTON_CLOSE);
152    views::WindowButtonOrderProvider::GetInstance()->
153        SetWindowButtonOrder(leading, trailing);
154
155    test::AshTestBase::SetUp();
156    // Enable brightness key.
157    test::DisplayManagerTestApi(Shell::GetInstance()->display_manager()).
158        SetFirstDisplayAsInternalDisplay();
159  }
160
161 private:
162  DISALLOW_COPY_AND_ASSIGN(SystemGestureEventFilterTest);
163};
164
165ui::GestureEvent* CreateGesture(ui::EventType type,
166                                int x,
167                                int y,
168                                float delta_x,
169                                float delta_y,
170                                int touch_id) {
171  ui::GestureEventDetails details =
172      ui::GestureEventDetails(type, delta_x, delta_y);
173  details.set_oldest_touch_id(touch_id);
174  return new ui::GestureEvent(x, y, 0,
175      base::TimeDelta::FromMilliseconds(base::Time::Now().ToDoubleT() * 1000),
176      ui::GestureEventDetails(type, delta_x, delta_y));
177}
178
179TEST_F(SystemGestureEventFilterTest, LongPressAffordanceStateOnCaptureLoss) {
180  aura::Window* root_window = Shell::GetPrimaryRootWindow();
181
182  aura::test::TestWindowDelegate delegate;
183  scoped_ptr<aura::Window> window0(
184      aura::test::CreateTestWindowWithDelegate(
185          &delegate, 9, gfx::Rect(0, 0, 100, 100), root_window));
186  scoped_ptr<aura::Window> window1(
187      aura::test::CreateTestWindowWithDelegate(
188          &delegate, 10, gfx::Rect(0, 0, 100, 50), window0.get()));
189  scoped_ptr<aura::Window> window2(
190      aura::test::CreateTestWindowWithDelegate(
191          &delegate, 11, gfx::Rect(0, 50, 100, 50), window0.get()));
192
193  const int kTouchId = 5;
194
195  // Capture first window.
196  window1->SetCapture();
197  EXPECT_TRUE(window1->HasCapture());
198
199  // Send touch event to first window.
200  ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
201                       gfx::Point(10, 10),
202                       kTouchId,
203                       ui::EventTimeForNow());
204  ui::EventDispatchDetails details =
205      root_window->GetHost()->dispatcher()->OnEventFromSource(&press);
206  ASSERT_FALSE(details.dispatcher_destroyed);
207  EXPECT_TRUE(window1->HasCapture());
208
209  base::OneShotTimer<LongPressAffordanceHandler>* timer =
210      GetLongPressAffordanceTimer();
211  EXPECT_TRUE(timer->IsRunning());
212  EXPECT_EQ(window1, GetLongPressAffordanceTarget());
213
214  // Force timeout so that the affordance animation can start.
215  timer->user_task().Run();
216  timer->Stop();
217  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
218
219  // Change capture.
220  window2->SetCapture();
221  EXPECT_TRUE(window2->HasCapture());
222
223  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
224  EXPECT_EQ(window1, GetLongPressAffordanceTarget());
225
226  // Animate to completion.
227  GetLongPressAffordance()->End();  // end grow animation.
228  // Force timeout to start shrink animation.
229  EXPECT_TRUE(timer->IsRunning());
230  timer->user_task().Run();
231  timer->Stop();
232  EXPECT_TRUE(GetLongPressAffordance()->is_animating());
233  GetLongPressAffordance()->End();  // end shrink animation.
234
235  // Check if state has reset.
236  EXPECT_EQ(NULL, GetLongPressAffordanceTarget());
237  EXPECT_EQ(NULL, GetLongPressAffordanceView());
238}
239
240TEST_F(SystemGestureEventFilterTest, TwoFingerDrag) {
241  gfx::Rect bounds(0, 0, 600, 600);
242  aura::Window* root_window = Shell::GetPrimaryRootWindow();
243  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
244      new ResizableWidgetDelegate, root_window, bounds);
245  toplevel->Show();
246
247  const int kSteps = 15;
248  const int kTouchPoints = 2;
249  gfx::Point points[kTouchPoints] = {
250    gfx::Point(250, 250),
251    gfx::Point(350, 350),
252  };
253
254  ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
255
256  wm::WindowState* toplevel_state =
257      wm::GetWindowState(toplevel->GetNativeWindow());
258  // Swipe down to minimize.
259  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
260  EXPECT_TRUE(toplevel_state->IsMinimized());
261
262  toplevel->Restore();
263  toplevel->GetNativeWindow()->SetBounds(bounds);
264
265  // Swipe up to maximize.
266  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
267  EXPECT_TRUE(toplevel_state->IsMaximized());
268
269  toplevel->Restore();
270  toplevel->GetNativeWindow()->SetBounds(bounds);
271
272  // Swipe right to snap.
273  gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
274  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
275  gfx::Rect right_tile_bounds = toplevel->GetWindowBoundsInScreen();
276  EXPECT_NE(normal_bounds.ToString(), right_tile_bounds.ToString());
277
278  // Swipe left to snap.
279  gfx::Point left_points[kTouchPoints];
280  for (int i = 0; i < kTouchPoints; ++i) {
281    left_points[i] = points[i];
282    left_points[i].Offset(right_tile_bounds.x(), right_tile_bounds.y());
283  }
284  generator.GestureMultiFingerScroll(kTouchPoints, left_points, 15, kSteps,
285      -150, 0);
286  gfx::Rect left_tile_bounds = toplevel->GetWindowBoundsInScreen();
287  EXPECT_NE(normal_bounds.ToString(), left_tile_bounds.ToString());
288  EXPECT_NE(right_tile_bounds.ToString(), left_tile_bounds.ToString());
289
290  // Swipe right again.
291  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
292  gfx::Rect current_bounds = toplevel->GetWindowBoundsInScreen();
293  EXPECT_NE(current_bounds.ToString(), left_tile_bounds.ToString());
294  EXPECT_EQ(current_bounds.ToString(), right_tile_bounds.ToString());
295}
296
297TEST_F(SystemGestureEventFilterTest, TwoFingerDragTwoWindows) {
298  aura::Window* root_window = Shell::GetPrimaryRootWindow();
299  ui::GestureConfiguration::set_max_separation_for_gesture_touches_in_pixels(0);
300  views::Widget* first = views::Widget::CreateWindowWithContextAndBounds(
301      new ResizableWidgetDelegate, root_window, gfx::Rect(10, 0, 50, 100));
302  first->Show();
303  views::Widget* second = views::Widget::CreateWindowWithContextAndBounds(
304      new ResizableWidgetDelegate, root_window, gfx::Rect(100, 0, 100, 100));
305  second->Show();
306
307  // Start a two-finger drag on |first|, and then try to use another two-finger
308  // drag to move |second|. The attempt to move |second| should fail.
309  const gfx::Rect& first_bounds = first->GetWindowBoundsInScreen();
310  const gfx::Rect& second_bounds = second->GetWindowBoundsInScreen();
311  const int kSteps = 15;
312  const int kTouchPoints = 4;
313  gfx::Point points[kTouchPoints] = {
314    first_bounds.origin() + gfx::Vector2d(5, 5),
315    first_bounds.origin() + gfx::Vector2d(30, 10),
316    second_bounds.origin() + gfx::Vector2d(5, 5),
317    second_bounds.origin() + gfx::Vector2d(40, 20)
318  };
319
320  ui::test::EventGenerator generator(root_window);
321  // Do not drag too fast to avoid fling.
322  generator.GestureMultiFingerScroll(kTouchPoints, points,
323      50, kSteps, 0, 150);
324
325  EXPECT_NE(first_bounds.ToString(),
326            first->GetWindowBoundsInScreen().ToString());
327  EXPECT_EQ(second_bounds.ToString(),
328            second->GetWindowBoundsInScreen().ToString());
329}
330
331TEST_F(SystemGestureEventFilterTest, WindowsWithMaxSizeDontSnap) {
332  gfx::Rect bounds(250, 150, 100, 100);
333  aura::Window* root_window = Shell::GetPrimaryRootWindow();
334  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
335      new MaxSizeWidgetDelegate, root_window, bounds);
336  toplevel->Show();
337
338  const int kSteps = 15;
339  const int kTouchPoints = 2;
340  gfx::Point points[kTouchPoints] = {
341    gfx::Point(bounds.x() + 10, bounds.y() + 30),
342    gfx::Point(bounds.x() + 30, bounds.y() + 20),
343  };
344
345  ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
346
347  // Swipe down to minimize.
348  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
349  wm::WindowState* toplevel_state =
350      wm::GetWindowState(toplevel->GetNativeWindow());
351  EXPECT_TRUE(toplevel_state->IsMinimized());
352
353  toplevel->Restore();
354  toplevel->GetNativeWindow()->SetBounds(bounds);
355
356  // Check that swiping up doesn't maximize.
357  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, -150);
358  EXPECT_FALSE(toplevel_state->IsMaximized());
359
360  toplevel->Restore();
361  toplevel->GetNativeWindow()->SetBounds(bounds);
362
363  // Check that swiping right doesn't snap.
364  gfx::Rect normal_bounds = toplevel->GetWindowBoundsInScreen();
365  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
366  normal_bounds.set_x(normal_bounds.x() + 150);
367  EXPECT_EQ(normal_bounds.ToString(),
368      toplevel->GetWindowBoundsInScreen().ToString());
369
370  toplevel->GetNativeWindow()->SetBounds(bounds);
371
372  // Check that swiping left doesn't snap.
373  normal_bounds = toplevel->GetWindowBoundsInScreen();
374  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, -150, 0);
375  normal_bounds.set_x(normal_bounds.x() - 150);
376  EXPECT_EQ(normal_bounds.ToString(),
377      toplevel->GetWindowBoundsInScreen().ToString());
378
379  toplevel->GetNativeWindow()->SetBounds(bounds);
380
381  // Swipe right again, make sure the window still doesn't snap.
382  normal_bounds = toplevel->GetWindowBoundsInScreen();
383  normal_bounds.set_x(normal_bounds.x() + 150);
384  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 150, 0);
385  EXPECT_EQ(normal_bounds.ToString(),
386      toplevel->GetWindowBoundsInScreen().ToString());
387}
388
389TEST_F(SystemGestureEventFilterTest, DISABLED_TwoFingerDragEdge) {
390  gfx::Rect bounds(0, 0, 200, 100);
391  aura::Window* root_window = Shell::GetPrimaryRootWindow();
392  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
393      new ResizableWidgetDelegate, root_window, bounds);
394  toplevel->Show();
395
396  const int kSteps = 15;
397  const int kTouchPoints = 2;
398  gfx::Point points[kTouchPoints] = {
399    gfx::Point(30, 20),  // Caption
400    gfx::Point(0, 40),   // Left edge
401  };
402
403  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
404                      GetNonClientComponent(points[0]));
405  EXPECT_EQ(HTLEFT, toplevel->GetNativeWindow()->delegate()->
406        GetNonClientComponent(points[1]));
407
408  ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
409
410  bounds = toplevel->GetNativeWindow()->bounds();
411  // Swipe down. Nothing should happen.
412  generator.GestureMultiFingerScroll(kTouchPoints, points, 15, kSteps, 0, 150);
413  EXPECT_EQ(bounds.ToString(),
414            toplevel->GetNativeWindow()->bounds().ToString());
415}
416
417// We do not allow resizing a window via multiple edges simultaneously. Test
418// that the behavior is reasonable if a user attempts to resize a window via
419// several edges.
420TEST_F(SystemGestureEventFilterTest,
421       TwoFingerAttemptResizeLeftAndRightEdgesSimultaneously) {
422  gfx::Rect initial_bounds(0, 0, 400, 400);
423  views::Widget* toplevel =
424      views::Widget::CreateWindowWithContextAndBounds(
425          new ResizableWidgetDelegate, CurrentContext(), initial_bounds);
426  toplevel->Show();
427
428  const int kSteps = 15;
429  const int kTouchPoints = 2;
430  gfx::Point points[kTouchPoints] = {
431    gfx::Point(0, 40),    // Left edge
432    gfx::Point(399, 40),  // Right edge
433  };
434  int delays[kTouchPoints] = {0, 120};
435
436  EXPECT_EQ(HTLEFT, toplevel->GetNonClientComponent(points[0]));
437  EXPECT_EQ(HTRIGHT, toplevel->GetNonClientComponent(points[1]));
438
439  GetEventGenerator().GestureMultiFingerScrollWithDelays(
440      kTouchPoints, points, delays, 15, kSteps, 0, 40);
441
442  // The window bounds should not have changed because neither of the fingers
443  // moved horizontally.
444  EXPECT_EQ(initial_bounds.ToString(),
445            toplevel->GetNativeWindow()->bounds().ToString());
446}
447
448TEST_F(SystemGestureEventFilterTest, TwoFingerDragDelayed) {
449  gfx::Rect bounds(0, 0, 200, 100);
450  aura::Window* root_window = Shell::GetPrimaryRootWindow();
451  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
452      new ResizableWidgetDelegate, root_window, bounds);
453  toplevel->Show();
454
455  const int kSteps = 15;
456  const int kTouchPoints = 2;
457  gfx::Point points[kTouchPoints] = {
458    gfx::Point(30, 20),  // Caption
459    gfx::Point(34, 20),  // Caption
460  };
461  int delays[kTouchPoints] = {0, 120};
462
463  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
464        GetNonClientComponent(points[0]));
465  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
466        GetNonClientComponent(points[1]));
467
468  ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
469
470  bounds = toplevel->GetNativeWindow()->bounds();
471  // Swipe right and down starting with one finger.
472  // Add another finger after 120ms and continue dragging.
473  // The window should move and the drag should be determined by the center
474  // point between the fingers.
475  generator.GestureMultiFingerScrollWithDelays(
476      kTouchPoints, points, delays, 15, kSteps, 150, 150);
477  bounds += gfx::Vector2d(150 + (points[1].x() - points[0].x()) / 2, 150);
478  EXPECT_EQ(bounds.ToString(),
479            toplevel->GetNativeWindow()->bounds().ToString());
480}
481
482TEST_F(SystemGestureEventFilterTest, ThreeFingerGestureStopsDrag) {
483  gfx::Rect bounds(0, 0, 200, 100);
484  aura::Window* root_window = Shell::GetPrimaryRootWindow();
485  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
486      new ResizableWidgetDelegate, root_window, bounds);
487  toplevel->Show();
488
489  const int kSteps = 10;
490  const int kTouchPoints = 3;
491  gfx::Point points[kTouchPoints] = {
492    gfx::Point(30, 20),  // Caption
493    gfx::Point(34, 20),  // Caption
494    gfx::Point(38, 20),  // Caption
495  };
496  int delays[kTouchPoints] = {0, 0, 120};
497
498  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
499        GetNonClientComponent(points[0]));
500  EXPECT_EQ(HTCAPTION, toplevel->GetNativeWindow()->delegate()->
501        GetNonClientComponent(points[1]));
502
503  ui::test::EventGenerator generator(root_window, toplevel->GetNativeWindow());
504
505  bounds = toplevel->GetNativeWindow()->bounds();
506  // Swipe right and down starting with two fingers.
507  // Add third finger after 120ms and continue dragging.
508  // The window should start moving but stop when the 3rd finger touches down.
509  const int kEventSeparation = 15;
510  generator.GestureMultiFingerScrollWithDelays(
511      kTouchPoints, points, delays, kEventSeparation, kSteps, 150, 150);
512  int expected_drag = 150 / kSteps * 120 / kEventSeparation;
513  bounds += gfx::Vector2d(expected_drag, expected_drag);
514  EXPECT_EQ(bounds.ToString(),
515            toplevel->GetNativeWindow()->bounds().ToString());
516}
517
518TEST_F(SystemGestureEventFilterTest, DragLeftNearEdgeSnaps) {
519  gfx::Rect bounds(200, 150, 400, 100);
520  aura::Window* root_window = Shell::GetPrimaryRootWindow();
521  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
522      new ResizableWidgetDelegate, root_window, bounds);
523  toplevel->Show();
524
525  const int kSteps = 15;
526  const int kTouchPoints = 2;
527  gfx::Point points[kTouchPoints] = {
528    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
529    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
530  };
531  aura::Window* toplevel_window = toplevel->GetNativeWindow();
532  ui::test::EventGenerator generator(root_window, toplevel_window);
533
534  // Check that dragging left snaps before reaching the screen edge.
535  gfx::Rect work_area =
536      Shell::GetScreen()->GetDisplayNearestWindow(root_window).work_area();
537  int drag_x = work_area.x() + 20 - points[0].x();
538  generator.GestureMultiFingerScroll(
539      kTouchPoints, points, 120, kSteps, drag_x, 0);
540
541  EXPECT_EQ(wm::GetDefaultLeftSnappedWindowBoundsInParent(
542                toplevel_window).ToString(),
543            toplevel_window->bounds().ToString());
544}
545
546TEST_F(SystemGestureEventFilterTest, DragRightNearEdgeSnaps) {
547  gfx::Rect bounds(200, 150, 400, 100);
548  aura::Window* root_window = Shell::GetPrimaryRootWindow();
549  views::Widget* toplevel = views::Widget::CreateWindowWithContextAndBounds(
550      new ResizableWidgetDelegate, root_window, bounds);
551  toplevel->Show();
552
553  const int kSteps = 15;
554  const int kTouchPoints = 2;
555  gfx::Point points[kTouchPoints] = {
556    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
557    gfx::Point(bounds.x() + bounds.width() / 2, bounds.y() + 5),
558  };
559  aura::Window* toplevel_window = toplevel->GetNativeWindow();
560  ui::test::EventGenerator generator(root_window, toplevel_window);
561
562  // Check that dragging right snaps before reaching the screen edge.
563  gfx::Rect work_area =
564      Shell::GetScreen()->GetDisplayNearestWindow(root_window).work_area();
565  int drag_x = work_area.right() - 20 - points[0].x();
566  generator.GestureMultiFingerScroll(
567      kTouchPoints, points, 120, kSteps, drag_x, 0);
568  EXPECT_EQ(wm::GetDefaultRightSnappedWindowBoundsInParent(
569                toplevel_window).ToString(),
570            toplevel_window->bounds().ToString());
571}
572
573// Tests that the window manager does not consume gesture events targeted to
574// windows of type WINDOW_TYPE_CONTROL. This is important because the web
575// contents are often (but not always) of type WINDOW_TYPE_CONTROL.
576TEST_F(SystemGestureEventFilterTest,
577       ControlWindowGetsMultiFingerGestureEvents) {
578  scoped_ptr<aura::Window> parent(
579      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100)));
580
581  aura::test::EventCountDelegate delegate;
582  delegate.set_window_component(HTCLIENT);
583  scoped_ptr<aura::Window> child(new aura::Window(&delegate));
584  child->SetType(ui::wm::WINDOW_TYPE_CONTROL);
585  child->Init(aura::WINDOW_LAYER_TEXTURED);
586  parent->AddChild(child.get());
587  child->SetBounds(gfx::Rect(100, 100));
588  child->Show();
589
590  ui::test::TestEventHandler event_handler;
591  aura::Env::GetInstance()->PrependPreTargetHandler(&event_handler);
592
593  GetEventGenerator().MoveMouseTo(0, 0);
594  for (int i = 1; i <= 3; ++i)
595    GetEventGenerator().PressTouchId(i);
596  for (int i = 1; i <= 3; ++i)
597    GetEventGenerator().ReleaseTouchId(i);
598  EXPECT_EQ(event_handler.num_gesture_events(),
599            delegate.GetGestureCountAndReset());
600
601  aura::Env::GetInstance()->RemovePreTargetHandler(&event_handler);
602}
603
604}  // namespace test
605}  // namespace ash
606