diff options
Diffstat (limited to 'chromium/ash/wm/workspace_controller_unittest.cc')
-rw-r--r-- | chromium/ash/wm/workspace_controller_unittest.cc | 1316 |
1 files changed, 1316 insertions, 0 deletions
diff --git a/chromium/ash/wm/workspace_controller_unittest.cc b/chromium/ash/wm/workspace_controller_unittest.cc new file mode 100644 index 00000000000..dbf114e0d0e --- /dev/null +++ b/chromium/ash/wm/workspace_controller_unittest.cc @@ -0,0 +1,1316 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ash/wm/workspace_controller.h" + +#include <map> + +#include "ash/ash_switches.h" +#include "ash/root_window_controller.h" +#include "ash/screen_ash.h" +#include "ash/shelf/shelf_layout_manager.h" +#include "ash/shelf/shelf_widget.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/system/status_area_widget.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/shell_test_api.h" +#include "ash/wm/window_state.h" +#include "ash/wm/window_util.h" +#include "base/command_line.h" +#include "base/strings/string_number_conversions.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/test/test_window_delegate.h" +#include "ui/aura/test/test_windows.h" +#include "ui/aura/window.h" +#include "ui/base/hit_test.h" +#include "ui/base/ui_base_types.h" +#include "ui/compositor/layer.h" +#include "ui/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/gfx/screen.h" +#include "ui/views/corewm/window_animations.h" +#include "ui/views/widget/widget.h" + +using aura::Window; + +namespace ash { +namespace internal { + +// Returns a string containing the names of all the children of |window| (in +// order). Each entry is separated by a space. +std::string GetWindowNames(const aura::Window* window) { + std::string result; + for (size_t i = 0; i < window->children().size(); ++i) { + if (i != 0) + result += " "; + result += window->children()[i]->name(); + } + return result; +} + +// Returns a string containing the names of windows corresponding to each of the +// child layers of |window|'s layer. Any layers that don't correspond to a child +// Window of |window| are ignored. The result is ordered based on the layer +// ordering. +std::string GetLayerNames(const aura::Window* window) { + typedef std::map<const ui::Layer*, std::string> LayerToWindowNameMap; + LayerToWindowNameMap window_names; + for (size_t i = 0; i < window->children().size(); ++i) { + window_names[window->children()[i]->layer()] = + window->children()[i]->name(); + } + + std::string result; + const std::vector<ui::Layer*>& layers(window->layer()->children()); + for (size_t i = 0; i < layers.size(); ++i) { + LayerToWindowNameMap::iterator layer_i = + window_names.find(layers[i]); + if (layer_i != window_names.end()) { + if (!result.empty()) + result += " "; + result += layer_i->second; + } + } + return result; +} + +class WorkspaceControllerTest : public test::AshTestBase { + public: + WorkspaceControllerTest() {} + virtual ~WorkspaceControllerTest() {} + + aura::Window* CreateTestWindowUnparented() { + aura::Window* window = new aura::Window(NULL); + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + return window; + } + + aura::Window* CreateTestWindow() { + aura::Window* window = new aura::Window(NULL); + window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + ParentWindowInPrimaryRootWindow(window); + return window; + } + + aura::Window* CreateBrowserLikeWindow(const gfx::Rect& bounds) { + aura::Window* window = CreateTestWindow(); + window->SetBounds(bounds); + wm::WindowState* window_state = wm::GetWindowState(window); + window_state->set_window_position_managed(true); + window->Show(); + return window; + } + + aura::Window* CreatePopupLikeWindow(const gfx::Rect& bounds) { + aura::Window* window = CreateTestWindowInShellWithBounds(bounds); + window->Show(); + return window; + } + + aura::Window* GetDesktop() { + return Shell::GetContainer(Shell::GetPrimaryRootWindow(), + kShellWindowId_DefaultContainer); + } + + gfx::Rect GetFullscreenBounds(aura::Window* window) { + return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds(); + } + + ShelfWidget* shelf_widget() { + return Shell::GetPrimaryRootWindowController()->shelf(); + } + + ShelfLayoutManager* shelf_layout_manager() { + return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager(); + } + + bool GetWindowOverlapsShelf() { + return shelf_layout_manager()->window_overlaps_shelf(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTest); +}; + +// Assertions around adding a normal window. +TEST_F(WorkspaceControllerTest, AddNormalWindowWhenEmpty) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + + wm::WindowState* window_state = wm::GetWindowState(w1.get()); + + EXPECT_FALSE(window_state->HasRestoreBounds()); + + w1->Show(); + + EXPECT_FALSE(window_state->HasRestoreBounds()); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); +} + +// Assertions around maximizing/unmaximizing. +TEST_F(WorkspaceControllerTest, SingleMaximizeWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + + w1->Show(); + wm::ActivateWindow(w1.get()); + + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + // Maximize the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + + EXPECT_TRUE(wm::IsActiveWindow(w1.get())); + + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).width(), + w1->bounds().width()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get()).height(), + w1->bounds().height()); + + // Restore the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); +} + +// Assertions around two windows and toggling one to be fullscreen. +TEST_F(WorkspaceControllerTest, FullscreenWithNormalWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + w1->Show(); + + ASSERT_TRUE(w1->layer() != NULL); + EXPECT_TRUE(w1->layer()->visible()); + + w2->SetBounds(gfx::Rect(0, 0, 50, 51)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Both windows should be in the same workspace. + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(w2.get(), GetDesktop()->children()[1]); + + gfx::Rect work_area( + ScreenAsh::GetMaximizedWindowBoundsInParent(w1.get())); + EXPECT_EQ(work_area.width(), w2->bounds().width()); + EXPECT_EQ(work_area.height(), w2->bounds().height()); + + // Restore w2, which should then go back to one workspace. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(50, w2->bounds().width()); + EXPECT_EQ(51, w2->bounds().height()); + EXPECT_TRUE(wm::IsActiveWindow(w2.get())); +} + +// Makes sure requests to change the bounds of a normal window go through. +TEST_F(WorkspaceControllerTest, ChangeBoundsOfNormalWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + + // Setting the bounds should go through since the window is in the normal + // workspace. + w1->SetBounds(gfx::Rect(0, 0, 200, 500)); + EXPECT_EQ(200, w1->bounds().width()); + EXPECT_EQ(500, w1->bounds().height()); +} + +// Verifies the bounds is not altered when showing and grid is enabled. +TEST_F(WorkspaceControllerTest, SnapToGrid) { + scoped_ptr<Window> w1(CreateTestWindowUnparented()); + w1->SetBounds(gfx::Rect(1, 6, 25, 30)); + ParentWindowInPrimaryRootWindow(w1.get()); + // We are not aligning this anymore this way. When the window gets shown + // the window is expected to be handled differently, but this cannot be + // tested with this test. So the result of this test should be that the + // bounds are exactly as passed in. + EXPECT_EQ("1,6 25x30", w1->bounds().ToString()); +} + +// Assertions around a fullscreen window. +TEST_F(WorkspaceControllerTest, SingleFullscreenWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->SetBounds(gfx::Rect(0, 0, 250, 251)); + // Make the window fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + w1->Show(); + wm::ActivateWindow(w1.get()); + + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width()); + EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height()); + + // Restore the window. Use SHOW_STATE_DEFAULT as that is what we'll end up + // with when using views::Widget. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_DEFAULT); + EXPECT_EQ("0,0 250x251", w1->bounds().ToString()); + + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(250, w1->bounds().width()); + EXPECT_EQ(251, w1->bounds().height()); + + // Back to fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width()); + EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height()); + wm::WindowState* window_state = wm::GetWindowState(w1.get()); + + ASSERT_TRUE(window_state->HasRestoreBounds()); + EXPECT_EQ("0,0 250x251", window_state->GetRestoreBoundsInScreen().ToString()); +} + +// Assertions around minimizing a single window. +TEST_F(WorkspaceControllerTest, MinimizeSingleWindow) { + scoped_ptr<Window> w1(CreateTestWindow()); + + w1->Show(); + + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_FALSE(w1->layer()->IsDrawn()); + + // Show the window. + w1->Show(); + EXPECT_TRUE(wm::GetWindowState(w1.get())->IsNormalShowState()); + EXPECT_TRUE(w1->layer()->IsDrawn()); +} + +// Assertions around minimizing a fullscreen window. +TEST_F(WorkspaceControllerTest, MinimizeFullscreenWindow) { + // Two windows, w1 normal, w2 fullscreen. + scoped_ptr<Window> w1(CreateTestWindow()); + scoped_ptr<Window> w2(CreateTestWindow()); + w1->Show(); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + w2->Show(); + + wm::WindowState* w1_state = wm::GetWindowState(w1.get()); + wm::WindowState* w2_state = wm::GetWindowState(w2.get()); + + w2_state->Activate(); + + // Minimize w2. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_TRUE(w1->layer()->IsDrawn()); + EXPECT_FALSE(w2->layer()->IsDrawn()); + + // Show the window, which should trigger unminimizing. + w2->Show(); + w2_state->Activate(); + + EXPECT_TRUE(w2_state->IsFullscreen()); + EXPECT_TRUE(w1->layer()->IsDrawn()); + EXPECT_TRUE(w2->layer()->IsDrawn()); + + // Minimize the window, which should hide the window. + EXPECT_TRUE(w2_state->IsActive()); + w2_state->Minimize(); + EXPECT_FALSE(w2_state->IsActive()); + EXPECT_FALSE(w2->layer()->IsDrawn()); + EXPECT_TRUE(w1_state->IsActive()); + + // Make the window normal. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(w1.get(), GetDesktop()->children()[0]); + EXPECT_EQ(w2.get(), GetDesktop()->children()[1]); + EXPECT_TRUE(w2->layer()->IsDrawn()); +} + +// Verifies ShelfLayoutManager's visibility/auto-hide state is correctly +// updated. +TEST_F(WorkspaceControllerTest, ShelfStateUpdated) { + // Since ShelfLayoutManager queries for mouse location, move the mouse so + // it isn't over the shelf. + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow(), gfx::Point()); + generator.MoveMouseTo(0, 0); + + scoped_ptr<Window> w1(CreateTestWindow()); + const gfx::Rect w1_bounds(0, 1, 101, 102); + ShelfLayoutManager* shelf = shelf_layout_manager(); + shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); + const gfx::Rect touches_shelf_bounds( + 0, shelf->GetIdealBounds().y() - 10, 101, 102); + // Move |w1| to overlap the shelf. + w1->SetBounds(touches_shelf_bounds); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // A visible ignored window should not trigger the overlap. + scoped_ptr<Window> w_ignored(CreateTestWindow()); + w_ignored->SetBounds(touches_shelf_bounds); + wm::GetWindowState(&(*w_ignored))->set_ignored_by_shelf(true); + w_ignored->Show(); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Make it visible, since visible shelf overlaps should be true. + w1->Show(); + EXPECT_TRUE(GetWindowOverlapsShelf()); + + wm::ActivateWindow(w1.get()); + w1->SetBounds(w1_bounds); + w1->Show(); + wm::ActivateWindow(w1.get()); + + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + + // Maximize the window. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + + // Restore. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Fullscreen. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state()); + + // Normal. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Move window so it obscures shelf. + w1->SetBounds(touches_shelf_bounds); + EXPECT_TRUE(GetWindowOverlapsShelf()); + + // Move it back. + w1->SetBounds(w1_bounds); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Maximize again. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + + // Minimize. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + + // Since the restore from minimize will restore to the pre-minimize + // state (tested elsewhere), we abandon the current size and restore + // rect and set them to the window. + wm::WindowState* window_state = wm::GetWindowState(w1.get()); + + gfx::Rect restore = window_state->GetRestoreBoundsInScreen(); + EXPECT_EQ("0,0 800x597", w1->bounds().ToString()); + EXPECT_EQ("0,1 101x102", restore.ToString()); + window_state->ClearRestoreBounds(); + w1->SetBounds(restore); + + // Restore. + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Create another window, maximized. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetBounds(gfx::Rect(10, 11, 250, 251)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + + // Switch to w1. + wm::ActivateWindow(w1.get()); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent( + w2->parent()).ToString(), + w2->bounds().ToString()); + + // Switch to w2. + wm::ActivateWindow(w2.get()); + EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state()); + EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state()); + EXPECT_EQ("0,1 101x102", w1->bounds().ToString()); + EXPECT_EQ(ScreenAsh::GetMaximizedWindowBoundsInParent(w2.get()).ToString(), + w2->bounds().ToString()); + + // Turn off auto-hide, switch back to w2 (maximized) and verify overlap. + shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); + wm::ActivateWindow(w2.get()); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Move w1 to overlap shelf, it shouldn't change window overlaps shelf since + // the window isn't in the visible workspace. + w1->SetBounds(touches_shelf_bounds); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Activate w1. Although w1 is visible, the overlap state is still false since + // w2 is maximized. + wm::ActivateWindow(w1.get()); + EXPECT_FALSE(GetWindowOverlapsShelf()); + + // Restore w2. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_TRUE(GetWindowOverlapsShelf()); +} + +// Verifies going from maximized to minimized sets the right state for painting +// the background of the launcher. +TEST_F(WorkspaceControllerTest, MinimizeResetsVisibility) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + wm::ActivateWindow(w1.get()); + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, shelf_widget()->GetBackgroundType()); + + w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_EQ(SHELF_VISIBLE, + shelf_layout_manager()->visibility_state()); + EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, shelf_widget()->GetBackgroundType()); +} + +// Verifies window visibility during various workspace changes. +TEST_F(WorkspaceControllerTest, VisibilityTests) { + scoped_ptr<Window> w1(CreateTestWindow()); + w1->Show(); + EXPECT_TRUE(w1->IsVisible()); + EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity()); + + // Create another window, activate it and make it fullscreen. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->Show(); + wm::ActivateWindow(w2.get()); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w1->IsVisible()); + + // Switch to w1. |w1| should be visible on top of |w2|. + wm::ActivateWindow(w1.get()); + EXPECT_TRUE(w1->IsVisible()); + EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w2->IsVisible()); + + // Switch back to |w2|. + wm::ActivateWindow(w2.get()); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w1->IsVisible()); + + // Restore |w2|, both windows should be visible. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_TRUE(w1->IsVisible()); + EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity()); + + // Make |w2| fullscreen again, then close it. + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN); + w2->Hide(); + EXPECT_FALSE(w2->IsVisible()); + EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w1->IsVisible()); + + // Create |w2| and maximize it. + w2.reset(CreateTestWindow()); + w2->Show(); + wm::ActivateWindow(w2.get()); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + EXPECT_TRUE(w2->IsVisible()); + EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w1->IsVisible()); + + // Close |w2|. + w2.reset(); + EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity()); + EXPECT_TRUE(w1->IsVisible()); +} + +// Verifies windows that are offscreen don't move when switching workspaces. +TEST_F(WorkspaceControllerTest, DontMoveOnSwitch) { + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow(), gfx::Point()); + generator.MoveMouseTo(0, 0); + + scoped_ptr<Window> w1(CreateTestWindow()); + ShelfLayoutManager* shelf = shelf_layout_manager(); + const gfx::Rect touches_shelf_bounds( + 0, shelf->GetIdealBounds().y() - 10, 101, 102); + // Move |w1| to overlap the shelf. + w1->SetBounds(touches_shelf_bounds); + w1->Show(); + wm::ActivateWindow(w1.get()); + + // Create another window and maximize it. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetBounds(gfx::Rect(10, 11, 250, 251)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Switch to w1. + wm::ActivateWindow(w1.get()); + EXPECT_EQ(touches_shelf_bounds.ToString(), w1->bounds().ToString()); +} + +// Verifies that windows that are completely offscreen move when switching +// workspaces. +TEST_F(WorkspaceControllerTest, MoveOnSwitch) { + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow(), gfx::Point()); + generator.MoveMouseTo(0, 0); + + scoped_ptr<Window> w1(CreateTestWindow()); + ShelfLayoutManager* shelf = shelf_layout_manager(); + const gfx::Rect w1_bounds(0, shelf->GetIdealBounds().y(), 100, 200); + // Move |w1| so that the top edge is the same as the top edge of the shelf. + w1->SetBounds(w1_bounds); + w1->Show(); + wm::ActivateWindow(w1.get()); + EXPECT_EQ(w1_bounds.ToString(), w1->bounds().ToString()); + + // Create another window and maximize it. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetBounds(gfx::Rect(10, 11, 250, 251)); + w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Increase the size of the WorkAreaInsets. This would make |w1| fall + // completely out of the display work area. + gfx::Insets insets = + Shell::GetScreen()->GetPrimaryDisplay().GetWorkAreaInsets(); + insets.Set(0, 0, insets.bottom() + 30, 0); + Shell::GetInstance()->SetDisplayWorkAreaInsets(w1.get(), insets); + + // Switch to w1. The window should have moved. + wm::ActivateWindow(w1.get()); + EXPECT_NE(w1_bounds.ToString(), w1->bounds().ToString()); +} + +namespace { + +// WindowDelegate used by DontCrashOnChangeAndActivate. +class DontCrashOnChangeAndActivateDelegate + : public aura::test::TestWindowDelegate { + public: + DontCrashOnChangeAndActivateDelegate() : window_(NULL) {} + + void set_window(aura::Window* window) { window_ = window; } + + // WindowDelegate overrides: + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE { + if (window_) { + wm::ActivateWindow(window_); + window_ = NULL; + } + } + + private: + aura::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(DontCrashOnChangeAndActivateDelegate); +}; + +} // namespace + +// Exercises possible crash in W2. Here's the sequence: +// . minimize a maximized window. +// . remove the window (which happens when switching displays). +// . add the window back. +// . show the window and during the bounds change activate it. +TEST_F(WorkspaceControllerTest, DontCrashOnChangeAndActivate) { + // Force the shelf + ShelfLayoutManager* shelf = shelf_layout_manager(); + shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); + + DontCrashOnChangeAndActivateDelegate delegate; + scoped_ptr<Window> w1(CreateTestWindowInShellWithDelegate( + &delegate, 1000, gfx::Rect(10, 11, 250, 251))); + + w1->Show(); + wm::WindowState* w1_state = wm::GetWindowState(w1.get()); + w1_state->Activate(); + w1_state->Maximize(); + w1_state->Minimize(); + + w1->parent()->RemoveChild(w1.get()); + + // Do this so that when we Show() the window a resize occurs and we make the + // window active. + shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS); + + ParentWindowInPrimaryRootWindow(w1.get()); + delegate.set_window(w1.get()); + w1->Show(); +} + +// Verifies a window with a transient parent not managed by workspace works. +TEST_F(WorkspaceControllerTest, TransientParent) { + // Normal window with no transient parent. + scoped_ptr<Window> w2(CreateTestWindow()); + w2->SetBounds(gfx::Rect(10, 11, 250, 251)); + w2->Show(); + wm::ActivateWindow(w2.get()); + + // Window with a transient parent. We set the transient parent to the root, + // which would never happen but is enough to exercise the bug. + scoped_ptr<Window> w1(CreateTestWindowUnparented()); + Shell::GetInstance()->GetPrimaryRootWindow()->AddTransientChild(w1.get()); + w1->SetBounds(gfx::Rect(10, 11, 250, 251)); + ParentWindowInPrimaryRootWindow(w1.get()); + w1->Show(); + wm::ActivateWindow(w1.get()); + + // The window with the transient parent should get added to the same parent as + // the normal window. + EXPECT_EQ(w2->parent(), w1->parent()); +} + +// Test the placement of newly created windows. +TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnCreate) { + if (!SupportsHostWindowResize()) + return; + UpdateDisplay("1600x1200"); + // Creating a popup handler here to make sure it does not interfere with the + // existing windows. + gfx::Rect source_browser_bounds(16, 32, 640, 320); + scoped_ptr<aura::Window> browser_window(CreateBrowserLikeWindow( + source_browser_bounds)); + + // Creating a popup to make sure it does not interfere with the positioning. + scoped_ptr<aura::Window> browser_popup(CreatePopupLikeWindow( + gfx::Rect(16, 32, 128, 256))); + + browser_window->Show(); + browser_popup->Show(); + + { // With a shown window it's size should get returned. + scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow( + source_browser_bounds)); + // The position should be right flush. + EXPECT_EQ("960,32 640x320", new_browser_window->bounds().ToString()); + } + + { // With the window shown - but more on the right side then on the left + // side (and partially out of the screen), it should default to the other + // side and inside the screen. + gfx::Rect source_browser_bounds(gfx::Rect(1000, 600, 640, 320)); + browser_window->SetBounds(source_browser_bounds); + + scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow( + source_browser_bounds)); + // The position should be left & bottom flush. + EXPECT_EQ("0,600 640x320", new_browser_window->bounds().ToString()); + + // If the other window was already beyond the point to get right flush + // it will remain where it is. + EXPECT_EQ("1000,600 640x320", browser_window->bounds().ToString()); + } + + { // Make sure that popups do not get changed. + scoped_ptr<aura::Window> new_popup_window(CreatePopupLikeWindow( + gfx::Rect(50, 100, 300, 150))); + EXPECT_EQ("50,100 300x150", new_popup_window->bounds().ToString()); + } + + browser_window->Hide(); + { // If a window is there but not shown the default should be centered. + scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow( + gfx::Rect(50, 100, 300, 150))); + EXPECT_EQ("650,100 300x150", new_browser_window->bounds().ToString()); + } +} + +// Test the basic auto placement of one and or two windows in a "simulated +// session" of sequential window operations. +TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnShowHide) { + // Test 1: In case there is no manageable window, no window should shift. + + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + gfx::Rect desktop_area = window1->parent()->bounds(); + + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + // Trigger the auto window placement function by making it visible. + // Note that the bounds are getting changed while it is invisible. + window2->Hide(); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + window2->Show(); + + // Check the initial position of the windows is unchanged. + EXPECT_EQ("16,32 640x320", window1->bounds().ToString()); + EXPECT_EQ("32,48 256x512", window2->bounds().ToString()); + + // Remove the second window and make sure that the first window + // does NOT get centered. + window2.reset(); + EXPECT_EQ("16,32 640x320", window1->bounds().ToString()); + + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + // Test 2: Set up two managed windows and check their auto positioning. + window1_state->set_window_position_managed(true); + + scoped_ptr<aura::Window> window3(CreateTestWindowInShellWithId(2)); + wm::GetWindowState(window3.get())->set_window_position_managed(true); + // To avoid any auto window manager changes due to SetBounds, the window + // gets first hidden and then shown again. + window3->Hide(); + window3->SetBounds(gfx::Rect(32, 48, 256, 512)); + window3->Show(); + // |window1| should be flush left and |window3| flush right. + EXPECT_EQ("0,32 640x320", window1->bounds().ToString()); + EXPECT_EQ(base::IntToString( + desktop_area.width() - window3->bounds().width()) + + ",48 256x512", window3->bounds().ToString()); + + // After removing |window3|, |window1| should be centered again. + window3.reset(); + EXPECT_EQ( + base::IntToString( + (desktop_area.width() - window1->bounds().width()) / 2) + + ",32 640x320", window1->bounds().ToString()); + + // Test 3: Set up a manageable and a non manageable window and check + // positioning. + scoped_ptr<aura::Window> window4(CreateTestWindowInShellWithId(3)); + // To avoid any auto window manager changes due to SetBounds, the window + // gets first hidden and then shown again. + window1->Hide(); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + window4->SetBounds(gfx::Rect(32, 48, 256, 512)); + window1->Show(); + // |window1| should be centered and |window4| untouched. + EXPECT_EQ( + base::IntToString( + (desktop_area.width() - window1->bounds().width()) / 2) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("32,48 256x512", window4->bounds().ToString()); + + // Test4: A single manageable window should get centered. + window4.reset(); + window1_state->set_bounds_changed_by_user(false); + // Trigger the auto window placement function by showing (and hiding) it. + window1->Hide(); + window1->Show(); + // |window1| should be centered. + EXPECT_EQ( + base::IntToString( + (desktop_area.width() - window1->bounds().width()) / 2) + + ",32 640x320", window1->bounds().ToString()); +} + +// Test the proper usage of user window movement interaction. +TEST_F(WorkspaceControllerTest, TestUserMovedWindowRepositioning) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + gfx::Rect desktop_area = window1->parent()->bounds(); + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + window1->Hide(); + window2->Hide(); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + wm::WindowState* window2_state = wm::GetWindowState(window2.get()); + + window1_state->set_window_position_managed(true); + window2_state->set_window_position_managed(true); + EXPECT_FALSE(window1_state->bounds_changed_by_user()); + EXPECT_FALSE(window2_state->bounds_changed_by_user()); + + // Check that the current location gets preserved if the user has + // positioned it previously. + window1_state->set_bounds_changed_by_user(true); + window1->Show(); + EXPECT_EQ("16,32 640x320", window1->bounds().ToString()); + // Flag should be still set. + EXPECT_TRUE(window1_state->bounds_changed_by_user()); + EXPECT_FALSE(window2_state->bounds_changed_by_user()); + + // Turn on the second window and make sure that both windows are now + // positionable again (user movement cleared). + window2->Show(); + + // |window1| should be flush left and |window2| flush right. + EXPECT_EQ("0,32 640x320", window1->bounds().ToString()); + EXPECT_EQ( + base::IntToString(desktop_area.width() - window2->bounds().width()) + + ",48 256x512", window2->bounds().ToString()); + // FLag should now be reset. + EXPECT_FALSE(window1_state->bounds_changed_by_user()); + EXPECT_FALSE(window2_state->bounds_changed_by_user()); + + // Going back to one shown window should keep the state. + window1_state->set_bounds_changed_by_user(true); + window2->Hide(); + EXPECT_EQ("0,32 640x320", window1->bounds().ToString()); + EXPECT_TRUE(window1_state->bounds_changed_by_user()); +} + +// Test if the single window will be restored at original position. +TEST_F(WorkspaceControllerTest, TestSingleWindowsRestoredBounds) { + scoped_ptr<aura::Window> window1( + CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100))); + scoped_ptr<aura::Window> window2( + CreateTestWindowInShellWithBounds(gfx::Rect(110, 110, 100, 100))); + scoped_ptr<aura::Window> window3( + CreateTestWindowInShellWithBounds(gfx::Rect(120, 120, 100, 100))); + window1->Hide(); + window2->Hide(); + window3->Hide(); + wm::GetWindowState(window1.get())->set_window_position_managed(true); + wm::GetWindowState(window2.get())->set_window_position_managed(true); + wm::GetWindowState(window3.get())->set_window_position_managed(true); + + window1->Show(); + wm::ActivateWindow(window1.get()); + window2->Show(); + wm::ActivateWindow(window2.get()); + window3->Show(); + wm::ActivateWindow(window3.get()); + EXPECT_EQ(0, window1->bounds().x()); + EXPECT_EQ(window2->GetRootWindow()->bounds().right(), + window2->bounds().right()); + EXPECT_EQ(0, window3->bounds().x()); + + window1->Hide(); + EXPECT_EQ(window2->GetRootWindow()->bounds().right(), + window2->bounds().right()); + EXPECT_EQ(0, window3->bounds().x()); + + // Being a single window will retore the original location. + window3->Hide(); + wm::ActivateWindow(window2.get()); + EXPECT_EQ("110,110 100x100", window2->bounds().ToString()); + + // Showing the 3rd will push the 2nd window left. + window3->Show(); + wm::ActivateWindow(window3.get()); + EXPECT_EQ(0, window2->bounds().x()); + EXPECT_EQ(window3->GetRootWindow()->bounds().right(), + window3->bounds().right()); + + // Being a single window will retore the original location. + window2->Hide(); + EXPECT_EQ("120,120 100x100", window3->bounds().ToString()); +} + +// Test that user placed windows go back to their user placement after the user +// closes all other windows. +TEST_F(WorkspaceControllerTest, TestUserHandledWindowRestore) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + gfx::Rect user_pos = gfx::Rect(16, 42, 640, 320); + window1->SetBounds(user_pos); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + + window1_state->SetPreAutoManageWindowBounds(user_pos); + gfx::Rect desktop_area = window1->parent()->bounds(); + + // Create a second window to let the auto manager kick in. + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + window1->Hide(); + window2->Hide(); + wm::GetWindowState(window1.get())->set_window_position_managed(true); + wm::GetWindowState(window2.get())->set_window_position_managed(true); + window1->Show(); + EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString()); + window2->Show(); + + // |window1| should be flush left and |window2| flush right. + EXPECT_EQ("0," + base::IntToString(user_pos.y()) + + " 640x320", window1->bounds().ToString()); + EXPECT_EQ( + base::IntToString(desktop_area.width() - window2->bounds().width()) + + ",48 256x512", window2->bounds().ToString()); + window2->Hide(); + + // After the other window get hidden the window has to move back to the + // previous position and the bounds should still be set and unchanged. + EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString()); + ASSERT_TRUE(window1_state->pre_auto_manage_window_bounds()); + EXPECT_EQ(user_pos.ToString(), + window1_state->pre_auto_manage_window_bounds()->ToString()); +} + +// Test that a window from normal to minimize will repos the remaining. +TEST_F(WorkspaceControllerTest, ToMinimizeRepositionsRemaining) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + window1_state->set_window_position_managed(true); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + gfx::Rect desktop_area = window1->parent()->bounds(); + + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + wm::WindowState* window2_state = wm::GetWindowState(window2.get()); + window2_state->set_window_position_managed(true); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + + window1_state->Minimize(); + + // |window2| should be centered now. + EXPECT_TRUE(window2->IsVisible()); + EXPECT_TRUE(window2_state->IsNormalShowState()); + EXPECT_EQ(base::IntToString( + (desktop_area.width() - window2->bounds().width()) / 2) + + ",48 256x512", window2->bounds().ToString()); + + window1_state->Restore(); + // |window1| should be flush right and |window3| flush left. + EXPECT_EQ(base::IntToString( + desktop_area.width() - window1->bounds().width()) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("0,48 256x512", window2->bounds().ToString()); +} + +// Test that minimizing an initially maximized window will repos the remaining. +TEST_F(WorkspaceControllerTest, MaxToMinRepositionsRemaining) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + window1_state->set_window_position_managed(true); + gfx::Rect desktop_area = window1->parent()->bounds(); + + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + wm::WindowState* window2_state = wm::GetWindowState(window2.get()); + window2_state->set_window_position_managed(true); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + + window1_state->Maximize(); + window1_state->Minimize(); + + // |window2| should be centered now. + EXPECT_TRUE(window2->IsVisible()); + EXPECT_TRUE(window2_state->IsNormalShowState()); + EXPECT_EQ(base::IntToString( + (desktop_area.width() - window2->bounds().width()) / 2) + + ",48 256x512", window2->bounds().ToString()); +} + +// Test that nomral, maximize, minimizing will repos the remaining. +TEST_F(WorkspaceControllerTest, NormToMaxToMinRepositionsRemaining) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + window1_state->set_window_position_managed(true); + gfx::Rect desktop_area = window1->parent()->bounds(); + + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + wm::WindowState* window2_state = wm::GetWindowState(window2.get()); + window2_state->set_window_position_managed(true); + window2->SetBounds(gfx::Rect(32, 40, 256, 512)); + + // Trigger the auto window placement function by showing (and hiding) it. + window1->Hide(); + window1->Show(); + + // |window1| should be flush right and |window3| flush left. + EXPECT_EQ(base::IntToString( + desktop_area.width() - window1->bounds().width()) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("0,40 256x512", window2->bounds().ToString()); + + window1_state->Maximize(); + window1_state->Minimize(); + + // |window2| should be centered now. + EXPECT_TRUE(window2->IsVisible()); + EXPECT_TRUE(window2_state->IsNormalShowState()); + EXPECT_EQ(base::IntToString( + (desktop_area.width() - window2->bounds().width()) / 2) + + ",40 256x512", window2->bounds().ToString()); +} + +// Test that nomral, maximize, normal will repos the remaining. +TEST_F(WorkspaceControllerTest, NormToMaxToNormRepositionsRemaining) { + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + wm::WindowState* window1_state = wm::GetWindowState(window1.get()); + window1_state->set_window_position_managed(true); + gfx::Rect desktop_area = window1->parent()->bounds(); + + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + wm::GetWindowState(window2.get())->set_window_position_managed(true); + window2->SetBounds(gfx::Rect(32, 40, 256, 512)); + + // Trigger the auto window placement function by showing (and hiding) it. + window1->Hide(); + window1->Show(); + + // |window1| should be flush right and |window3| flush left. + EXPECT_EQ(base::IntToString( + desktop_area.width() - window1->bounds().width()) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("0,40 256x512", window2->bounds().ToString()); + + window1_state->Maximize(); + window1_state->Restore(); + + // |window1| should be flush right and |window2| flush left. + EXPECT_EQ(base::IntToString( + desktop_area.width() - window1->bounds().width()) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("0,40 256x512", window2->bounds().ToString()); +} + +// Test that animations are triggered. +TEST_F(WorkspaceControllerTest, AnimatedNormToMaxToNormRepositionsRemaining) { + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0)); + window1->Hide(); + window1->SetBounds(gfx::Rect(16, 32, 640, 320)); + gfx::Rect desktop_area = window1->parent()->bounds(); + scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1)); + window2->Hide(); + window2->SetBounds(gfx::Rect(32, 48, 256, 512)); + + wm::GetWindowState(window1.get())->set_window_position_managed(true); + wm::GetWindowState(window2.get())->set_window_position_managed(true); + // Make sure nothing is animating. + window1->layer()->GetAnimator()->StopAnimating(); + window2->layer()->GetAnimator()->StopAnimating(); + window2->Show(); + + // The second window should now animate. + EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating()); + EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating()); + window2->layer()->GetAnimator()->StopAnimating(); + + window1->Show(); + EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating()); + EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating()); + + window1->layer()->GetAnimator()->StopAnimating(); + window2->layer()->GetAnimator()->StopAnimating(); + // |window1| should be flush right and |window2| flush left. + EXPECT_EQ(base::IntToString( + desktop_area.width() - window1->bounds().width()) + + ",32 640x320", window1->bounds().ToString()); + EXPECT_EQ("0,48 256x512", window2->bounds().ToString()); +} + +// This tests simulates a browser and an app and verifies the ordering of the +// windows and layers doesn't get out of sync as various operations occur. Its +// really testing code in FocusController, but easier to simulate here. Just as +// with a real browser the browser here has a transient child window +// (corresponds to the status bubble). +TEST_F(WorkspaceControllerTest, VerifyLayerOrdering) { + scoped_ptr<Window> browser( + aura::test::CreateTestWindowWithDelegate( + NULL, + aura::client::WINDOW_TYPE_NORMAL, + gfx::Rect(5, 6, 7, 8), + NULL)); + browser->SetName("browser"); + ParentWindowInPrimaryRootWindow(browser.get()); + browser->Show(); + wm::ActivateWindow(browser.get()); + + // |status_bubble| is made a transient child of |browser| and as a result + // owned by |browser|. + aura::test::TestWindowDelegate* status_bubble_delegate = + aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(); + status_bubble_delegate->set_can_focus(false); + Window* status_bubble = + aura::test::CreateTestWindowWithDelegate( + status_bubble_delegate, + aura::client::WINDOW_TYPE_POPUP, + gfx::Rect(5, 6, 7, 8), + NULL); + browser->AddTransientChild(status_bubble); + ParentWindowInPrimaryRootWindow(status_bubble); + status_bubble->SetName("status_bubble"); + + scoped_ptr<Window> app( + aura::test::CreateTestWindowWithDelegate( + NULL, + aura::client::WINDOW_TYPE_NORMAL, + gfx::Rect(5, 6, 7, 8), + NULL)); + app->SetName("app"); + ParentWindowInPrimaryRootWindow(app.get()); + + aura::Window* parent = browser->parent(); + + app->Show(); + wm::ActivateWindow(app.get()); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Minimize the app, focus should go the browser. + app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_TRUE(wm::IsActiveWindow(browser.get())); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Minimize the browser (neither windows are focused). + browser->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED); + EXPECT_FALSE(wm::IsActiveWindow(browser.get())); + EXPECT_FALSE(wm::IsActiveWindow(app.get())); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Show the browser (which should restore it). + browser->Show(); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Activate the browser. + ash::wm::ActivateWindow(browser.get()); + EXPECT_TRUE(wm::IsActiveWindow(browser.get())); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Restore the app. This differs from above code for |browser| as internally + // the app code does this. Restoring this way or using Show() should not make + // a difference. + app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); + + // Activate the app. + ash::wm::ActivateWindow(app.get()); + EXPECT_TRUE(wm::IsActiveWindow(app.get())); + EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent)); +} + +namespace { + +// Used by DragMaximizedNonTrackedWindow to track how many times the window +// hierarchy changes affecting the specified window. +class DragMaximizedNonTrackedWindowObserver + : public aura::WindowObserver { + public: + DragMaximizedNonTrackedWindowObserver(aura::Window* window) + : change_count_(0), + window_(window) { + } + + // Number of times OnWindowHierarchyChanged() has been received. + void clear_change_count() { change_count_ = 0; } + int change_count() const { + return change_count_; + } + + // aura::WindowObserver overrides: + // Counts number of times a window is reparented. Ignores reparenting into and + // from a docked container which is expected when a tab is dragged. + virtual void OnWindowHierarchyChanged( + const HierarchyChangeParams& params) OVERRIDE { + if (params.target != window_ || + (params.old_parent->id() == kShellWindowId_DefaultContainer && + params.new_parent->id() == kShellWindowId_DockedContainer) || + (params.old_parent->id() == kShellWindowId_DockedContainer && + params.new_parent->id() == kShellWindowId_DefaultContainer)) { + return; + } + change_count_++; + } + + private: + int change_count_; + aura::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(DragMaximizedNonTrackedWindowObserver); +}; + +} // namespace + +// Verifies that a new maximized window becomes visible after its activation +// is requested, even though it does not become activated because a system +// modal window is active. +TEST_F(WorkspaceControllerTest, SwitchFromModal) { + scoped_ptr<Window> modal_window(CreateTestWindowUnparented()); + modal_window->SetBounds(gfx::Rect(10, 11, 21, 22)); + modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM); + ParentWindowInPrimaryRootWindow(modal_window.get()); + modal_window->Show(); + wm::ActivateWindow(modal_window.get()); + + scoped_ptr<Window> maximized_window(CreateTestWindow()); + maximized_window->SetProperty( + aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); + maximized_window->Show(); + wm::ActivateWindow(maximized_window.get()); + EXPECT_TRUE(maximized_window->IsVisible()); +} + +namespace { + +// Subclass of WorkspaceControllerTest that runs tests with docked windows +// enabled and disabled. +class WorkspaceControllerTestDragging + : public WorkspaceControllerTest, + public testing::WithParamInterface<bool> { + public: + WorkspaceControllerTestDragging() {} + virtual ~WorkspaceControllerTestDragging() {} + + // testing::Test: + virtual void SetUp() OVERRIDE { + WorkspaceControllerTest::SetUp(); + if (docked_windows_enabled()) { + CommandLine::ForCurrentProcess()->AppendSwitch( + ash::switches::kAshEnableDockedWindows); + } + } + + bool docked_windows_enabled() const { return GetParam(); } + + private: + DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTestDragging); +}; + +} // namespace + +// Verifies that when dragging a window over the shelf overlap is detected +// during and after the drag. +TEST_P(WorkspaceControllerTestDragging, DragWindowOverlapShelf) { + aura::test::TestWindowDelegate delegate; + delegate.set_window_component(HTCAPTION); + scoped_ptr<Window> w1( + aura::test::CreateTestWindowWithDelegate(&delegate, + aura::client::WINDOW_TYPE_NORMAL, + gfx::Rect(5, 5, 100, 50), + NULL)); + ParentWindowInPrimaryRootWindow(w1.get()); + + ShelfLayoutManager* shelf = shelf_layout_manager(); + shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER); + + // Drag near the shelf + aura::test::EventGenerator generator( + Shell::GetPrimaryRootWindow(), gfx::Point()); + generator.MoveMouseTo(10, 10); + generator.PressLeftButton(); + generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 70); + + // Shelf should not be in overlapped state. + EXPECT_FALSE(GetWindowOverlapsShelf()); + + generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 20); + + // Shelf should detect overlap. Overlap state stays after mouse is released. + EXPECT_TRUE(GetWindowOverlapsShelf()); + generator.ReleaseLeftButton(); + EXPECT_TRUE(GetWindowOverlapsShelf()); +} + +INSTANTIATE_TEST_CASE_P(DockedOrNot, WorkspaceControllerTestDragging, + ::testing::Bool()); + +} // namespace internal +} // namespace ash |