| // 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_launcher_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::TestLauncherDelegate::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::TestLauncherDelegate::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 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()); |
| Cycle(WindowSelector::FORWARD); |
| 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"); |
| Shell::RootWindowList 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 initial window by default. |
| TEST_F(WindowSelectorTest, CycleOverviewUsesInitialDisplay) { |
| if (!SupportsMultipleDisplays()) |
| return; |
| |
| UpdateDisplay("400x400,400x400"); |
| Shell::RootWindowList 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[0]->GetBoundsInScreen().Contains( |
| ToEnclosingRect(GetTransformedTargetBounds(window1.get())))); |
| EXPECT_TRUE(root_windows[0]->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"); |
| Shell::RootWindowList 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(), copy1->GetBoundsInScreen()); |
| EXPECT_EQ(moved1->layer()->GetTargetTransform(), |
| copy1->layer()->GetTargetTransform()); |
| 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"); |
| Shell::RootWindowList 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"); |
| Shell::RootWindowList 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(); |
| } |
| |
| } // namespace internal |
| } // namespace ash |