diff options
Diffstat (limited to 'chromium/ash/wm/overview/window_selector_unittest.cc')
-rw-r--r-- | chromium/ash/wm/overview/window_selector_unittest.cc | 998 |
1 files changed, 998 insertions, 0 deletions
diff --git a/chromium/ash/wm/overview/window_selector_unittest.cc b/chromium/ash/wm/overview/window_selector_unittest.cc new file mode 100644 index 00000000000..199f4a72c20 --- /dev/null +++ b/chromium/ash/wm/overview/window_selector_unittest.cc @@ -0,0 +1,998 @@ +// 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/launcher/launcher.h" +#include "ash/root_window_controller.h" +#include "ash/screen_ash.h" +#include "ash/shelf/shelf_widget.h" +#include "ash/shell.h" +#include "ash/test/ash_test_base.h" +#include "ash/test/launcher_test_api.h" +#include "ash/test/shelf_view_test_api.h" +#include "ash/test/shell_test_api.h" +#include "ash/test/test_shelf_delegate.h" +#include "ash/wm/mru_window_tracker.h" +#include "ash/wm/overview/window_selector.h" +#include "ash/wm/overview/window_selector_controller.h" +#include "ash/wm/window_state.h" +#include "ash/wm/window_util.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "ui/aura/client/activation_delegate.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/cursor_client.h" +#include "ui/aura/client/focus_client.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/compositor/scoped_animation_duration_scale_mode.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/transform.h" + +namespace ash { +namespace internal { + +namespace { + +class NonActivatableActivationDelegate + : public aura::client::ActivationDelegate { + public: + virtual bool ShouldActivate() const OVERRIDE { + return false; + } +}; + +bool IsWindowAbove(aura::Window* w1, aura::Window* w2) { + aura::Window* parent = w1->parent(); + DCHECK_EQ(parent, w2->parent()); + for (aura::Window::Windows::const_iterator iter = parent->children().begin(); + iter != parent->children().end(); ++iter) { + if (*iter == w1) + return false; + if (*iter == w2) + return true; + } + NOTREACHED(); + return false; +} + +aura::Window* GetWindowByName(aura::Window* container, + const std::string& name) { + aura::Window* window = NULL; + for (aura::Window::Windows::const_iterator iter = + container->children().begin(); iter != container->children().end(); + ++iter) { + if ((*iter)->name() == name) { + // The name should be unique. + DCHECK(!window); + window = *iter; + } + } + return window; +} + +// Returns the copy of |window| created for overview. It is found using the +// window name which should be the same as the source window's name with a +// special suffix, and in the same container as the source window. +aura::Window* GetCopyWindow(aura::Window* window) { + aura::Window* copy_window = NULL; + std::string copy_name = window->name() + " (Copy)"; + std::vector<aura::Window*> containers( + Shell::GetContainersFromAllRootWindows(window->parent()->id(), NULL)); + for (std::vector<aura::Window*>::iterator iter = containers.begin(); + iter != containers.end(); ++iter) { + aura::Window* found = GetWindowByName(*iter, copy_name); + if (found) { + // There should only be one copy window. + DCHECK(!copy_window); + copy_window = found; + } + } + return copy_window; +} + +} // namespace + +class WindowSelectorTest : public test::AshTestBase { + public: + WindowSelectorTest() {} + virtual ~WindowSelectorTest() {} + + virtual void SetUp() OVERRIDE { + test::AshTestBase::SetUp(); + ASSERT_TRUE(test::TestShelfDelegate::instance()); + + shelf_view_test_.reset(new test::ShelfViewTestAPI( + test::LauncherTestAPI(Launcher::ForPrimaryDisplay()).shelf_view())); + shelf_view_test_->SetAnimationDuration(1); + } + + aura::Window* CreateWindow(const gfx::Rect& bounds) { + return CreateTestWindowInShellWithDelegate(&delegate_, -1, bounds); + } + + aura::Window* CreateNonActivatableWindow(const gfx::Rect& bounds) { + aura::Window* window = CreateWindow(bounds); + aura::client::SetActivationDelegate(window, + &non_activatable_activation_delegate_); + EXPECT_FALSE(ash::wm::CanActivateWindow(window)); + return window; + } + + aura::Window* CreatePanelWindow(const gfx::Rect& bounds) { + aura::Window* window = CreateTestWindowInShellWithDelegateAndType( + NULL, aura::client::WINDOW_TYPE_PANEL, 0, bounds); + test::TestShelfDelegate::instance()->AddLauncherItem(window); + shelf_view_test()->RunMessageLoopUntilAnimationsDone(); + return window; + } + + bool WindowsOverlapping(aura::Window* window1, aura::Window* window2) { + gfx::RectF window1_bounds = GetTransformedTargetBounds(window1); + gfx::RectF window2_bounds = GetTransformedTargetBounds(window2); + return window1_bounds.Intersects(window2_bounds); + } + + void ToggleOverview() { + ash::Shell::GetInstance()->window_selector_controller()->ToggleOverview(); + } + + void Cycle(WindowSelector::Direction direction) { + ash::Shell::GetInstance()->window_selector_controller()-> + HandleCycleWindow(direction); + } + + void StopCycling() { + ash::Shell::GetInstance()->window_selector_controller()->window_selector_-> + SelectWindow(); + } + + void FireOverviewStartTimer() { + // Calls the method to start overview mode which is normally called by the + // timer. The timer will still fire and call this method triggering the + // DCHECK that overview mode was not already started, except that we call + // StopCycling before the timer has a chance to fire. + ash::Shell::GetInstance()->window_selector_controller()->window_selector_-> + StartOverview(); + } + + gfx::Transform GetTransformRelativeTo(gfx::PointF origin, + const gfx::Transform& transform) { + gfx::Transform t; + t.Translate(origin.x(), origin.y()); + t.PreconcatTransform(transform); + t.Translate(-origin.x(), -origin.y()); + return t; + } + + gfx::RectF GetTransformedBounds(aura::Window* window) { + gfx::RectF bounds(ash::ScreenAsh::ConvertRectToScreen( + window->parent(), window->layer()->bounds())); + gfx::Transform transform(GetTransformRelativeTo(bounds.origin(), + window->layer()->transform())); + transform.TransformRect(&bounds); + return bounds; + } + + gfx::RectF GetTransformedTargetBounds(aura::Window* window) { + gfx::RectF bounds(ash::ScreenAsh::ConvertRectToScreen( + window->parent(), window->layer()->GetTargetBounds())); + gfx::Transform transform(GetTransformRelativeTo(bounds.origin(), + window->layer()->GetTargetTransform())); + transform.TransformRect(&bounds); + return bounds; + } + + void ClickWindow(aura::Window* window) { + aura::test::EventGenerator event_generator(window->GetRootWindow(), window); + gfx::RectF target = GetTransformedBounds(window); + event_generator.ClickLeftButton(); + } + + bool IsSelecting() { + return ash::Shell::GetInstance()->window_selector_controller()-> + IsSelecting(); + } + + aura::Window* GetFocusedWindow() { + return aura::client::GetFocusClient( + Shell::GetPrimaryRootWindow())->GetFocusedWindow(); + } + + test::ShelfViewTestAPI* shelf_view_test() { + return shelf_view_test_.get(); + } + + private: + aura::test::TestWindowDelegate delegate_; + NonActivatableActivationDelegate non_activatable_activation_delegate_; + scoped_ptr<test::ShelfViewTestAPI> shelf_view_test_; + + DISALLOW_COPY_AND_ASSIGN(WindowSelectorTest); +}; + +// Tests entering overview mode with two windows and selecting one. +TEST_F(WindowSelectorTest, Basic) { + gfx::Rect bounds(0, 0, 400, 400); + aura::Window* root_window = Shell::GetPrimaryRootWindow(); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + scoped_ptr<aura::Window> panel2(CreatePanelWindow(bounds)); + EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get())); + EXPECT_TRUE(WindowsOverlapping(panel1.get(), panel2.get())); + wm::ActivateWindow(window2.get()); + EXPECT_FALSE(wm::IsActiveWindow(window1.get())); + EXPECT_TRUE(wm::IsActiveWindow(window2.get())); + EXPECT_EQ(window2.get(), GetFocusedWindow()); + // Hide the cursor before entering overview to test that it will be shown. + aura::client::GetCursorClient(root_window)->HideCursor(); + + // In overview mode the windows should no longer overlap and focus should + // be removed from the window. + ToggleOverview(); + EXPECT_EQ(NULL, GetFocusedWindow()); + EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); + EXPECT_FALSE(WindowsOverlapping(window1.get(), panel1.get())); + // Panels 1 and 2 should still be overlapping being in a single selector + // item. + EXPECT_TRUE(WindowsOverlapping(panel1.get(), panel2.get())); + + // The cursor should be visible and locked as a pointer + EXPECT_EQ(ui::kCursorPointer, + root_window->GetDispatcher()->last_cursor().native_type()); + EXPECT_TRUE(aura::client::GetCursorClient(root_window)->IsCursorLocked()); + EXPECT_TRUE(aura::client::GetCursorClient(root_window)->IsCursorVisible()); + + // Clicking window 1 should activate it. + ClickWindow(window1.get()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + EXPECT_FALSE(wm::IsActiveWindow(window2.get())); + EXPECT_EQ(window1.get(), GetFocusedWindow()); + + // Cursor should have been unlocked. + EXPECT_FALSE(aura::client::GetCursorClient(root_window)->IsCursorLocked()); +} + +// Tests entering overview mode with two windows and selecting one. +TEST_F(WindowSelectorTest, FullscreenWindow) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + wm::ActivateWindow(window1.get()); + + wm::GetWindowState(window1.get())->ToggleFullscreen(); + // The panel is hidden in fullscreen mode. + EXPECT_FALSE(panel1->IsVisible()); + EXPECT_TRUE(wm::GetWindowState(window1.get())->IsFullscreen()); + + // Enter overview and select the fullscreen window. + ToggleOverview(); + + // The panel becomes temporarily visible for the overview. + EXPECT_TRUE(panel1->IsVisible()); + ClickWindow(window1.get()); + + // The window is still fullscreen as it was selected. The panel should again + // be hidden. + EXPECT_TRUE(wm::GetWindowState(window1.get())->IsFullscreen()); + EXPECT_FALSE(panel1->IsVisible()); + + // Entering overview and selecting another window, the previous window remains + // fullscreen. + // TODO(flackr): Currently the panel remains hidden, but should become visible + // again. + ToggleOverview(); + ClickWindow(window2.get()); + EXPECT_TRUE(wm::GetWindowState(window1.get())->IsFullscreen()); +} + +// Tests that the shelf dimming state is removed while in overview and restored +// on exiting overview. +TEST_F(WindowSelectorTest, OverviewUndimsShelf) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + wm::WindowState* window_state = wm::GetWindowState(window1.get()); + window_state->Maximize(); + ash::ShelfWidget* shelf = Shell::GetPrimaryRootWindowController()->shelf(); + EXPECT_TRUE(shelf->GetDimsShelf()); + ToggleOverview(); + EXPECT_FALSE(shelf->GetDimsShelf()); + ToggleOverview(); + EXPECT_TRUE(shelf->GetDimsShelf()); +} + +// Tests that beginning window selection hides the app list. +TEST_F(WindowSelectorTest, SelectingHidesAppList) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + Shell::GetInstance()->ToggleAppList(NULL); + EXPECT_TRUE(Shell::GetInstance()->GetAppListTargetVisibility()); + ToggleOverview(); + EXPECT_FALSE(Shell::GetInstance()->GetAppListTargetVisibility()); + ToggleOverview(); + + // The app list uses an animation to fade out. If it is toggled on immediately + // after being removed the old widget is re-used and it does not gain focus. + // When running under normal circumstances this shouldn't be possible, but + // it is in a test without letting the message loop run. + RunAllPendingInMessageLoop(); + + Shell::GetInstance()->ToggleAppList(NULL); + EXPECT_TRUE(Shell::GetInstance()->GetAppListTargetVisibility()); + Cycle(WindowSelector::FORWARD); + EXPECT_FALSE(Shell::GetInstance()->GetAppListTargetVisibility()); + StopCycling(); +} + +// Tests that a minimized window's visibility and layer visibility is correctly +// changed when entering overview and restored when leaving overview mode. +TEST_F(WindowSelectorTest, MinimizedWindowVisibility) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + wm::WindowState* window_state = wm::GetWindowState(window1.get()); + window_state->Minimize(); + EXPECT_FALSE(window1->IsVisible()); + EXPECT_FALSE(window1->layer()->GetTargetVisibility()); + { + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + ToggleOverview(); + EXPECT_TRUE(window1->IsVisible()); + EXPECT_TRUE(window1->layer()->GetTargetVisibility()); + } + { + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + ToggleOverview(); + EXPECT_FALSE(window1->IsVisible()); + EXPECT_FALSE(window1->layer()->GetTargetVisibility()); + } +} + +// Tests that a bounds change during overview is corrected for. +TEST_F(WindowSelectorTest, BoundsChangeDuringOverview) { + scoped_ptr<aura::Window> window(CreateWindow(gfx::Rect(0, 0, 400, 400))); + ToggleOverview(); + gfx::Rect overview_bounds = + ToEnclosingRect(GetTransformedTargetBounds(window.get())); + window->SetBounds(gfx::Rect(200, 0, 200, 200)); + gfx::Rect new_overview_bounds = + ToEnclosingRect(GetTransformedTargetBounds(window.get())); + EXPECT_EQ(overview_bounds.x(), new_overview_bounds.x()); + EXPECT_EQ(overview_bounds.y(), new_overview_bounds.y()); + EXPECT_EQ(overview_bounds.width(), new_overview_bounds.width()); + EXPECT_EQ(overview_bounds.height(), new_overview_bounds.height()); + ToggleOverview(); +} + +// Tests entering overview mode with three windows and cycling through them. +TEST_F(WindowSelectorTest, BasicCycle) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> window3(CreateWindow(bounds)); + wm::ActivateWindow(window3.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + EXPECT_FALSE(wm::IsActiveWindow(window2.get())); + EXPECT_FALSE(wm::IsActiveWindow(window3.get())); + + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(IsSelecting()); + EXPECT_TRUE(wm::IsActiveWindow(window2.get())); + + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(wm::IsActiveWindow(window3.get())); + + StopCycling(); + EXPECT_FALSE(IsSelecting()); + EXPECT_FALSE(wm::IsActiveWindow(window1.get())); + EXPECT_FALSE(wm::IsActiveWindow(window2.get())); + EXPECT_TRUE(wm::IsActiveWindow(window3.get())); +} + +// Tests that cycling through windows preserves the window stacking order. +TEST_F(WindowSelectorTest, CyclePreservesStackingOrder) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> window3(CreateWindow(bounds)); + wm::ActivateWindow(window3.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + // Window order from top to bottom is 1, 2, 3. + EXPECT_TRUE(IsWindowAbove(window1.get(), window2.get())); + EXPECT_TRUE(IsWindowAbove(window2.get(), window3.get())); + + // On window 2. + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(IsWindowAbove(window2.get(), window1.get())); + EXPECT_TRUE(IsWindowAbove(window1.get(), window3.get())); + + // On window 3. + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(IsWindowAbove(window3.get(), window1.get())); + EXPECT_TRUE(IsWindowAbove(window1.get(), window2.get())); + + // Back on window 1. + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(IsWindowAbove(window1.get(), window2.get())); + EXPECT_TRUE(IsWindowAbove(window2.get(), window3.get())); + StopCycling(); +} + +// Tests that cycling through windows shows and minimizes windows as they +// are passed. +TEST_F(WindowSelectorTest, CyclePreservesMinimization) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + wm::ActivateWindow(window2.get()); + wm::GetWindowState(window2.get())->Minimize(); + wm::ActivateWindow(window1.get()); + EXPECT_TRUE(wm::IsWindowMinimized(window2.get())); + + // On window 2. + Cycle(WindowSelector::FORWARD); + EXPECT_FALSE(wm::IsWindowMinimized(window2.get())); + + // Back on window 1. + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(wm::IsWindowMinimized(window2.get())); + + StopCycling(); + EXPECT_TRUE(wm::IsWindowMinimized(window2.get())); +} + +// Tests beginning cycling while in overview mode. +TEST_F(WindowSelectorTest, OverviewTransitionToCycle) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + + ToggleOverview(); + Cycle(WindowSelector::FORWARD); + StopCycling(); + + EXPECT_TRUE(wm::IsActiveWindow(window2.get())); + EXPECT_FALSE(wm::IsActiveWindow(window1.get())); + EXPECT_EQ(window2.get(), GetFocusedWindow()); +} + +// Tests cycles between panel and normal windows. +TEST_F(WindowSelectorTest, CyclePanels) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + scoped_ptr<aura::Window> panel2(CreatePanelWindow(bounds)); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + wm::ActivateWindow(panel2.get()); + wm::ActivateWindow(panel1.get()); + EXPECT_TRUE(wm::IsActiveWindow(panel1.get())); + + // Cycling once should select window1 since the panels are grouped into a + // single selectable item. + Cycle(WindowSelector::FORWARD); + StopCycling(); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + + // Cycling again should select the most recently used panel. + Cycle(WindowSelector::FORWARD); + StopCycling(); + EXPECT_TRUE(wm::IsActiveWindow(panel1.get())); +} + +// Tests the visibility of panel windows during cycling. +TEST_F(WindowSelectorTest, CyclePanelVisibility) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + wm::ActivateWindow(panel1.get()); + wm::ActivateWindow(window1.get()); + + Cycle(WindowSelector::FORWARD); + FireOverviewStartTimer(); + EXPECT_EQ(1.0f, panel1->layer()->GetTargetOpacity()); + StopCycling(); +} + +// Tests cycles between panel and normal windows. +TEST_F(WindowSelectorTest, CyclePanelsDestroyed) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> window3(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + scoped_ptr<aura::Window> panel2(CreatePanelWindow(bounds)); + wm::ActivateWindow(window3.get()); + wm::ActivateWindow(panel2.get()); + wm::ActivateWindow(panel1.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + + // Cycling once highlights window2. + Cycle(WindowSelector::FORWARD); + // All panels are destroyed. + panel1.reset(); + panel2.reset(); + // Cycling again should now select window3. + Cycle(WindowSelector::FORWARD); + StopCycling(); + EXPECT_TRUE(wm::IsActiveWindow(window3.get())); +} + +// Tests cycles between panel and normal windows. +TEST_F(WindowSelectorTest, CycleMruPanelDestroyed) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds)); + scoped_ptr<aura::Window> panel2(CreatePanelWindow(bounds)); + wm::ActivateWindow(panel2.get()); + wm::ActivateWindow(panel1.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); + + // Cycling once highlights window2. + Cycle(WindowSelector::FORWARD); + // Panel 1 is the next item as the MRU panel, removing it should make panel 2 + // the next window to be selected. + panel1.reset(); + // Cycling again should now select window3. + Cycle(WindowSelector::FORWARD); + StopCycling(); + EXPECT_TRUE(wm::IsActiveWindow(panel2.get())); +} + +// Tests that a newly created window aborts overview. +TEST_F(WindowSelectorTest, NewWindowCancelsOveriew) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + ToggleOverview(); + EXPECT_TRUE(IsSelecting()); + + // A window being created should exit overview mode. + scoped_ptr<aura::Window> window3(CreateWindow(bounds)); + EXPECT_FALSE(IsSelecting()); +} + +// Tests that a window activation exits overview mode. +TEST_F(WindowSelectorTest, ActivationCancelsOveriew) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + window2->Focus(); + ToggleOverview(); + EXPECT_TRUE(IsSelecting()); + + // A window being activated should exit overview mode. + window1->Focus(); + EXPECT_FALSE(IsSelecting()); + + // window1 should be focused after exiting even though window2 was focused on + // entering overview because we exited due to an activation. + EXPECT_EQ(window1.get(), GetFocusedWindow()); +} + +// Verifies that overview mode only begins after a delay when cycling. +TEST_F(WindowSelectorTest, CycleOverviewDelay) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get())); + + // When cycling first starts, the windows will still be overlapping. + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(IsSelecting()); + EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get())); + + // Once the overview timer fires, the windows should no longer overlap. + FireOverviewStartTimer(); + EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); + StopCycling(); +} + +// Tests that exiting overview mode without selecting a window restores focus +// to the previously focused window. +TEST_F(WindowSelectorTest, CancelRestoresFocus) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window(CreateWindow(bounds)); + wm::ActivateWindow(window.get()); + EXPECT_EQ(window.get(), GetFocusedWindow()); + + // In overview mode, focus should be removed. + ToggleOverview(); + EXPECT_EQ(NULL, GetFocusedWindow()); + + // If canceling overview mode, focus should be restored. + ToggleOverview(); + EXPECT_EQ(window.get(), GetFocusedWindow()); +} + +// Tests that overview mode is exited if the last remaining window is destroyed. +TEST_F(WindowSelectorTest, LastWindowDestroyed) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + ToggleOverview(); + + window1.reset(); + window2.reset(); + EXPECT_FALSE(IsSelecting()); +} + +// Tests that entering overview mode restores a window to its original +// target location. +TEST_F(WindowSelectorTest, QuickReentryRestoresInitialTransform) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window(CreateWindow(bounds)); + gfx::Rect initial_bounds = ToEnclosingRect( + GetTransformedBounds(window.get())); + ToggleOverview(); + // Quickly exit and reenter overview mode. The window should still be + // animating when we reenter. We cannot short circuit animations for this but + // we also don't have to wait for them to complete. + { + ui::ScopedAnimationDurationScaleMode normal_duration_mode( + ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); + ToggleOverview(); + ToggleOverview(); + } + EXPECT_NE(initial_bounds, ToEnclosingRect( + GetTransformedTargetBounds(window.get()))); + ToggleOverview(); + EXPECT_FALSE(IsSelecting()); + EXPECT_EQ(initial_bounds, ToEnclosingRect( + GetTransformedTargetBounds(window.get()))); +} + +// Tests that non-activatable windows are hidden when entering overview mode. +TEST_F(WindowSelectorTest, NonActivatableWindowsHidden) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds)); + scoped_ptr<aura::Window> non_activatable_window( + CreateNonActivatableWindow(Shell::GetPrimaryRootWindow()->bounds())); + EXPECT_TRUE(non_activatable_window->IsVisible()); + ToggleOverview(); + EXPECT_FALSE(non_activatable_window->IsVisible()); + ToggleOverview(); + EXPECT_TRUE(non_activatable_window->IsVisible()); + + // Test that a window behind the fullscreen non-activatable window can be + // clicked. + non_activatable_window->parent()->StackChildAtTop( + non_activatable_window.get()); + ToggleOverview(); + ClickWindow(window1.get()); + EXPECT_FALSE(IsSelecting()); + EXPECT_TRUE(wm::IsActiveWindow(window1.get())); +} + +// Tests that windows with modal child windows are transformed with the modal +// child even though not activatable themselves. +TEST_F(WindowSelectorTest, ModalChild) { + gfx::Rect bounds(0, 0, 400, 400); + scoped_ptr<aura::Window> window1(CreateWindow(bounds)); + scoped_ptr<aura::Window> child1(CreateWindow(bounds)); + child1->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); + window1->AddTransientChild(child1.get()); + EXPECT_EQ(window1->parent(), child1->parent()); + ToggleOverview(); + EXPECT_TRUE(window1->IsVisible()); + EXPECT_TRUE(child1->IsVisible()); + EXPECT_EQ(ToEnclosingRect(GetTransformedTargetBounds(child1.get())), + ToEnclosingRect(GetTransformedTargetBounds(window1.get()))); + ToggleOverview(); +} + +// Tests that clicking a modal window's parent activates the modal window in +// overview. +TEST_F(WindowSelectorTest, ClickModalWindowParent) { + scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 180, 180))); + scoped_ptr<aura::Window> child1(CreateWindow(gfx::Rect(200, 0, 180, 180))); + child1->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); + window1->AddTransientChild(child1.get()); + EXPECT_FALSE(WindowsOverlapping(window1.get(), child1.get())); + EXPECT_EQ(window1->parent(), child1->parent()); + ToggleOverview(); + // Given that their relative positions are preserved, the windows should still + // not overlap. + EXPECT_FALSE(WindowsOverlapping(window1.get(), child1.get())); + ClickWindow(window1.get()); + EXPECT_FALSE(IsSelecting()); + + // Clicking on window1 should activate child1. + EXPECT_TRUE(wm::IsActiveWindow(child1.get())); +} + +// Tests that windows remain on the display they are currently on in overview +// mode. +TEST_F(WindowSelectorTest, MultipleDisplays) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("600x400,600x400"); + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + gfx::Rect bounds1(0, 0, 400, 400); + gfx::Rect bounds2(650, 0, 400, 400); + + scoped_ptr<aura::Window> window1(CreateWindow(bounds1)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds1)); + scoped_ptr<aura::Window> window3(CreateWindow(bounds2)); + scoped_ptr<aura::Window> window4(CreateWindow(bounds2)); + scoped_ptr<aura::Window> panel1(CreatePanelWindow(bounds1)); + scoped_ptr<aura::Window> panel2(CreatePanelWindow(bounds1)); + scoped_ptr<aura::Window> panel3(CreatePanelWindow(bounds2)); + scoped_ptr<aura::Window> panel4(CreatePanelWindow(bounds2)); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[0], window2->GetRootWindow()); + EXPECT_EQ(root_windows[1], window3->GetRootWindow()); + EXPECT_EQ(root_windows[1], window4->GetRootWindow()); + + EXPECT_EQ(root_windows[0], panel1->GetRootWindow()); + EXPECT_EQ(root_windows[0], panel2->GetRootWindow()); + EXPECT_EQ(root_windows[1], panel3->GetRootWindow()); + EXPECT_EQ(root_windows[1], panel4->GetRootWindow()); + + // In overview mode, each window remains in the same root window. + ToggleOverview(); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[0], window2->GetRootWindow()); + EXPECT_EQ(root_windows[1], window3->GetRootWindow()); + EXPECT_EQ(root_windows[1], window4->GetRootWindow()); + EXPECT_EQ(root_windows[0], panel1->GetRootWindow()); + EXPECT_EQ(root_windows[0], panel2->GetRootWindow()); + EXPECT_EQ(root_windows[1], panel3->GetRootWindow()); + EXPECT_EQ(root_windows[1], panel4->GetRootWindow()); + + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window1.get())))); + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window2.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window3.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window4.get())))); + + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(panel1.get())))); + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(panel2.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(panel3.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(panel4.get())))); + EXPECT_TRUE(WindowsOverlapping(panel1.get(), panel2.get())); + EXPECT_TRUE(WindowsOverlapping(panel3.get(), panel4.get())); + EXPECT_FALSE(WindowsOverlapping(panel1.get(), panel3.get())); +} + +// Verifies that the single display overview used during alt tab cycling uses +// the display of the selected window by default. +TEST_F(WindowSelectorTest, CycleOverviewUsesCurrentDisplay) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + + scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 100, 100))); + scoped_ptr<aura::Window> window2(CreateWindow(gfx::Rect(450, 0, 100, 100))); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[1], window2->GetRootWindow()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + EXPECT_EQ(root_windows[0], Shell::GetTargetRootWindow()); + + Cycle(WindowSelector::FORWARD); + FireOverviewStartTimer(); + + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window1.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window2.get())))); + StopCycling(); +} + +// Verifies that the windows being shown on another display are copied. +TEST_F(WindowSelectorTest, CycleMultipleDisplaysCopiesWindows) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + + gfx::Rect root1_rect(0, 0, 100, 100); + gfx::Rect root2_rect(450, 0, 100, 100); + scoped_ptr<aura::Window> unmoved1(CreateWindow(root2_rect)); + scoped_ptr<aura::Window> unmoved2(CreateWindow(root2_rect)); + scoped_ptr<aura::Window> moved1_trans_parent(CreateWindow(root1_rect)); + scoped_ptr<aura::Window> moved1(CreateWindow(root1_rect)); + unmoved1->SetName("unmoved1"); + unmoved2->SetName("unmoved2"); + moved1->SetName("moved1"); + moved1->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); + moved1_trans_parent->AddTransientChild(moved1.get()); + moved1_trans_parent->SetName("moved1_trans_parent"); + + EXPECT_EQ(root_windows[0], moved1->GetRootWindow()); + EXPECT_EQ(root_windows[0], moved1_trans_parent->GetRootWindow()); + EXPECT_EQ(root_windows[1], unmoved1->GetRootWindow()); + EXPECT_EQ(root_windows[1], unmoved2->GetRootWindow()); + wm::ActivateWindow(unmoved2.get()); + wm::ActivateWindow(unmoved1.get()); + + Cycle(WindowSelector::FORWARD); + FireOverviewStartTimer(); + + // All windows are moved to second root window. + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(unmoved1.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(unmoved2.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(moved1.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(moved1_trans_parent.get())))); + + // unmoved1 and unmoved2 were already on the correct display and should not + // have been copied. + EXPECT_TRUE(!GetCopyWindow(unmoved1.get())); + EXPECT_TRUE(!GetCopyWindow(unmoved2.get())); + + // moved1 and its transient parent moved1_trans_parent should have also been + // copied for displaying on root_windows[1]. + aura::Window* copy1 = GetCopyWindow(moved1.get()); + aura::Window* copy1_trans_parent = GetCopyWindow(moved1_trans_parent.get()); + ASSERT_FALSE(!copy1); + ASSERT_FALSE(!copy1_trans_parent); + + // Verify that the bounds and transform of the copy match the original window + // but that it is on the other root window. + EXPECT_EQ(root_windows[1], copy1->GetRootWindow()); + EXPECT_EQ(moved1->GetBoundsInScreen().ToString(), + copy1->GetBoundsInScreen().ToString()); + EXPECT_EQ(moved1->layer()->GetTargetTransform().ToString(), + copy1->layer()->GetTargetTransform().ToString()); + StopCycling(); + + // After cycling the copy windows should have been destroyed. + RunAllPendingInMessageLoop(); + EXPECT_TRUE(!GetCopyWindow(moved1.get())); + EXPECT_TRUE(!GetCopyWindow(moved1_trans_parent.get())); +} + +// Tests that beginning to cycle from overview mode moves windows to the +// active display. +TEST_F(WindowSelectorTest, MultipleDisplaysOverviewTransitionToCycle) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + + scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 100, 100))); + scoped_ptr<aura::Window> window2(CreateWindow(gfx::Rect(450, 0, 100, 100))); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[1], window2->GetRootWindow()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + + ToggleOverview(); + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window1.get())))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window2.get())))); + + Cycle(WindowSelector::FORWARD); + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window1.get())))); + EXPECT_TRUE(root_windows[0]->GetBoundsInScreen().Contains( + ToEnclosingRect(GetTransformedTargetBounds(window2.get())))); + StopCycling(); +} + +// Tests that a bounds change during overview is corrected for. +TEST_F(WindowSelectorTest, BoundsChangeDuringCycleOnOtherDisplay) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + + scoped_ptr<aura::Window> window1(CreateWindow(gfx::Rect(0, 0, 100, 100))); + scoped_ptr<aura::Window> window2(CreateWindow(gfx::Rect(450, 0, 100, 100))); + scoped_ptr<aura::Window> window3(CreateWindow(gfx::Rect(450, 0, 100, 100))); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[1], window2->GetRootWindow()); + EXPECT_EQ(root_windows[1], window3->GetRootWindow()); + wm::ActivateWindow(window1.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window3.get()); + + Cycle(WindowSelector::FORWARD); + FireOverviewStartTimer(); + + gfx::Rect overview_bounds( + ToEnclosingRect(GetTransformedTargetBounds(window1.get()))); + EXPECT_TRUE(root_windows[1]->GetBoundsInScreen().Contains(overview_bounds)); + + // Change the position and size of window1 (being displayed on the second + // root window) and it should remain within the same bounds. + window1->SetBounds(gfx::Rect(100, 0, 200, 200)); + gfx::Rect new_overview_bounds = + ToEnclosingRect(GetTransformedTargetBounds(window1.get())); + EXPECT_EQ(overview_bounds.x(), new_overview_bounds.x()); + EXPECT_EQ(overview_bounds.y(), new_overview_bounds.y()); + EXPECT_EQ(overview_bounds.width(), new_overview_bounds.width()); + EXPECT_EQ(overview_bounds.height(), new_overview_bounds.height()); + StopCycling(); +} + +// Tests shutting down during overview. +TEST_F(WindowSelectorTest, Shutdown) { + gfx::Rect bounds(0, 0, 400, 400); + // These windows will be deleted when the test exits and the Shell instance + // is shut down. + aura::Window* window1(CreateWindow(bounds)); + aura::Window* window2(CreateWindow(bounds)); + aura::Window* window3(CreatePanelWindow(bounds)); + aura::Window* window4(CreatePanelWindow(bounds)); + + wm::ActivateWindow(window4); + wm::ActivateWindow(window3); + wm::ActivateWindow(window2); + wm::ActivateWindow(window1); + + ToggleOverview(); +} + +// Tests removing a display during overview. +TEST_F(WindowSelectorTest, RemoveDisplay) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("400x400,400x400"); + gfx::Rect bounds1(0, 0, 100, 100); + gfx::Rect bounds2(450, 0, 100, 100); + scoped_ptr<aura::Window> window1(CreateWindow(bounds1)); + scoped_ptr<aura::Window> window2(CreateWindow(bounds2)); + scoped_ptr<aura::Window> window3(CreatePanelWindow(bounds1)); + scoped_ptr<aura::Window> window4(CreatePanelWindow(bounds2)); + + aura::Window::Windows root_windows = Shell::GetAllRootWindows(); + EXPECT_EQ(root_windows[0], window1->GetRootWindow()); + EXPECT_EQ(root_windows[1], window2->GetRootWindow()); + EXPECT_EQ(root_windows[0], window3->GetRootWindow()); + EXPECT_EQ(root_windows[1], window4->GetRootWindow()); + + wm::ActivateWindow(window4.get()); + wm::ActivateWindow(window3.get()); + wm::ActivateWindow(window2.get()); + wm::ActivateWindow(window1.get()); + + ToggleOverview(); + EXPECT_TRUE(IsSelecting()); + UpdateDisplay("400x400"); + EXPECT_FALSE(IsSelecting()); +} + +} // namespace internal +} // namespace ash |