1// Copyright 2013 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/workspace_controller.h"
6
7#include <map>
8
9#include "ash/root_window_controller.h"
10#include "ash/screen_util.h"
11#include "ash/shelf/shelf_layout_manager.h"
12#include "ash/shelf/shelf_widget.h"
13#include "ash/shell.h"
14#include "ash/shell_window_ids.h"
15#include "ash/system/status_area_widget.h"
16#include "ash/test/ash_test_base.h"
17#include "ash/test/shell_test_api.h"
18#include "ash/test/test_shelf_delegate.h"
19#include "ash/wm/panels/panel_layout_manager.h"
20#include "ash/wm/window_state.h"
21#include "ash/wm/window_util.h"
22#include "ash/wm/workspace/workspace_window_resizer.h"
23#include "base/strings/string_number_conversions.h"
24#include "ui/aura/client/aura_constants.h"
25#include "ui/aura/test/test_window_delegate.h"
26#include "ui/aura/test/test_windows.h"
27#include "ui/aura/window.h"
28#include "ui/aura/window_event_dispatcher.h"
29#include "ui/base/hit_test.h"
30#include "ui/base/ui_base_types.h"
31#include "ui/compositor/layer.h"
32#include "ui/compositor/scoped_animation_duration_scale_mode.h"
33#include "ui/events/event_utils.h"
34#include "ui/events/test/event_generator.h"
35#include "ui/gfx/screen.h"
36#include "ui/views/widget/widget.h"
37#include "ui/wm/core/window_animations.h"
38#include "ui/wm/core/window_util.h"
39
40using aura::Window;
41
42namespace ash {
43
44// Returns a string containing the names of all the children of |window| (in
45// order). Each entry is separated by a space.
46std::string GetWindowNames(const aura::Window* window) {
47  std::string result;
48  for (size_t i = 0; i < window->children().size(); ++i) {
49    if (i != 0)
50      result += " ";
51    result += window->children()[i]->name();
52  }
53  return result;
54}
55
56// Returns a string containing the names of windows corresponding to each of the
57// child layers of |window|'s layer. Any layers that don't correspond to a child
58// Window of |window| are ignored. The result is ordered based on the layer
59// ordering.
60std::string GetLayerNames(const aura::Window* window) {
61  typedef std::map<const ui::Layer*, std::string> LayerToWindowNameMap;
62  LayerToWindowNameMap window_names;
63  for (size_t i = 0; i < window->children().size(); ++i) {
64    window_names[window->children()[i]->layer()] =
65        window->children()[i]->name();
66  }
67
68  std::string result;
69  const std::vector<ui::Layer*>& layers(window->layer()->children());
70  for (size_t i = 0; i < layers.size(); ++i) {
71    LayerToWindowNameMap::iterator layer_i =
72        window_names.find(layers[i]);
73    if (layer_i != window_names.end()) {
74      if (!result.empty())
75        result += " ";
76      result += layer_i->second;
77    }
78  }
79  return result;
80}
81
82class WorkspaceControllerTest : public test::AshTestBase {
83 public:
84  WorkspaceControllerTest() {}
85  virtual ~WorkspaceControllerTest() {}
86
87  aura::Window* CreateTestWindowUnparented() {
88    aura::Window* window = new aura::Window(NULL);
89    window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
90    window->SetType(ui::wm::WINDOW_TYPE_NORMAL);
91    window->Init(aura::WINDOW_LAYER_TEXTURED);
92    return window;
93  }
94
95  aura::Window* CreateTestWindow() {
96    aura::Window* window = new aura::Window(NULL);
97    window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
98    window->SetType(ui::wm::WINDOW_TYPE_NORMAL);
99    window->Init(aura::WINDOW_LAYER_TEXTURED);
100    ParentWindowInPrimaryRootWindow(window);
101    return window;
102  }
103
104  aura::Window* CreateBrowserLikeWindow(const gfx::Rect& bounds) {
105    aura::Window* window = CreateTestWindow();
106    window->SetBounds(bounds);
107    wm::WindowState* window_state = wm::GetWindowState(window);
108    window_state->set_window_position_managed(true);
109    window->Show();
110    return window;
111  }
112
113  aura::Window* CreatePopupLikeWindow(const gfx::Rect& bounds) {
114    aura::Window* window = CreateTestWindowInShellWithBounds(bounds);
115    window->Show();
116    return window;
117  }
118
119  aura::Window* CreateTestPanel(aura::WindowDelegate* delegate,
120                                const gfx::Rect& bounds) {
121    aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
122        delegate,
123        ui::wm::WINDOW_TYPE_PANEL,
124        0,
125        bounds);
126    test::TestShelfDelegate* shelf_delegate =
127        test::TestShelfDelegate::instance();
128    shelf_delegate->AddShelfItem(window);
129    PanelLayoutManager* manager = static_cast<PanelLayoutManager*>(
130        Shell::GetContainer(window->GetRootWindow(),
131                            kShellWindowId_PanelContainer)->layout_manager());
132    manager->Relayout();
133    return window;
134  }
135
136  aura::Window* GetDesktop() {
137    return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
138                               kShellWindowId_DefaultContainer);
139  }
140
141  gfx::Rect GetFullscreenBounds(aura::Window* window) {
142    return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds();
143  }
144
145  ShelfWidget* shelf_widget() {
146    return Shell::GetPrimaryRootWindowController()->shelf();
147  }
148
149  ShelfLayoutManager* shelf_layout_manager() {
150    return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
151  }
152
153  bool GetWindowOverlapsShelf() {
154    return shelf_layout_manager()->window_overlaps_shelf();
155  }
156
157 private:
158  DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTest);
159};
160
161// Assertions around adding a normal window.
162TEST_F(WorkspaceControllerTest, AddNormalWindowWhenEmpty) {
163  scoped_ptr<Window> w1(CreateTestWindow());
164  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
165
166  wm::WindowState* window_state = wm::GetWindowState(w1.get());
167
168  EXPECT_FALSE(window_state->HasRestoreBounds());
169
170  w1->Show();
171
172  EXPECT_FALSE(window_state->HasRestoreBounds());
173
174  ASSERT_TRUE(w1->layer() != NULL);
175  EXPECT_TRUE(w1->layer()->visible());
176
177  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
178
179  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
180}
181
182// Assertions around maximizing/unmaximizing.
183TEST_F(WorkspaceControllerTest, SingleMaximizeWindow) {
184  scoped_ptr<Window> w1(CreateTestWindow());
185  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
186
187  w1->Show();
188  wm::ActivateWindow(w1.get());
189
190  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
191
192  ASSERT_TRUE(w1->layer() != NULL);
193  EXPECT_TRUE(w1->layer()->visible());
194
195  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
196
197  // Maximize the window.
198  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
199
200  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));
201
202  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
203  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()).width(),
204            w1->bounds().width());
205  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()).height(),
206            w1->bounds().height());
207
208  // Restore the window.
209  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
210
211  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
212  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
213}
214
215// Assertions around two windows and toggling one to be fullscreen.
216TEST_F(WorkspaceControllerTest, FullscreenWithNormalWindow) {
217  scoped_ptr<Window> w1(CreateTestWindow());
218  scoped_ptr<Window> w2(CreateTestWindow());
219  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
220  w1->Show();
221
222  ASSERT_TRUE(w1->layer() != NULL);
223  EXPECT_TRUE(w1->layer()->visible());
224
225  w2->SetBounds(gfx::Rect(0, 0, 50, 51));
226  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
227  w2->Show();
228  wm::ActivateWindow(w2.get());
229
230  // Both windows should be in the same workspace.
231  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
232  EXPECT_EQ(w2.get(), GetDesktop()->children()[1]);
233
234  gfx::Rect work_area(
235      ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()));
236  EXPECT_EQ(work_area.width(), w2->bounds().width());
237  EXPECT_EQ(work_area.height(), w2->bounds().height());
238
239  // Restore w2, which should then go back to one workspace.
240  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
241  EXPECT_EQ(50, w2->bounds().width());
242  EXPECT_EQ(51, w2->bounds().height());
243  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
244}
245
246// Makes sure requests to change the bounds of a normal window go through.
247TEST_F(WorkspaceControllerTest, ChangeBoundsOfNormalWindow) {
248  scoped_ptr<Window> w1(CreateTestWindow());
249  w1->Show();
250
251  // Setting the bounds should go through since the window is in the normal
252  // workspace.
253  w1->SetBounds(gfx::Rect(0, 0, 200, 500));
254  EXPECT_EQ(200, w1->bounds().width());
255  EXPECT_EQ(500, w1->bounds().height());
256}
257
258// Verifies the bounds is not altered when showing and grid is enabled.
259TEST_F(WorkspaceControllerTest, SnapToGrid) {
260  scoped_ptr<Window> w1(CreateTestWindowUnparented());
261  w1->SetBounds(gfx::Rect(1, 6, 25, 30));
262  ParentWindowInPrimaryRootWindow(w1.get());
263  // We are not aligning this anymore this way. When the window gets shown
264  // the window is expected to be handled differently, but this cannot be
265  // tested with this test. So the result of this test should be that the
266  // bounds are exactly as passed in.
267  EXPECT_EQ("1,6 25x30", w1->bounds().ToString());
268}
269
270// Assertions around a fullscreen window.
271TEST_F(WorkspaceControllerTest, SingleFullscreenWindow) {
272  scoped_ptr<Window> w1(CreateTestWindow());
273  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
274  // Make the window fullscreen.
275  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
276  w1->Show();
277  wm::ActivateWindow(w1.get());
278
279  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
280  EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
281  EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());
282
283  // Restore the window. Use SHOW_STATE_DEFAULT as that is what we'll end up
284  // with when using views::Widget.
285  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_DEFAULT);
286  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
287
288  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
289  EXPECT_EQ(250, w1->bounds().width());
290  EXPECT_EQ(251, w1->bounds().height());
291
292  // Back to fullscreen.
293  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
294  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
295  EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
296  EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());
297  wm::WindowState* window_state = wm::GetWindowState(w1.get());
298
299  ASSERT_TRUE(window_state->HasRestoreBounds());
300  EXPECT_EQ("0,0 250x251", window_state->GetRestoreBoundsInScreen().ToString());
301}
302
303// Assertions around minimizing a single window.
304TEST_F(WorkspaceControllerTest, MinimizeSingleWindow) {
305  scoped_ptr<Window> w1(CreateTestWindow());
306
307  w1->Show();
308
309  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
310  EXPECT_FALSE(w1->layer()->IsDrawn());
311  EXPECT_TRUE(w1->layer()->GetTargetTransform().IsIdentity());
312
313  // Show the window.
314  w1->Show();
315  EXPECT_TRUE(wm::GetWindowState(w1.get())->IsNormalStateType());
316  EXPECT_TRUE(w1->layer()->IsDrawn());
317}
318
319// Assertions around minimizing a fullscreen window.
320TEST_F(WorkspaceControllerTest, MinimizeFullscreenWindow) {
321  // Two windows, w1 normal, w2 fullscreen.
322  scoped_ptr<Window> w1(CreateTestWindow());
323  scoped_ptr<Window> w2(CreateTestWindow());
324  w1->Show();
325  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
326  w2->Show();
327
328  wm::WindowState* w1_state = wm::GetWindowState(w1.get());
329  wm::WindowState* w2_state = wm::GetWindowState(w2.get());
330
331  w2_state->Activate();
332
333  // Minimize w2.
334  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
335  EXPECT_TRUE(w1->layer()->IsDrawn());
336  EXPECT_FALSE(w2->layer()->IsDrawn());
337
338  // Show the window, which should trigger unminimizing.
339  w2->Show();
340  w2_state->Activate();
341
342  EXPECT_TRUE(w2_state->IsFullscreen());
343  EXPECT_TRUE(w1->layer()->IsDrawn());
344  EXPECT_TRUE(w2->layer()->IsDrawn());
345
346  // Minimize the window, which should hide the window.
347  EXPECT_TRUE(w2_state->IsActive());
348  w2_state->Minimize();
349  EXPECT_FALSE(w2_state->IsActive());
350  EXPECT_FALSE(w2->layer()->IsDrawn());
351  EXPECT_TRUE(w1_state->IsActive());
352  EXPECT_EQ(w2.get(), GetDesktop()->children()[0]);
353  EXPECT_EQ(w1.get(), GetDesktop()->children()[1]);
354
355  // Make the window normal.
356  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
357  // Setting back to normal doesn't change the activation.
358  EXPECT_FALSE(w2_state->IsActive());
359  EXPECT_TRUE(w1_state->IsActive());
360  EXPECT_EQ(w2.get(), GetDesktop()->children()[0]);
361  EXPECT_EQ(w1.get(), GetDesktop()->children()[1]);
362  EXPECT_TRUE(w2->layer()->IsDrawn());
363}
364
365// Verifies ShelfLayoutManager's visibility/auto-hide state is correctly
366// updated.
367TEST_F(WorkspaceControllerTest, ShelfStateUpdated) {
368  // Since ShelfLayoutManager queries for mouse location, move the mouse so
369  // it isn't over the shelf.
370  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
371                                     gfx::Point());
372  generator.MoveMouseTo(0, 0);
373
374  scoped_ptr<Window> w1(CreateTestWindow());
375  const gfx::Rect w1_bounds(0, 1, 101, 102);
376  ShelfLayoutManager* shelf = shelf_layout_manager();
377  shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
378  const gfx::Rect touches_shelf_bounds(
379      0, shelf->GetIdealBounds().y() - 10, 101, 102);
380  // Move |w1| to overlap the shelf.
381  w1->SetBounds(touches_shelf_bounds);
382  EXPECT_FALSE(GetWindowOverlapsShelf());
383
384  // A visible ignored window should not trigger the overlap.
385  scoped_ptr<Window> w_ignored(CreateTestWindow());
386  w_ignored->SetBounds(touches_shelf_bounds);
387  wm::GetWindowState(&(*w_ignored))->set_ignored_by_shelf(true);
388  w_ignored->Show();
389  EXPECT_FALSE(GetWindowOverlapsShelf());
390
391  // Make it visible, since visible shelf overlaps should be true.
392  w1->Show();
393  EXPECT_TRUE(GetWindowOverlapsShelf());
394
395  wm::ActivateWindow(w1.get());
396  w1->SetBounds(w1_bounds);
397  w1->Show();
398  wm::ActivateWindow(w1.get());
399
400  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
401
402  // Maximize the window.
403  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
404  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
405  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
406
407  // Restore.
408  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
409  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
410  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
411
412  // Fullscreen.
413  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
414  EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());
415
416  // Normal.
417  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
418  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
419  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
420  EXPECT_FALSE(GetWindowOverlapsShelf());
421
422  // Move window so it obscures shelf.
423  w1->SetBounds(touches_shelf_bounds);
424  EXPECT_TRUE(GetWindowOverlapsShelf());
425
426  // Move it back.
427  w1->SetBounds(w1_bounds);
428  EXPECT_FALSE(GetWindowOverlapsShelf());
429
430  // Maximize again.
431  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
432  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
433  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
434
435  // Minimize.
436  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
437  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
438
439  // Since the restore from minimize will restore to the pre-minimize
440  // state (tested elsewhere), we abandon the current size and restore
441  // rect and set them to the window.
442  wm::WindowState* window_state = wm::GetWindowState(w1.get());
443
444  gfx::Rect restore = window_state->GetRestoreBoundsInScreen();
445  EXPECT_EQ("0,0 800x597", w1->bounds().ToString());
446  EXPECT_EQ("0,1 101x102", restore.ToString());
447  window_state->ClearRestoreBounds();
448  w1->SetBounds(restore);
449
450  // Restore.
451  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
452  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
453  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
454
455  // Create another window, maximized.
456  scoped_ptr<Window> w2(CreateTestWindow());
457  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
458  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
459  w2->Show();
460  wm::ActivateWindow(w2.get());
461  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
462  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
463  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
464  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(
465                w2->parent()).ToString(),
466            w2->bounds().ToString());
467
468  // Switch to w1.
469  wm::ActivateWindow(w1.get());
470  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
471  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
472  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(
473                w2->parent()).ToString(),
474            w2->bounds().ToString());
475
476  // Switch to w2.
477  wm::ActivateWindow(w2.get());
478  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
479  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
480  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
481  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w2.get()).ToString(),
482            w2->bounds().ToString());
483
484  // Turn off auto-hide, switch back to w2 (maximized) and verify overlap.
485  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
486  wm::ActivateWindow(w2.get());
487  EXPECT_FALSE(GetWindowOverlapsShelf());
488
489  // Move w1 to overlap shelf, it shouldn't change window overlaps shelf since
490  // the window isn't in the visible workspace.
491  w1->SetBounds(touches_shelf_bounds);
492  EXPECT_FALSE(GetWindowOverlapsShelf());
493
494  // Activate w1. Although w1 is visible, the overlap state is still false since
495  // w2 is maximized.
496  wm::ActivateWindow(w1.get());
497  EXPECT_FALSE(GetWindowOverlapsShelf());
498
499  // Restore w2.
500  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
501  EXPECT_TRUE(GetWindowOverlapsShelf());
502}
503
504// Verifies going from maximized to minimized sets the right state for painting
505// the background of the launcher.
506TEST_F(WorkspaceControllerTest, MinimizeResetsVisibility) {
507  scoped_ptr<Window> w1(CreateTestWindow());
508  w1->Show();
509  wm::ActivateWindow(w1.get());
510  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
511  EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, shelf_widget()->GetBackgroundType());
512
513  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
514  EXPECT_EQ(SHELF_VISIBLE,
515            shelf_layout_manager()->visibility_state());
516  EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, shelf_widget()->GetBackgroundType());
517}
518
519// Verifies window visibility during various workspace changes.
520TEST_F(WorkspaceControllerTest, VisibilityTests) {
521  scoped_ptr<Window> w1(CreateTestWindow());
522  w1->Show();
523  EXPECT_TRUE(w1->IsVisible());
524  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
525
526  // Create another window, activate it and make it fullscreen.
527  scoped_ptr<Window> w2(CreateTestWindow());
528  w2->Show();
529  wm::ActivateWindow(w2.get());
530  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
531  EXPECT_TRUE(w2->IsVisible());
532  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
533  EXPECT_TRUE(w1->IsVisible());
534
535  // Switch to w1. |w1| should be visible on top of |w2|.
536  wm::ActivateWindow(w1.get());
537  EXPECT_TRUE(w1->IsVisible());
538  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
539  EXPECT_TRUE(w2->IsVisible());
540
541  // Switch back to |w2|.
542  wm::ActivateWindow(w2.get());
543  EXPECT_TRUE(w2->IsVisible());
544  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
545  EXPECT_TRUE(w1->IsVisible());
546
547  // Restore |w2|, both windows should be visible.
548  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
549  EXPECT_TRUE(w1->IsVisible());
550  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
551  EXPECT_TRUE(w2->IsVisible());
552  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
553
554  // Make |w2| fullscreen again, then close it.
555  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
556  w2->Hide();
557  EXPECT_FALSE(w2->IsVisible());
558  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
559  EXPECT_TRUE(w1->IsVisible());
560
561  // Create |w2| and maximize it.
562  w2.reset(CreateTestWindow());
563  w2->Show();
564  wm::ActivateWindow(w2.get());
565  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
566  EXPECT_TRUE(w2->IsVisible());
567  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
568  EXPECT_TRUE(w1->IsVisible());
569
570  // Close |w2|.
571  w2.reset();
572  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
573  EXPECT_TRUE(w1->IsVisible());
574}
575
576// Verifies windows that are offscreen don't move when switching workspaces.
577TEST_F(WorkspaceControllerTest, DontMoveOnSwitch) {
578  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
579                                     gfx::Point());
580  generator.MoveMouseTo(0, 0);
581
582  scoped_ptr<Window> w1(CreateTestWindow());
583  ShelfLayoutManager* shelf = shelf_layout_manager();
584  const gfx::Rect touches_shelf_bounds(
585      0, shelf->GetIdealBounds().y() - 10, 101, 102);
586  // Move |w1| to overlap the shelf.
587  w1->SetBounds(touches_shelf_bounds);
588  w1->Show();
589  wm::ActivateWindow(w1.get());
590
591  // Create another window and maximize it.
592  scoped_ptr<Window> w2(CreateTestWindow());
593  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
594  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
595  w2->Show();
596  wm::ActivateWindow(w2.get());
597
598  // Switch to w1.
599  wm::ActivateWindow(w1.get());
600  EXPECT_EQ(touches_shelf_bounds.ToString(), w1->bounds().ToString());
601}
602
603// Verifies that windows that are completely offscreen move when switching
604// workspaces.
605TEST_F(WorkspaceControllerTest, MoveOnSwitch) {
606  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
607                                     gfx::Point());
608  generator.MoveMouseTo(0, 0);
609
610  scoped_ptr<Window> w1(CreateTestWindow());
611  ShelfLayoutManager* shelf = shelf_layout_manager();
612  const gfx::Rect w1_bounds(0, shelf->GetIdealBounds().y(), 100, 200);
613  // Move |w1| so that the top edge is the same as the top edge of the shelf.
614  w1->SetBounds(w1_bounds);
615  w1->Show();
616  wm::ActivateWindow(w1.get());
617  EXPECT_EQ(w1_bounds.ToString(), w1->bounds().ToString());
618
619  // Create another window and maximize it.
620  scoped_ptr<Window> w2(CreateTestWindow());
621  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
622  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
623  w2->Show();
624  wm::ActivateWindow(w2.get());
625
626  // Increase the size of the WorkAreaInsets. This would make |w1| fall
627  // completely out of the display work area.
628  gfx::Insets insets =
629      Shell::GetScreen()->GetPrimaryDisplay().GetWorkAreaInsets();
630  insets.Set(0, 0, insets.bottom() + 30, 0);
631  Shell::GetInstance()->SetDisplayWorkAreaInsets(w1.get(), insets);
632
633  // Switch to w1. The window should have moved.
634  wm::ActivateWindow(w1.get());
635  EXPECT_NE(w1_bounds.ToString(), w1->bounds().ToString());
636}
637
638namespace {
639
640// WindowDelegate used by DontCrashOnChangeAndActivate.
641class DontCrashOnChangeAndActivateDelegate
642    : public aura::test::TestWindowDelegate {
643 public:
644  DontCrashOnChangeAndActivateDelegate() : window_(NULL) {}
645
646  void set_window(aura::Window* window) { window_ = window; }
647
648  // WindowDelegate overrides:
649  virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
650                               const gfx::Rect& new_bounds) OVERRIDE {
651    if (window_) {
652      wm::ActivateWindow(window_);
653      window_ = NULL;
654    }
655  }
656
657 private:
658  aura::Window* window_;
659
660  DISALLOW_COPY_AND_ASSIGN(DontCrashOnChangeAndActivateDelegate);
661};
662
663}  // namespace
664
665// Exercises possible crash in W2. Here's the sequence:
666// . minimize a maximized window.
667// . remove the window (which happens when switching displays).
668// . add the window back.
669// . show the window and during the bounds change activate it.
670TEST_F(WorkspaceControllerTest, DontCrashOnChangeAndActivate) {
671  // Force the shelf
672  ShelfLayoutManager* shelf = shelf_layout_manager();
673  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
674
675  DontCrashOnChangeAndActivateDelegate delegate;
676  scoped_ptr<Window> w1(CreateTestWindowInShellWithDelegate(
677      &delegate, 1000, gfx::Rect(10, 11, 250, 251)));
678
679  w1->Show();
680  wm::WindowState* w1_state = wm::GetWindowState(w1.get());
681  w1_state->Activate();
682  w1_state->Maximize();
683  w1_state->Minimize();
684
685  w1->parent()->RemoveChild(w1.get());
686
687  // Do this so that when we Show() the window a resize occurs and we make the
688  // window active.
689  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
690
691  ParentWindowInPrimaryRootWindow(w1.get());
692  delegate.set_window(w1.get());
693  w1->Show();
694}
695
696// Verifies a window with a transient parent not managed by workspace works.
697TEST_F(WorkspaceControllerTest, TransientParent) {
698  // Normal window with no transient parent.
699  scoped_ptr<Window> w2(CreateTestWindow());
700  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
701  w2->Show();
702  wm::ActivateWindow(w2.get());
703
704  // Window with a transient parent. We set the transient parent to the root,
705  // which would never happen but is enough to exercise the bug.
706  scoped_ptr<Window> w1(CreateTestWindowUnparented());
707  ::wm::AddTransientChild(
708      Shell::GetInstance()->GetPrimaryRootWindow(), w1.get());
709  w1->SetBounds(gfx::Rect(10, 11, 250, 251));
710  ParentWindowInPrimaryRootWindow(w1.get());
711  w1->Show();
712  wm::ActivateWindow(w1.get());
713
714  // The window with the transient parent should get added to the same parent as
715  // the normal window.
716  EXPECT_EQ(w2->parent(), w1->parent());
717}
718
719// Test the placement of newly created windows.
720TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnCreate) {
721  if (!SupportsHostWindowResize())
722    return;
723  UpdateDisplay("1600x1200");
724  // Creating a popup handler here to make sure it does not interfere with the
725  // existing windows.
726  gfx::Rect source_browser_bounds(16, 32, 640, 320);
727  scoped_ptr<aura::Window> browser_window(CreateBrowserLikeWindow(
728      source_browser_bounds));
729
730  // Creating a popup to make sure it does not interfere with the positioning.
731  scoped_ptr<aura::Window> browser_popup(CreatePopupLikeWindow(
732      gfx::Rect(16, 32, 128, 256)));
733
734  browser_window->Show();
735  browser_popup->Show();
736
737  { // With a shown window it's size should get returned.
738    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
739        source_browser_bounds));
740    // The position should be right flush.
741    EXPECT_EQ("960,32 640x320", new_browser_window->bounds().ToString());
742  }
743
744  { // With the window shown - but more on the right side then on the left
745    // side (and partially out of the screen), it should default to the other
746    // side and inside the screen.
747    gfx::Rect source_browser_bounds(gfx::Rect(1000, 600, 640, 320));
748    browser_window->SetBounds(source_browser_bounds);
749
750    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
751        source_browser_bounds));
752    // The position should be left & bottom flush.
753    EXPECT_EQ("0,600 640x320", new_browser_window->bounds().ToString());
754
755    // If the other window was already beyond the point to get right flush
756    // it will remain where it is.
757    EXPECT_EQ("1000,600 640x320", browser_window->bounds().ToString());
758  }
759
760  { // Make sure that popups do not get changed.
761    scoped_ptr<aura::Window> new_popup_window(CreatePopupLikeWindow(
762        gfx::Rect(50, 100, 300, 150)));
763    EXPECT_EQ("50,100 300x150", new_popup_window->bounds().ToString());
764  }
765
766  browser_window->Hide();
767  { // If a window is there but not shown the default should be centered.
768    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
769        gfx::Rect(50, 100, 300, 150)));
770    EXPECT_EQ("650,100 300x150", new_browser_window->bounds().ToString());
771  }
772}
773
774// Test that adding a second window shifts both the first window and its
775// transient child.
776TEST_F(WorkspaceControllerTest, AutoPlacingMovesTransientChild) {
777  // Create an auto-positioned window.
778  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
779  gfx::Rect desktop_area = window1->parent()->bounds();
780  wm::GetWindowState(window1.get())->set_window_position_managed(true);
781  // Hide and then show |window1| to trigger auto-positioning logic.
782  window1->Hide();
783  window1->SetBounds(gfx::Rect(16, 32, 300, 300));
784  window1->Show();
785
786  // |window1| should be horizontally centered.
787  int x_window1 = (desktop_area.width() - 300) / 2;
788  EXPECT_EQ(base::IntToString(x_window1) + ",32 300x300",
789            window1->bounds().ToString());
790
791  // Create a |child| window and make it a transient child of |window1|.
792  scoped_ptr<Window> child(CreateTestWindowUnparented());
793  ::wm::AddTransientChild(window1.get(), child.get());
794  const int x_child = x_window1 + 50;
795  child->SetBounds(gfx::Rect(x_child, 20, 200, 200));
796  ParentWindowInPrimaryRootWindow(child.get());
797  child->Show();
798  wm::ActivateWindow(child.get());
799
800  // The |child| should be where it was created.
801  EXPECT_EQ(base::IntToString(x_child) + ",20 200x200",
802            child->bounds().ToString());
803
804  // Create and show a second window forcing the first window and its child to
805  // move.
806  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
807  wm::GetWindowState(window2.get())->set_window_position_managed(true);
808  // Hide and then show |window2| to trigger auto-positioning logic.
809  window2->Hide();
810  window2->SetBounds(gfx::Rect(32, 48, 250, 250));
811  window2->Show();
812
813  // Check that both |window1| and |child| have moved left.
814  EXPECT_EQ("0,32 300x300", window1->bounds().ToString());
815  int x = x_child - x_window1;
816  EXPECT_EQ(base::IntToString(x) + ",20 200x200", child->bounds().ToString());
817  // Check that |window2| has moved right.
818  x = desktop_area.width() - window2->bounds().width();
819  EXPECT_EQ(base::IntToString(x) + ",48 250x250", window2->bounds().ToString());
820}
821
822// Test the basic auto placement of one and or two windows in a "simulated
823// session" of sequential window operations.
824TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnShowHide) {
825  // Test 1: In case there is no manageable window, no window should shift.
826
827  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
828  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
829  gfx::Rect desktop_area = window1->parent()->bounds();
830
831  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
832  // Trigger the auto window placement function by making it visible.
833  // Note that the bounds are getting changed while it is invisible.
834  window2->Hide();
835  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
836  window2->Show();
837
838  // Check the initial position of the windows is unchanged.
839  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
840  EXPECT_EQ("32,48 256x512", window2->bounds().ToString());
841
842  // Remove the second window and make sure that the first window
843  // does NOT get centered.
844  window2.reset();
845  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
846
847  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
848  // Test 2: Set up two managed windows and check their auto positioning.
849  window1_state->set_window_position_managed(true);
850
851  scoped_ptr<aura::Window> window3(CreateTestWindowInShellWithId(2));
852  wm::GetWindowState(window3.get())->set_window_position_managed(true);
853  // To avoid any auto window manager changes due to SetBounds, the window
854  // gets first hidden and then shown again.
855  window3->Hide();
856  window3->SetBounds(gfx::Rect(32, 48, 256, 512));
857  window3->Show();
858  // |window1| should be flush left and |window3| flush right.
859  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
860  EXPECT_EQ(base::IntToString(
861                desktop_area.width() - window3->bounds().width()) +
862            ",48 256x512", window3->bounds().ToString());
863
864  // After removing |window3|, |window1| should be centered again.
865  window3.reset();
866  EXPECT_EQ(
867      base::IntToString(
868          (desktop_area.width() - window1->bounds().width()) / 2) +
869      ",32 640x320", window1->bounds().ToString());
870
871  // Test 3: Set up a manageable and a non manageable window and check
872  // positioning.
873  scoped_ptr<aura::Window> window4(CreateTestWindowInShellWithId(3));
874  // To avoid any auto window manager changes due to SetBounds, the window
875  // gets first hidden and then shown again.
876  window1->Hide();
877  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
878  window4->SetBounds(gfx::Rect(32, 48, 256, 512));
879  window1->Show();
880  // |window1| should be centered and |window4| untouched.
881  EXPECT_EQ(
882      base::IntToString(
883          (desktop_area.width() - window1->bounds().width()) / 2) +
884      ",32 640x320", window1->bounds().ToString());
885  EXPECT_EQ("32,48 256x512", window4->bounds().ToString());
886
887  // Test4: A single manageable window should get centered.
888  window4.reset();
889  window1_state->set_bounds_changed_by_user(false);
890  // Trigger the auto window placement function by showing (and hiding) it.
891  window1->Hide();
892  window1->Show();
893  // |window1| should be centered.
894  EXPECT_EQ(
895      base::IntToString(
896          (desktop_area.width() - window1->bounds().width()) / 2) +
897      ",32 640x320", window1->bounds().ToString());
898}
899
900// Test the proper usage of user window movement interaction.
901TEST_F(WorkspaceControllerTest, TestUserMovedWindowRepositioning) {
902  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
903  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
904  gfx::Rect desktop_area = window1->parent()->bounds();
905  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
906  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
907  window1->Hide();
908  window2->Hide();
909  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
910  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
911
912  window1_state->set_window_position_managed(true);
913  window2_state->set_window_position_managed(true);
914  EXPECT_FALSE(window1_state->bounds_changed_by_user());
915  EXPECT_FALSE(window2_state->bounds_changed_by_user());
916
917  // Check that the current location gets preserved if the user has
918  // positioned it previously.
919  window1_state->set_bounds_changed_by_user(true);
920  window1->Show();
921  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
922  // Flag should be still set.
923  EXPECT_TRUE(window1_state->bounds_changed_by_user());
924  EXPECT_FALSE(window2_state->bounds_changed_by_user());
925
926  // Turn on the second window and make sure that both windows are now
927  // positionable again (user movement cleared).
928  window2->Show();
929
930  // |window1| should be flush left and |window2| flush right.
931  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
932  EXPECT_EQ(
933      base::IntToString(desktop_area.width() - window2->bounds().width()) +
934      ",48 256x512", window2->bounds().ToString());
935  // FLag should now be reset.
936  EXPECT_FALSE(window1_state->bounds_changed_by_user());
937  EXPECT_FALSE(window2_state->bounds_changed_by_user());
938
939  // Going back to one shown window should keep the state.
940  window1_state->set_bounds_changed_by_user(true);
941  window2->Hide();
942  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
943  EXPECT_TRUE(window1_state->bounds_changed_by_user());
944}
945
946// Test if the single window will be restored at original position.
947TEST_F(WorkspaceControllerTest, TestSingleWindowsRestoredBounds) {
948  scoped_ptr<aura::Window> window1(
949      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100)));
950  scoped_ptr<aura::Window> window2(
951      CreateTestWindowInShellWithBounds(gfx::Rect(110, 110, 100, 100)));
952  scoped_ptr<aura::Window> window3(
953      CreateTestWindowInShellWithBounds(gfx::Rect(120, 120, 100, 100)));
954  window1->Hide();
955  window2->Hide();
956  window3->Hide();
957  wm::GetWindowState(window1.get())->set_window_position_managed(true);
958  wm::GetWindowState(window2.get())->set_window_position_managed(true);
959  wm::GetWindowState(window3.get())->set_window_position_managed(true);
960
961  window1->Show();
962  wm::ActivateWindow(window1.get());
963  window2->Show();
964  wm::ActivateWindow(window2.get());
965  window3->Show();
966  wm::ActivateWindow(window3.get());
967  EXPECT_EQ(0, window1->bounds().x());
968  EXPECT_EQ(window2->GetRootWindow()->bounds().right(),
969            window2->bounds().right());
970  EXPECT_EQ(0, window3->bounds().x());
971
972  window1->Hide();
973  EXPECT_EQ(window2->GetRootWindow()->bounds().right(),
974            window2->bounds().right());
975  EXPECT_EQ(0, window3->bounds().x());
976
977  // Being a single window will retore the original location.
978  window3->Hide();
979  wm::ActivateWindow(window2.get());
980  EXPECT_EQ("110,110 100x100", window2->bounds().ToString());
981
982  // Showing the 3rd will push the 2nd window left.
983  window3->Show();
984  wm::ActivateWindow(window3.get());
985  EXPECT_EQ(0, window2->bounds().x());
986  EXPECT_EQ(window3->GetRootWindow()->bounds().right(),
987            window3->bounds().right());
988
989  // Being a single window will retore the original location.
990  window2->Hide();
991  EXPECT_EQ("120,120 100x100", window3->bounds().ToString());
992}
993
994// Test that user placed windows go back to their user placement after the user
995// closes all other windows.
996TEST_F(WorkspaceControllerTest, TestUserHandledWindowRestore) {
997  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
998  gfx::Rect user_pos = gfx::Rect(16, 42, 640, 320);
999  window1->SetBounds(user_pos);
1000  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1001
1002  window1_state->SetPreAutoManageWindowBounds(user_pos);
1003  gfx::Rect desktop_area = window1->parent()->bounds();
1004
1005  // Create a second window to let the auto manager kick in.
1006  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1007  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
1008  window1->Hide();
1009  window2->Hide();
1010  wm::GetWindowState(window1.get())->set_window_position_managed(true);
1011  wm::GetWindowState(window2.get())->set_window_position_managed(true);
1012  window1->Show();
1013  EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
1014  window2->Show();
1015
1016  // |window1| should be flush left and |window2| flush right.
1017  EXPECT_EQ("0," + base::IntToString(user_pos.y()) +
1018            " 640x320", window1->bounds().ToString());
1019  EXPECT_EQ(
1020      base::IntToString(desktop_area.width() - window2->bounds().width()) +
1021      ",48 256x512", window2->bounds().ToString());
1022  window2->Hide();
1023
1024  // After the other window get hidden the window has to move back to the
1025  // previous position and the bounds should still be set and unchanged.
1026  EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
1027  ASSERT_TRUE(window1_state->pre_auto_manage_window_bounds());
1028  EXPECT_EQ(user_pos.ToString(),
1029            window1_state->pre_auto_manage_window_bounds()->ToString());
1030}
1031
1032// Solo window should be restored to the bounds where a user moved to.
1033TEST_F(WorkspaceControllerTest, TestRestoreToUserModifiedBounds) {
1034  if (!SupportsHostWindowResize())
1035    return;
1036
1037  UpdateDisplay("400x300");
1038  gfx::Rect default_bounds(10, 0, 100, 100);
1039  scoped_ptr<aura::Window> window1(
1040      CreateTestWindowInShellWithBounds(default_bounds));
1041  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1042  window1->Hide();
1043  window1_state->set_window_position_managed(true);
1044  window1->Show();
1045  // First window is centered.
1046  EXPECT_EQ("150,0 100x100", window1->bounds().ToString());
1047  scoped_ptr<aura::Window> window2(
1048      CreateTestWindowInShellWithBounds(default_bounds));
1049  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
1050  window2->Hide();
1051  window2_state->set_window_position_managed(true);
1052  window2->Show();
1053
1054  // Auto positioning pushes windows to each sides.
1055  EXPECT_EQ("0,0 100x100", window1->bounds().ToString());
1056  EXPECT_EQ("300,0 100x100", window2->bounds().ToString());
1057
1058  window2->Hide();
1059  // Restores to the center.
1060  EXPECT_EQ("150,0 100x100", window1->bounds().ToString());
1061
1062  // A user moved the window.
1063  scoped_ptr<WindowResizer> resizer(CreateWindowResizer(
1064      window1.get(),
1065      gfx::Point(),
1066      HTCAPTION,
1067      aura::client::WINDOW_MOVE_SOURCE_MOUSE).release());
1068  gfx::Point location = resizer->GetInitialLocation();
1069  location.Offset(-50, 0);
1070  resizer->Drag(location, 0);
1071  resizer->CompleteDrag();
1072
1073  window1_state->set_bounds_changed_by_user(true);
1074  window1->SetBounds(gfx::Rect(100, 0, 100, 100));
1075
1076  window2->Show();
1077  EXPECT_EQ("0,0 100x100", window1->bounds().ToString());
1078  EXPECT_EQ("300,0 100x100", window2->bounds().ToString());
1079
1080  // Window 1 should be restored to the user modified bounds.
1081  window2->Hide();
1082  EXPECT_EQ("100,0 100x100", window1->bounds().ToString());
1083}
1084
1085// Test that a window from normal to minimize will repos the remaining.
1086TEST_F(WorkspaceControllerTest, ToMinimizeRepositionsRemaining) {
1087  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
1088  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1089  window1_state->set_window_position_managed(true);
1090  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
1091  gfx::Rect desktop_area = window1->parent()->bounds();
1092
1093  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1094  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
1095  window2_state->set_window_position_managed(true);
1096  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
1097
1098  window1_state->Minimize();
1099
1100  // |window2| should be centered now.
1101  EXPECT_TRUE(window2->IsVisible());
1102  EXPECT_TRUE(window2_state->IsNormalStateType());
1103  EXPECT_EQ(base::IntToString(
1104                (desktop_area.width() - window2->bounds().width()) / 2) +
1105            ",48 256x512", window2->bounds().ToString());
1106
1107  window1_state->Restore();
1108  // |window1| should be flush right and |window3| flush left.
1109  EXPECT_EQ(base::IntToString(
1110                desktop_area.width() - window1->bounds().width()) +
1111            ",32 640x320", window1->bounds().ToString());
1112  EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
1113}
1114
1115// Test that minimizing an initially maximized window will repos the remaining.
1116TEST_F(WorkspaceControllerTest, MaxToMinRepositionsRemaining) {
1117  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
1118  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1119  window1_state->set_window_position_managed(true);
1120  gfx::Rect desktop_area = window1->parent()->bounds();
1121
1122  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1123  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
1124  window2_state->set_window_position_managed(true);
1125  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
1126
1127  window1_state->Maximize();
1128  window1_state->Minimize();
1129
1130  // |window2| should be centered now.
1131  EXPECT_TRUE(window2->IsVisible());
1132  EXPECT_TRUE(window2_state->IsNormalStateType());
1133  EXPECT_EQ(base::IntToString(
1134                (desktop_area.width() - window2->bounds().width()) / 2) +
1135            ",48 256x512", window2->bounds().ToString());
1136}
1137
1138// Test that nomral, maximize, minimizing will repos the remaining.
1139TEST_F(WorkspaceControllerTest, NormToMaxToMinRepositionsRemaining) {
1140  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
1141  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
1142  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1143  window1_state->set_window_position_managed(true);
1144  gfx::Rect desktop_area = window1->parent()->bounds();
1145
1146  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1147  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
1148  window2_state->set_window_position_managed(true);
1149  window2->SetBounds(gfx::Rect(32, 40, 256, 512));
1150
1151  // Trigger the auto window placement function by showing (and hiding) it.
1152  window1->Hide();
1153  window1->Show();
1154
1155  // |window1| should be flush right and |window3| flush left.
1156  EXPECT_EQ(base::IntToString(
1157                desktop_area.width() - window1->bounds().width()) +
1158            ",32 640x320", window1->bounds().ToString());
1159  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
1160
1161  window1_state->Maximize();
1162  window1_state->Minimize();
1163
1164  // |window2| should be centered now.
1165  EXPECT_TRUE(window2->IsVisible());
1166  EXPECT_TRUE(window2_state->IsNormalStateType());
1167  EXPECT_EQ(base::IntToString(
1168                (desktop_area.width() - window2->bounds().width()) / 2) +
1169            ",40 256x512", window2->bounds().ToString());
1170}
1171
1172// Test that nomral, maximize, normal will repos the remaining.
1173TEST_F(WorkspaceControllerTest, NormToMaxToNormRepositionsRemaining) {
1174  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
1175  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
1176  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
1177  window1_state->set_window_position_managed(true);
1178  gfx::Rect desktop_area = window1->parent()->bounds();
1179
1180  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1181  wm::GetWindowState(window2.get())->set_window_position_managed(true);
1182  window2->SetBounds(gfx::Rect(32, 40, 256, 512));
1183
1184  // Trigger the auto window placement function by showing (and hiding) it.
1185  window1->Hide();
1186  window1->Show();
1187
1188  // |window1| should be flush right and |window3| flush left.
1189  EXPECT_EQ(base::IntToString(
1190                desktop_area.width() - window1->bounds().width()) +
1191            ",32 640x320", window1->bounds().ToString());
1192  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
1193
1194  window1_state->Maximize();
1195  window1_state->Restore();
1196
1197  // |window1| should be flush right and |window2| flush left.
1198  EXPECT_EQ(base::IntToString(
1199                desktop_area.width() - window1->bounds().width()) +
1200            ",32 640x320", window1->bounds().ToString());
1201  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
1202}
1203
1204// Test that animations are triggered.
1205TEST_F(WorkspaceControllerTest, AnimatedNormToMaxToNormRepositionsRemaining) {
1206  ui::ScopedAnimationDurationScaleMode test_duration_mode(
1207      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
1208  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
1209  window1->Hide();
1210  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
1211  gfx::Rect desktop_area = window1->parent()->bounds();
1212  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
1213  window2->Hide();
1214  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
1215
1216  wm::GetWindowState(window1.get())->set_window_position_managed(true);
1217  wm::GetWindowState(window2.get())->set_window_position_managed(true);
1218  // Make sure nothing is animating.
1219  window1->layer()->GetAnimator()->StopAnimating();
1220  window2->layer()->GetAnimator()->StopAnimating();
1221  window2->Show();
1222
1223  // The second window should now animate.
1224  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
1225  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
1226  window2->layer()->GetAnimator()->StopAnimating();
1227
1228  window1->Show();
1229  EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
1230  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
1231
1232  window1->layer()->GetAnimator()->StopAnimating();
1233  window2->layer()->GetAnimator()->StopAnimating();
1234  // |window1| should be flush right and |window2| flush left.
1235  EXPECT_EQ(base::IntToString(
1236                desktop_area.width() - window1->bounds().width()) +
1237            ",32 640x320", window1->bounds().ToString());
1238  EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
1239}
1240
1241// This tests simulates a browser and an app and verifies the ordering of the
1242// windows and layers doesn't get out of sync as various operations occur. Its
1243// really testing code in FocusController, but easier to simulate here. Just as
1244// with a real browser the browser here has a transient child window
1245// (corresponds to the status bubble).
1246TEST_F(WorkspaceControllerTest, VerifyLayerOrdering) {
1247  scoped_ptr<Window> browser(aura::test::CreateTestWindowWithDelegate(
1248      NULL, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 6, 7, 8), NULL));
1249  browser->SetName("browser");
1250  ParentWindowInPrimaryRootWindow(browser.get());
1251  browser->Show();
1252  wm::ActivateWindow(browser.get());
1253
1254  // |status_bubble| is made a transient child of |browser| and as a result
1255  // owned by |browser|.
1256  aura::test::TestWindowDelegate* status_bubble_delegate =
1257      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
1258  status_bubble_delegate->set_can_focus(false);
1259  Window* status_bubble =
1260      aura::test::CreateTestWindowWithDelegate(status_bubble_delegate,
1261                                               ui::wm::WINDOW_TYPE_POPUP,
1262                                               gfx::Rect(5, 6, 7, 8),
1263                                               NULL);
1264  ::wm::AddTransientChild(browser.get(), status_bubble);
1265  ParentWindowInPrimaryRootWindow(status_bubble);
1266  status_bubble->SetName("status_bubble");
1267
1268  scoped_ptr<Window> app(aura::test::CreateTestWindowWithDelegate(
1269      NULL, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 6, 7, 8), NULL));
1270  app->SetName("app");
1271  ParentWindowInPrimaryRootWindow(app.get());
1272
1273  aura::Window* parent = browser->parent();
1274
1275  app->Show();
1276  wm::ActivateWindow(app.get());
1277  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1278
1279  // Minimize the app, focus should go the browser.
1280  app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
1281  EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
1282  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1283
1284  // Minimize the browser (neither windows are focused).
1285  browser->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
1286  EXPECT_FALSE(wm::IsActiveWindow(browser.get()));
1287  EXPECT_FALSE(wm::IsActiveWindow(app.get()));
1288  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1289
1290  // Show the browser (which should restore it).
1291  browser->Show();
1292  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1293
1294  // Activate the browser.
1295  ash::wm::ActivateWindow(browser.get());
1296  EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
1297  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1298
1299  // Restore the app. This differs from above code for |browser| as internally
1300  // the app code does this. Restoring this way or using Show() should not make
1301  // a difference.
1302  app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
1303  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1304
1305  // Activate the app.
1306  ash::wm::ActivateWindow(app.get());
1307  EXPECT_TRUE(wm::IsActiveWindow(app.get()));
1308  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
1309}
1310
1311namespace {
1312
1313// Used by DragMaximizedNonTrackedWindow to track how many times the window
1314// hierarchy changes affecting the specified window.
1315class DragMaximizedNonTrackedWindowObserver
1316    : public aura::WindowObserver {
1317 public:
1318  DragMaximizedNonTrackedWindowObserver(aura::Window* window)
1319  : change_count_(0),
1320    window_(window) {
1321  }
1322
1323  // Number of times OnWindowHierarchyChanged() has been received.
1324  void clear_change_count() { change_count_ = 0; }
1325  int change_count() const {
1326    return change_count_;
1327  }
1328
1329  // aura::WindowObserver overrides:
1330  // Counts number of times a window is reparented. Ignores reparenting into and
1331  // from a docked container which is expected when a tab is dragged.
1332  virtual void OnWindowHierarchyChanged(
1333      const HierarchyChangeParams& params) OVERRIDE {
1334    if (params.target != window_ ||
1335        (params.old_parent->id() == kShellWindowId_DefaultContainer &&
1336         params.new_parent->id() == kShellWindowId_DockedContainer) ||
1337        (params.old_parent->id() == kShellWindowId_DockedContainer &&
1338         params.new_parent->id() == kShellWindowId_DefaultContainer)) {
1339      return;
1340    }
1341    change_count_++;
1342  }
1343
1344 private:
1345  int change_count_;
1346  aura::Window* window_;
1347
1348  DISALLOW_COPY_AND_ASSIGN(DragMaximizedNonTrackedWindowObserver);
1349};
1350
1351}  // namespace
1352
1353// Verifies that a new maximized window becomes visible after its activation
1354// is requested, even though it does not become activated because a system
1355// modal window is active.
1356TEST_F(WorkspaceControllerTest, SwitchFromModal) {
1357  scoped_ptr<Window> modal_window(CreateTestWindowUnparented());
1358  modal_window->SetBounds(gfx::Rect(10, 11, 21, 22));
1359  modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
1360  ParentWindowInPrimaryRootWindow(modal_window.get());
1361  modal_window->Show();
1362  wm::ActivateWindow(modal_window.get());
1363
1364  scoped_ptr<Window> maximized_window(CreateTestWindow());
1365  maximized_window->SetProperty(
1366      aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
1367  maximized_window->Show();
1368  wm::ActivateWindow(maximized_window.get());
1369  EXPECT_TRUE(maximized_window->IsVisible());
1370}
1371
1372namespace {
1373
1374// Subclass of WorkspaceControllerTest that runs tests with docked windows
1375// enabled and disabled.
1376class WorkspaceControllerTestDragging : public WorkspaceControllerTest {
1377 public:
1378  WorkspaceControllerTestDragging() {}
1379  virtual ~WorkspaceControllerTestDragging() {}
1380
1381 private:
1382  DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTestDragging);
1383};
1384
1385}  // namespace
1386
1387// Verifies that when dragging a window over the shelf overlap is detected
1388// during and after the drag.
1389TEST_F(WorkspaceControllerTestDragging, DragWindowOverlapShelf) {
1390  aura::test::TestWindowDelegate delegate;
1391  delegate.set_window_component(HTCAPTION);
1392  scoped_ptr<Window> w1(aura::test::CreateTestWindowWithDelegate(
1393      &delegate, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 5, 100, 50), NULL));
1394  ParentWindowInPrimaryRootWindow(w1.get());
1395
1396  ShelfLayoutManager* shelf = shelf_layout_manager();
1397  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
1398
1399  // Drag near the shelf.
1400  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
1401                                     gfx::Point());
1402  generator.MoveMouseTo(10, 10);
1403  generator.PressLeftButton();
1404  generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 70);
1405
1406  // Shelf should not be in overlapped state.
1407  EXPECT_FALSE(GetWindowOverlapsShelf());
1408
1409  generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 20);
1410
1411  // Shelf should detect overlap. Overlap state stays after mouse is released.
1412  EXPECT_TRUE(GetWindowOverlapsShelf());
1413  generator.ReleaseLeftButton();
1414  EXPECT_TRUE(GetWindowOverlapsShelf());
1415}
1416
1417// Verifies that when dragging a window autohidden shelf stays hidden during
1418// and after the drag.
1419TEST_F(WorkspaceControllerTestDragging, DragWindowKeepsShelfAutohidden) {
1420  aura::test::TestWindowDelegate delegate;
1421  delegate.set_window_component(HTCAPTION);
1422  scoped_ptr<Window> w1(aura::test::CreateTestWindowWithDelegate(
1423      &delegate, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 5, 100, 50), NULL));
1424  ParentWindowInPrimaryRootWindow(w1.get());
1425
1426  ShelfLayoutManager* shelf = shelf_layout_manager();
1427  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
1428  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
1429
1430  // Drag very little.
1431  ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(),
1432                                     gfx::Point());
1433  generator.MoveMouseTo(10, 10);
1434  generator.PressLeftButton();
1435  generator.MoveMouseTo(12, 12);
1436
1437  // Shelf should be hidden during and after the drag.
1438  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
1439  generator.ReleaseLeftButton();
1440  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
1441}
1442
1443// Verifies that events are targeted properly just outside the window edges.
1444TEST_F(WorkspaceControllerTest, WindowEdgeHitTest) {
1445  aura::test::TestWindowDelegate d_first, d_second;
1446  scoped_ptr<Window> first(aura::test::CreateTestWindowWithDelegate(&d_first,
1447      123, gfx::Rect(20, 10, 100, 50), NULL));
1448  ParentWindowInPrimaryRootWindow(first.get());
1449  first->Show();
1450
1451  scoped_ptr<Window> second(aura::test::CreateTestWindowWithDelegate(&d_second,
1452      234, gfx::Rect(30, 40, 40, 10), NULL));
1453  ParentWindowInPrimaryRootWindow(second.get());
1454  second->Show();
1455
1456  ui::EventTarget* root = first->GetRootWindow();
1457  ui::EventTargeter* targeter = root->GetEventTargeter();
1458
1459  // The windows overlap, and |second| is on top of |first|. Events targeted
1460  // slightly outside the edges of the |second| window should still be targeted
1461  // to |second| to allow resizing the windows easily.
1462
1463  const int kNumPoints = 4;
1464  struct {
1465    const char* direction;
1466    gfx::Point location;
1467  } points[kNumPoints] = {
1468    { "left", gfx::Point(28, 45) },  // outside the left edge.
1469    { "top", gfx::Point(50, 38) },  // outside the top edge.
1470    { "right", gfx::Point(72, 45) },  // outside the right edge.
1471    { "bottom", gfx::Point(50, 52) },  // outside the bottom edge.
1472  };
1473  // Do two iterations, first without any transform on |second|, and the second
1474  // time after applying some transform on |second| so that it doesn't get
1475  // targeted.
1476  for (int times = 0; times < 2; ++times) {
1477    SCOPED_TRACE(times == 0 ? "Without transform" : "With transform");
1478    aura::Window* expected_target = times == 0 ? second.get() : first.get();
1479    for (int i = 0; i < kNumPoints; ++i) {
1480      SCOPED_TRACE(points[i].direction);
1481      const gfx::Point& location = points[i].location;
1482      ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
1483                           ui::EF_NONE);
1484      ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
1485      EXPECT_EQ(expected_target, target);
1486
1487      ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
1488                           ui::EventTimeForNow());
1489      target = targeter->FindTargetForEvent(root, &touch);
1490      EXPECT_EQ(expected_target, target);
1491    }
1492    // Apply a transform on |second|. After the transform is applied, the window
1493    // should no longer be targeted.
1494    gfx::Transform transform;
1495    transform.Translate(70, 40);
1496    second->SetTransform(transform);
1497  }
1498}
1499
1500// Verifies mouse event targeting just outside the window edges for panels.
1501TEST_F(WorkspaceControllerTest, WindowEdgeMouseHitTestPanel) {
1502  aura::test::TestWindowDelegate delegate;
1503  scoped_ptr<Window> window(CreateTestPanel(&delegate,
1504                                           gfx::Rect(20, 10, 100, 50)));
1505  ui::EventTarget* root = window->GetRootWindow();
1506  ui::EventTargeter* targeter = root->GetEventTargeter();
1507  const gfx::Rect bounds = window->bounds();
1508  const int kNumPoints = 5;
1509  struct {
1510    const char* direction;
1511    gfx::Point location;
1512    bool is_target_hit;
1513  } points[kNumPoints] = {
1514    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
1515    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
1516    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
1517    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), true },
1518    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
1519  };
1520  for (int i = 0; i < kNumPoints; ++i) {
1521    SCOPED_TRACE(points[i].direction);
1522    const gfx::Point& location = points[i].location;
1523    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
1524                         ui::EF_NONE);
1525    ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
1526    if (points[i].is_target_hit)
1527      EXPECT_EQ(window.get(), target);
1528    else
1529      EXPECT_NE(window.get(), target);
1530  }
1531}
1532
1533// Verifies touch event targeting just outside the window edges for panels.
1534// The shelf is aligned to the bottom by default, and so touches just below
1535// the bottom edge of the panel should not target the panel itself because
1536// an AttachedPanelWindowTargeter is installed on the panel container.
1537TEST_F(WorkspaceControllerTest, WindowEdgeTouchHitTestPanel) {
1538  aura::test::TestWindowDelegate delegate;
1539  scoped_ptr<Window> window(CreateTestPanel(&delegate,
1540                                            gfx::Rect(20, 10, 100, 50)));
1541  ui::EventTarget* root = window->GetRootWindow();
1542  ui::EventTargeter* targeter = root->GetEventTargeter();
1543  const gfx::Rect bounds = window->bounds();
1544  const int kNumPoints = 5;
1545  struct {
1546    const char* direction;
1547    gfx::Point location;
1548    bool is_target_hit;
1549  } points[kNumPoints] = {
1550    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
1551    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
1552    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
1553    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), false },
1554    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
1555  };
1556  for (int i = 0; i < kNumPoints; ++i) {
1557    SCOPED_TRACE(points[i].direction);
1558    const gfx::Point& location = points[i].location;
1559    ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
1560                         ui::EventTimeForNow());
1561    ui::EventTarget* target = targeter->FindTargetForEvent(root, &touch);
1562    if (points[i].is_target_hit)
1563      EXPECT_EQ(window.get(), target);
1564    else
1565      EXPECT_NE(window.get(), target);
1566  }
1567}
1568
1569// Verifies events targeting just outside the window edges for docked windows.
1570TEST_F(WorkspaceControllerTest, WindowEdgeHitTestDocked) {
1571  aura::test::TestWindowDelegate delegate;
1572  // Make window smaller than the minimum docked area so that the window edges
1573  // are exposed.
1574  delegate.set_maximum_size(gfx::Size(180, 200));
1575  scoped_ptr<Window> window(aura::test::CreateTestWindowWithDelegate(&delegate,
1576      123, gfx::Rect(20, 10, 100, 50), NULL));
1577  ParentWindowInPrimaryRootWindow(window.get());
1578  aura::Window* docked_container = Shell::GetContainer(
1579      window->GetRootWindow(), kShellWindowId_DockedContainer);
1580  docked_container->AddChild(window.get());
1581  window->Show();
1582  ui::EventTarget* root = window->GetRootWindow();
1583  ui::EventTargeter* targeter = root->GetEventTargeter();
1584  const gfx::Rect bounds = window->bounds();
1585  const int kNumPoints = 5;
1586  struct {
1587    const char* direction;
1588    gfx::Point location;
1589    bool is_target_hit;
1590  } points[kNumPoints] = {
1591    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
1592    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
1593    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
1594    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), true },
1595    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
1596  };
1597  for (int i = 0; i < kNumPoints; ++i) {
1598    SCOPED_TRACE(points[i].direction);
1599    const gfx::Point& location = points[i].location;
1600    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
1601                         ui::EF_NONE);
1602    ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
1603    if (points[i].is_target_hit)
1604      EXPECT_EQ(window.get(), target);
1605    else
1606      EXPECT_NE(window.get(), target);
1607
1608    ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
1609                         ui::EventTimeForNow());
1610    target = targeter->FindTargetForEvent(root, &touch);
1611    if (points[i].is_target_hit)
1612      EXPECT_EQ(window.get(), target);
1613    else
1614      EXPECT_NE(window.get(), target);
1615  }
1616}
1617
1618}  // namespace ash
1619