| // Copyright 2019 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 <vector> |
| |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/display/screen_orientation_controller_test_api.h" |
| #include "ash/multi_user/multi_user_window_manager_impl.h" |
| #include "ash/public/cpp/ash_features.h" |
| #include "ash/public/cpp/ash_pref_names.h" |
| #include "ash/public/cpp/ash_prefs.h" |
| #include "ash/public/cpp/event_rewriter_controller.h" |
| #include "ash/public/cpp/multi_user_window_manager.h" |
| #include "ash/public/cpp/multi_user_window_manager_delegate.h" |
| #include "ash/public/cpp/shelf_model.h" |
| #include "ash/public/cpp/shelf_types.h" |
| #include "ash/screen_util.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/hotseat_widget.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_layout_manager.h" |
| #include "ash/shelf/shelf_view.h" |
| #include "ash/shell.h" |
| #include "ash/sticky_keys/sticky_keys_controller.h" |
| #include "ash/style/ash_color_provider.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/window_factory.h" |
| #include "ash/wm/desks/close_desk_button.h" |
| #include "ash/wm/desks/desk.h" |
| #include "ash/wm/desks/desk_animation_base.h" |
| #include "ash/wm/desks/desk_mini_view.h" |
| #include "ash/wm/desks/desk_name_view.h" |
| #include "ash/wm/desks/desk_preview_view.h" |
| #include "ash/wm/desks/desks_bar_view.h" |
| #include "ash/wm/desks/desks_controller.h" |
| #include "ash/wm/desks/desks_test_util.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/desks/new_desk_button.h" |
| #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_grid.h" |
| #include "ash/wm/overview/overview_item.h" |
| #include "ash/wm/overview/overview_session.h" |
| #include "ash/wm/overview/overview_window_drag_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_drag_indicators.h" |
| #include "ash/wm/splitview/split_view_utils.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/wm_event.h" |
| #include "ash/wm/workspace/backdrop_controller.h" |
| #include "ash/wm/workspace/workspace_layout_manager.h" |
| #include "ash/wm/workspace_controller.h" |
| #include "base/containers/contains.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/session_manager/session_manager_types.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/window_parenting_client.h" |
| #include "ui/aura/test/test_window_delegate.h" |
| #include "ui/base/clipboard/clipboard_buffer.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/base/ui_base_types.h" |
| #include "ui/chromeos/events/event_rewriter_chromeos.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor_extra/shadow.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/gesture_detection/gesture_configuration.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/views/background.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_delegate.h" |
| #include "ui/views/window/client_view.h" |
| #include "ui/wm/core/shadow_controller.h" |
| #include "ui/wm/core/window_modality_controller.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| void NewDesk() { |
| DesksController::Get()->NewDesk(DesksCreationRemovalSource::kButton); |
| } |
| |
| std::unique_ptr<aura::Window> CreateTransientWindow( |
| aura::Window* transient_parent, |
| const gfx::Rect& bounds) { |
| std::unique_ptr<aura::Window> window = |
| window_factory::NewWindow(nullptr, aura::client::WINDOW_TYPE_POPUP); |
| window->Init(ui::LAYER_NOT_DRAWN); |
| window->SetBounds(bounds); |
| ::wm::AddTransientChild(transient_parent, window.get()); |
| aura::client::ParentWindowWithContext( |
| window.get(), transient_parent->GetRootWindow(), bounds); |
| window->Show(); |
| return window; |
| } |
| |
| std::unique_ptr<aura::Window> CreateTransientModalChildWindow( |
| aura::Window* transient_parent) { |
| auto child = |
| CreateTransientWindow(transient_parent, gfx::Rect(20, 30, 200, 150)); |
| child->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); |
| ::wm::SetModalParent(child.get(), transient_parent); |
| return child; |
| } |
| |
| bool DoesActiveDeskContainWindow(aura::Window* window) { |
| return base::Contains(DesksController::Get()->active_desk()->windows(), |
| window); |
| } |
| |
| OverviewGrid* GetOverviewGridForRoot(aura::Window* root) { |
| DCHECK(root->IsRootWindow()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| DCHECK(overview_controller->InOverviewSession()); |
| |
| return overview_controller->overview_session()->GetGridWithRootWindow(root); |
| } |
| |
| void CloseDeskFromMiniView(const DeskMiniView* desk_mini_view, |
| ui::test::EventGenerator* event_generator) { |
| DCHECK(desk_mini_view); |
| |
| // Move to the center of the mini view so that the close button shows up. |
| const gfx::Point mini_view_center = |
| desk_mini_view->GetBoundsInScreen().CenterPoint(); |
| event_generator->MoveMouseTo(mini_view_center); |
| EXPECT_TRUE(desk_mini_view->close_desk_button()->GetVisible()); |
| // Move to the center of the close button and click. |
| event_generator->MoveMouseTo( |
| desk_mini_view->close_desk_button()->GetBoundsInScreen().CenterPoint()); |
| event_generator->ClickLeftButton(); |
| } |
| |
| void ClickOnMiniView(const DeskMiniView* desk_mini_view, |
| ui::test::EventGenerator* event_generator) { |
| DCHECK(desk_mini_view); |
| |
| const gfx::Point mini_view_center = |
| desk_mini_view->GetBoundsInScreen().CenterPoint(); |
| event_generator->MoveMouseTo(mini_view_center); |
| event_generator->ClickLeftButton(); |
| } |
| |
| void LongGestureTap(const gfx::Point& screen_location, |
| ui::test::EventGenerator* event_generator, |
| bool release_touch = true) { |
| // Temporarily reconfigure gestures so that the long tap takes 2 milliseconds. |
| ui::GestureConfiguration* gesture_config = |
| ui::GestureConfiguration::GetInstance(); |
| const int old_long_press_time_in_ms = gesture_config->long_press_time_in_ms(); |
| const int old_show_press_delay_in_ms = |
| gesture_config->show_press_delay_in_ms(); |
| gesture_config->set_long_press_time_in_ms(1); |
| gesture_config->set_show_press_delay_in_ms(1); |
| |
| event_generator->set_current_screen_location(screen_location); |
| event_generator->PressTouch(); |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::TimeDelta::FromMilliseconds(2)); |
| run_loop.Run(); |
| |
| gesture_config->set_long_press_time_in_ms(old_long_press_time_in_ms); |
| gesture_config->set_show_press_delay_in_ms(old_show_press_delay_in_ms); |
| |
| if (release_touch) |
| event_generator->ReleaseTouch(); |
| } |
| |
| void GestureTapOnView(const views::View* view, |
| ui::test::EventGenerator* event_generator) { |
| event_generator->GestureTapAt(view->GetBoundsInScreen().CenterPoint()); |
| } |
| |
| // If |drop| is false, the dragged |item| won't be dropped; giving the caller |
| // a chance to do some validations before the item is dropped. |
| void DragItemToPoint(OverviewItem* item, |
| const gfx::Point& screen_location, |
| ui::test::EventGenerator* event_generator, |
| bool by_touch_gestures = false, |
| bool drop = true) { |
| DCHECK(item); |
| |
| const gfx::Point item_center = |
| gfx::ToRoundedPoint(item->target_bounds().CenterPoint()); |
| event_generator->set_current_screen_location(item_center); |
| if (by_touch_gestures) { |
| event_generator->PressTouch(); |
| // Move the touch by an enough amount in X to engage in the normal drag mode |
| // rather than the drag to close mode. |
| event_generator->MoveTouchBy(50, 0); |
| event_generator->MoveTouch(screen_location); |
| if (drop) |
| event_generator->ReleaseTouch(); |
| } else { |
| event_generator->PressLeftButton(); |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestPoint(screen_location)); |
| event_generator->MoveMouseTo(screen_location); |
| if (drop) |
| event_generator->ReleaseLeftButton(); |
| } |
| } |
| |
| BackdropController* GetDeskBackdropController(const Desk* desk, |
| aura::Window* root) { |
| auto* workspace_controller = |
| GetWorkspaceController(desk->GetDeskContainerForRoot(root)); |
| WorkspaceLayoutManager* layout_manager = |
| workspace_controller->layout_manager(); |
| return layout_manager->backdrop_controller(); |
| } |
| |
| // Returns true if |win1| is stacked (not directly) below |win2|. |
| bool IsStackedBelow(aura::Window* win1, aura::Window* win2) { |
| DCHECK_NE(win1, win2); |
| DCHECK_EQ(win1->parent(), win2->parent()); |
| |
| const auto& children = win1->parent()->children(); |
| auto win1_iter = std::find(children.begin(), children.end(), win1); |
| auto win2_iter = std::find(children.begin(), children.end(), win2); |
| DCHECK(win1_iter != children.end()); |
| DCHECK(win2_iter != children.end()); |
| return win1_iter < win2_iter; |
| } |
| |
| // Verifies DesksBarView layout under different screen sizes |
| void DesksBarViewLayoutTestHelper(const DesksBarView* desks_bar_view, |
| aura::Window* root_window, |
| bool use_compact_layout) { |
| DCHECK(desks_bar_view); |
| const NewDeskButton* button = desks_bar_view->new_desk_button(); |
| EXPECT_EQ(button->IsLabelVisibleForTesting(), !use_compact_layout); |
| |
| for (auto* mini_view : desks_bar_view->mini_views()) { |
| EXPECT_EQ(mini_view->GetDeskPreviewForTesting()->height(), |
| DeskPreviewView::GetHeight(root_window, use_compact_layout)); |
| EXPECT_EQ(mini_view->IsDeskNameViewVisibleForTesting(), |
| !use_compact_layout); |
| } |
| } |
| |
| // Defines an observer to test DesksController notifications. |
| class TestObserver : public DesksController::Observer { |
| public: |
| TestObserver() = default; |
| ~TestObserver() override = default; |
| |
| const std::vector<const Desk*>& desks() const { return desks_; } |
| |
| // DesksController::Observer: |
| void OnDeskAdded(const Desk* desk) override { |
| desks_.emplace_back(desk); |
| EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified()); |
| } |
| void OnDeskRemoved(const Desk* desk) override { |
| base::Erase(desks_, desk); |
| EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified()); |
| } |
| void OnDeskActivationChanged(const Desk* activated, |
| const Desk* deactivated) override { |
| EXPECT_TRUE(DesksController::Get()->AreDesksBeingModified()); |
| } |
| void OnDeskSwitchAnimationLaunching() override {} |
| void OnDeskSwitchAnimationFinished() override { |
| EXPECT_FALSE(DesksController::Get()->AreDesksBeingModified()); |
| } |
| |
| private: |
| std::vector<const Desk*> desks_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestObserver); |
| }; |
| |
| class TestDeskObserver : public Desk::Observer { |
| public: |
| TestDeskObserver() = default; |
| ~TestDeskObserver() override = default; |
| |
| int notify_counts() const { return notify_counts_; } |
| |
| // Desk::Observer: |
| void OnContentChanged() override { ++notify_counts_; } |
| void OnDeskDestroyed(const Desk* desk) override {} |
| void OnDeskNameChanged(const base::string16& new_name) override {} |
| |
| private: |
| int notify_counts_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestDeskObserver); |
| }; |
| |
| // Defines a test fixture to test Virtual Desks behavior, parameterized to run |
| // some window drag tests with both touch gestures and with mouse events. |
| class DesksTest : public AshTestBase, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| DesksTest() = default; |
| ~DesksTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DesksTest); |
| }; |
| |
| TEST_F(DesksTest, DesksCreationAndRemoval) { |
| TestObserver observer; |
| auto* controller = DesksController::Get(); |
| controller->AddObserver(&observer); |
| |
| // There's always a default pre-existing desk that cannot be removed. |
| EXPECT_EQ(1u, controller->desks().size()); |
| EXPECT_FALSE(controller->CanRemoveDesks()); |
| EXPECT_TRUE(controller->CanCreateDesks()); |
| |
| // Add desks until no longer possible. |
| while (controller->CanCreateDesks()) |
| NewDesk(); |
| |
| // Expect we've reached the max number of desks, and we've been notified only |
| // with the newly created desks. |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks(), controller->desks().size()); |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks() - 1, observer.desks().size()); |
| EXPECT_TRUE(controller->CanRemoveDesks()); |
| |
| // Remove all desks until no longer possible, and expect that there's always |
| // one default desk remaining. |
| while (controller->CanRemoveDesks()) |
| RemoveDesk(observer.desks().back()); |
| |
| EXPECT_EQ(1u, controller->desks().size()); |
| EXPECT_FALSE(controller->CanRemoveDesks()); |
| EXPECT_TRUE(controller->CanCreateDesks()); |
| EXPECT_TRUE(observer.desks().empty()); |
| |
| controller->RemoveObserver(&observer); |
| } |
| |
| TEST_F(DesksTest, DesksBarViewDeskCreation) { |
| auto* controller = DesksController::Get(); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| |
| // Initially the grid is not offset down when there are no desk mini_views |
| // once animations are added. |
| EXPECT_FALSE(overview_grid->IsDesksBarViewActive()); |
| |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| |
| // Since we have a single default desk, there should be no mini_views, and the |
| // new desk button is enabled. |
| DCHECK(desks_bar_view); |
| EXPECT_TRUE(desks_bar_view->mini_views().empty()); |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| |
| // Click many times on the new desk button and expect only the max number of |
| // desks will be created, and the button is no longer enabled. |
| const gfx::Point button_center = |
| new_desk_button->GetBoundsInScreen().CenterPoint(); |
| |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(button_center); |
| for (size_t i = 0; i < desks_util::GetMaxNumberOfDesks() + 2; ++i) |
| event_generator->ClickLeftButton(); |
| |
| EXPECT_TRUE(overview_grid->IsDesksBarViewActive()); |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks(), controller->desks().size()); |
| EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size()); |
| EXPECT_FALSE(controller->CanCreateDesks()); |
| EXPECT_TRUE(controller->CanRemoveDesks()); |
| EXPECT_FALSE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(views::Button::STATE_DISABLED, new_desk_button->GetState()); |
| |
| // Hover over one of the mini_views, and expect that the close button becomes |
| // visible. |
| const auto* mini_view = desks_bar_view->mini_views().back(); |
| EXPECT_FALSE(mini_view->close_desk_button()->GetVisible()); |
| const gfx::Point mini_view_center = |
| mini_view->GetBoundsInScreen().CenterPoint(); |
| event_generator->MoveMouseTo(mini_view_center); |
| EXPECT_TRUE(mini_view->close_desk_button()->GetVisible()); |
| |
| // Use the close button to close the desk. |
| event_generator->MoveMouseTo( |
| mini_view->close_desk_button()->GetBoundsInScreen().CenterPoint()); |
| event_generator->ClickLeftButton(); |
| |
| // The new desk button is now enabled again. |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks() - 1, controller->desks().size()); |
| EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size()); |
| EXPECT_TRUE(controller->CanCreateDesks()); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(views::Button::STATE_NORMAL, new_desk_button->GetState()); |
| |
| // Exit overview mode and re-enter. Since we have more than one pre-existing |
| // desks, their mini_views should be created upon construction of the desks |
| // bar. |
| overview_controller->EndOverview(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Get the new grid and the new desk_bar_view. |
| overview_grid = |
| overview_controller->overview_session()->GetGridWithRootWindow( |
| Shell::GetPrimaryRootWindow()); |
| EXPECT_TRUE(overview_grid->IsDesksBarViewActive()); |
| desks_bar_view = overview_grid->desks_bar_view(); |
| |
| DCHECK(desks_bar_view); |
| EXPECT_EQ(controller->desks().size(), desks_bar_view->mini_views().size()); |
| EXPECT_TRUE(desks_bar_view->new_desk_button()->GetEnabled()); |
| } |
| |
| // Test that gesture taps do not reset the button state to normal when the |
| // button is disabled. https://crbug.com/1084241. |
| TEST_F(DesksTest, GestureTapOnNewDeskButton) { |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| DCHECK(desks_bar_view); |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| |
| // Gesture tap multiple times on the new desk button until it's disabled, and |
| // verify the button state. |
| auto* event_generator = GetEventGenerator(); |
| for (size_t i = 0; i < desks_util::GetMaxNumberOfDesks() + 2; ++i) |
| GestureTapOnView(new_desk_button, event_generator); |
| |
| EXPECT_FALSE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(views::Button::STATE_DISABLED, new_desk_button->GetState()); |
| } |
| |
| TEST_F(DesksTest, DesksBarViewScreenLayoutTest) { |
| UpdateDisplay("1600x1200"); |
| DesksController* controller = DesksController::Get(); |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* root_window = Shell::GetPrimaryRootWindow(); |
| const OverviewGrid* overview_grid = GetOverviewGridForRoot(root_window); |
| |
| EXPECT_FALSE(overview_grid->IsDesksBarViewActive()); |
| const DesksBarView* desks_bar_view = overview_grid->desks_bar_view(); |
| while (controller->CanCreateDesks()) { |
| NewDesk(); |
| DesksBarViewLayoutTestHelper(desks_bar_view, root_window, |
| /*use_compact_layout=*/false); |
| }; |
| |
| UpdateDisplay("500x480"); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| while (controller->CanRemoveDesks()) { |
| DesksBarViewLayoutTestHelper(desks_bar_view, root_window, |
| /*use_compact_layout=*/true); |
| RemoveDesk(controller->desks().back().get()); |
| } |
| |
| UpdateDisplay("1600x480"); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| while (controller->CanCreateDesks()) { |
| DesksBarViewLayoutTestHelper(desks_bar_view, root_window, |
| /*use_compact_layout=*/false); |
| NewDesk(); |
| } |
| } |
| |
| TEST_F(DesksTest, DeskActivation) { |
| auto* controller = DesksController::Get(); |
| ASSERT_EQ(1u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| auto* root = Shell::GetPrimaryRootWindow(); |
| EXPECT_TRUE(desk_1->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root), |
| desk_1->GetDeskContainerForRoot(root)); |
| |
| // Create three new desks, and activate one of the middle ones. |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| const Desk* desk_3 = controller->desks()[2].get(); |
| const Desk* desk_4 = controller->desks()[3].get(); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| EXPECT_FALSE(desk_1->is_active()); |
| EXPECT_TRUE(desk_2->is_active()); |
| EXPECT_FALSE(desk_3->is_active()); |
| EXPECT_FALSE(desk_4->is_active()); |
| EXPECT_FALSE(desk_1->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_TRUE(desk_2->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_FALSE(desk_3->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible()); |
| |
| // Remove the active desk, which is in the middle, activation should move to |
| // the left, so desk 1 should be activated. |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| RemoveDesk(desk_2); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| ASSERT_EQ(3u, controller->desks().size()); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(desk_1->is_active()); |
| EXPECT_FALSE(desk_3->is_active()); |
| EXPECT_FALSE(desk_4->is_active()); |
| EXPECT_TRUE(desk_1->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_FALSE(desk_3->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible()); |
| |
| // Remove the active desk, it's the first one on the left, so desk_3 (on the |
| // right) will be activated. |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| RemoveDesk(desk_1); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| ASSERT_EQ(2u, controller->desks().size()); |
| EXPECT_EQ(desk_3, controller->active_desk()); |
| EXPECT_TRUE(desk_3->is_active()); |
| EXPECT_FALSE(desk_4->is_active()); |
| EXPECT_TRUE(desk_3->GetDeskContainerForRoot(root)->IsVisible()); |
| EXPECT_FALSE(desk_4->GetDeskContainerForRoot(root)->IsVisible()); |
| } |
| |
| TEST_F(DesksTest, TestWindowPositioningPaused) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| |
| // Create two windows whose window positioning is managed. |
| const auto win0_bounds = gfx::Rect{10, 20, 250, 100}; |
| const auto win1_bounds = gfx::Rect{50, 50, 200, 200}; |
| auto win0 = CreateAppWindow(win0_bounds); |
| auto win1 = CreateAppWindow(win1_bounds); |
| WindowState* window_state = WindowState::Get(win0.get()); |
| window_state->SetWindowPositionManaged(true); |
| window_state = WindowState::Get(win1.get()); |
| window_state->SetWindowPositionManaged(true); |
| EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen()); |
| EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen()); |
| |
| // Moving one window to the second desk should not affect the bounds of either |
| // windows. |
| Desk* desk_2 = controller->desks()[1].get(); |
| controller->MoveWindowFromActiveDeskTo( |
| win1.get(), desk_2, win1->GetRootWindow(), |
| DesksMoveWindowFromActiveDeskSource::kDragAndDrop); |
| EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen()); |
| EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen()); |
| |
| // Removing a desk, which results in moving its windows to another desk should |
| // not affect the positions of those managed windows. |
| RemoveDesk(desk_2); |
| EXPECT_EQ(win0_bounds, win0->GetBoundsInScreen()); |
| EXPECT_EQ(win1_bounds, win1->GetBoundsInScreen()); |
| } |
| |
| // This test makes sure we have coverage for that desk switch animation when run |
| // with multiple displays. |
| TEST_F(DesksTest, DeskActivationDualDisplay) { |
| UpdateDisplay("600x600,400x500"); |
| |
| auto* controller = DesksController::Get(); |
| ASSERT_EQ(1u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| // Create three new desks, and activate one of the middle ones. |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| const Desk* desk_3 = controller->desks()[2].get(); |
| const Desk* desk_4 = controller->desks()[3].get(); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(controller->AreDesksBeingModified()); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| EXPECT_FALSE(desk_1->is_active()); |
| EXPECT_TRUE(desk_2->is_active()); |
| EXPECT_FALSE(desk_3->is_active()); |
| EXPECT_FALSE(desk_4->is_active()); |
| |
| auto roots = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, roots.size()); |
| EXPECT_FALSE(desk_1->GetDeskContainerForRoot(roots[0])->IsVisible()); |
| EXPECT_FALSE(desk_1->GetDeskContainerForRoot(roots[1])->IsVisible()); |
| EXPECT_TRUE(desk_2->GetDeskContainerForRoot(roots[0])->IsVisible()); |
| EXPECT_TRUE(desk_2->GetDeskContainerForRoot(roots[1])->IsVisible()); |
| EXPECT_FALSE(desk_3->GetDeskContainerForRoot(roots[0])->IsVisible()); |
| EXPECT_FALSE(desk_3->GetDeskContainerForRoot(roots[1])->IsVisible()); |
| EXPECT_FALSE(desk_4->GetDeskContainerForRoot(roots[0])->IsVisible()); |
| EXPECT_FALSE(desk_4->GetDeskContainerForRoot(roots[1])->IsVisible()); |
| } |
| |
| TEST_F(DesksTest, TransientWindows) { |
| auto* controller = DesksController::Get(); |
| ASSERT_EQ(1u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| // Create two windows, one is a transient child of the other. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateTransientWindow(win0.get(), gfx::Rect(100, 100, 100, 100)); |
| |
| EXPECT_EQ(2u, desk_1->windows().size()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get())); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get())); |
| |
| auto* root = Shell::GetPrimaryRootWindow(); |
| EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root), |
| desks_util::GetDeskContainerForContext(win0.get())); |
| EXPECT_EQ(desks_util::GetActiveDeskContainerForRoot(root), |
| desks_util::GetDeskContainerForContext(win1.get())); |
| |
| // Create a new desk and activate it. |
| NewDesk(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(desk_1->is_active()); |
| EXPECT_TRUE(desk_2->is_active()); |
| |
| // Create another transient child of the earlier transient child, and confirm |
| // it's tracked in desk_1 (even though desk_2 is the currently active one). |
| // This is because the transient parent exists in desk_1. |
| auto win2 = CreateTransientWindow(win1.get(), gfx::Rect(100, 100, 50, 50)); |
| EXPECT_EQ(3u, desk_1->windows().size()); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win2.get())); |
| auto* desk_1_container = desk_1->GetDeskContainerForRoot(root); |
| EXPECT_EQ(win0.get(), desk_1_container->children()[0]); |
| EXPECT_EQ(win1.get(), desk_1_container->children()[1]); |
| EXPECT_EQ(win2.get(), desk_1_container->children()[2]); |
| |
| // Remove the inactive desk 1, and expect that its windows, including |
| // transient will move to desk 2. |
| RemoveDesk(desk_1); |
| EXPECT_EQ(1u, controller->desks().size()); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| EXPECT_EQ(3u, desk_2->windows().size()); |
| auto* desk_2_container = desk_2->GetDeskContainerForRoot(root); |
| EXPECT_EQ(win0.get(), desk_2_container->children()[0]); |
| EXPECT_EQ(win1.get(), desk_2_container->children()[1]); |
| EXPECT_EQ(win2.get(), desk_2_container->children()[2]); |
| } |
| |
| TEST_F(DesksTest, TransientModalChildren) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| Desk* desk_3 = controller->desks()[2].get(); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| // Create three windows, one of them is a modal transient child. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateTransientModalChildWindow(win0.get()); |
| EXPECT_EQ(win1.get(), ::wm::GetModalTransient(win0.get())); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 200, 100)); |
| ASSERT_EQ(3u, desk_1->windows().size()); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| auto* desk_1_container = desk_1->GetDeskContainerForRoot(root); |
| EXPECT_EQ(win0.get(), desk_1_container->children()[0]); |
| EXPECT_EQ(win1.get(), desk_1_container->children()[1]); |
| EXPECT_EQ(win2.get(), desk_1_container->children()[2]); |
| |
| // Remove desk_1, and expect that all its windows (including the transient |
| // modal child and its parent) are moved to desk_2, and that their z-order |
| // within the container is preserved. |
| RemoveDesk(desk_1); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| ASSERT_EQ(3u, desk_2->windows().size()); |
| auto* desk_2_container = desk_2->GetDeskContainerForRoot(root); |
| EXPECT_EQ(win0.get(), desk_2_container->children()[0]); |
| EXPECT_EQ(win1.get(), desk_2_container->children()[1]); |
| EXPECT_EQ(win2.get(), desk_2_container->children()[2]); |
| EXPECT_EQ(win1.get(), ::wm::GetModalTransient(win0.get())); |
| |
| // Move only the modal child window to desk_3, and expect that its parent will |
| // move along with it, and their z-order is preserved. |
| controller->MoveWindowFromActiveDeskTo( |
| win1.get(), desk_3, win1->GetRootWindow(), |
| DesksMoveWindowFromActiveDeskSource::kDragAndDrop); |
| ASSERT_EQ(1u, desk_2->windows().size()); |
| ASSERT_EQ(2u, desk_3->windows().size()); |
| EXPECT_EQ(win2.get(), desk_2_container->children()[0]); |
| auto* desk_3_container = desk_3->GetDeskContainerForRoot(root); |
| EXPECT_EQ(win0.get(), desk_3_container->children()[0]); |
| EXPECT_EQ(win1.get(), desk_3_container->children()[1]); |
| |
| // The modality remains the same. |
| ActivateDesk(desk_3); |
| EXPECT_EQ(win1.get(), ::wm::GetModalTransient(win0.get())); |
| } |
| |
| TEST_F(DesksTest, WindowActivation) { |
| // Create three windows. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto win2 = CreateAppWindow(gfx::Rect(100, 100, 100, 100)); |
| |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win0.get())); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get())); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win2.get())); |
| |
| // Activate win0 and expects that it remains activated until we switch desks. |
| wm::ActivateWindow(win0.get()); |
| |
| // Create a new desk and activate it. Expect it's not tracking any windows |
| // yet. |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_EQ(3u, desk_1->windows().size()); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| |
| // Activate the newly-added desk. Expect that the tracked windows per each |
| // desk will remain the same. |
| ActivateDesk(desk_2); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| EXPECT_EQ(3u, desk_1->windows().size()); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| |
| // `desk_2` has no windows, so now no window should be active. However, |
| // windows on `desk_1` are activateable. |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| EXPECT_TRUE(wm::CanActivateWindow(win0.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win1.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win2.get())); |
| |
| // Create two new windows, they should now go to desk_2. |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 300, 200)); |
| auto win4 = CreateAppWindow(gfx::Rect(10, 30, 400, 200)); |
| wm::ActivateWindow(win3.get()); |
| EXPECT_EQ(2u, desk_2->windows().size()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win3.get())); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win4.get())); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win0.get())); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win1.get())); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win2.get())); |
| EXPECT_EQ(win3.get(), window_util::GetActiveWindow()); |
| |
| // Delete `win0` and expect that `desk_1`'s windows will be updated. |
| win0.reset(); |
| EXPECT_EQ(2u, desk_1->windows().size()); |
| EXPECT_EQ(2u, desk_2->windows().size()); |
| // No change in the activation. |
| EXPECT_EQ(win3.get(), window_util::GetActiveWindow()); |
| |
| // Switch back to `desk_1`. Now we can activate its windows. |
| ActivateDesk(desk_1); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_TRUE(wm::CanActivateWindow(win1.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win2.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win3.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win4.get())); |
| |
| // After `win0` has been deleted, `win2` is next on the MRU list. |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| |
| // Remove `desk_2` and expect that its windows will be moved to the active |
| // desk. |
| TestDeskObserver observer; |
| desk_1->AddObserver(&observer); |
| RemoveDesk(desk_2); |
| EXPECT_EQ(1u, controller->desks().size()); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_EQ(4u, desk_1->windows().size()); |
| // Even though two new windows have been added to desk_1, observers should be |
| // notified only once. |
| EXPECT_EQ(1, observer.notify_counts()); |
| desk_1->RemoveObserver(&observer); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win3.get())); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win4.get())); |
| |
| // `desk_2`'s windows moved to `desk_1`, but that should not change the |
| // already active window. |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| |
| // Moved windows can still be activated. |
| EXPECT_TRUE(wm::CanActivateWindow(win3.get())); |
| EXPECT_TRUE(wm::CanActivateWindow(win4.get())); |
| } |
| |
| TEST_F(DesksTest, ActivateDeskFromOverview) { |
| auto* controller = DesksController::Get(); |
| |
| // Create three desks other than the default initial desk. |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, controller->desks().size()); |
| |
| // Create two windows on desk_1. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win1.get()); |
| EXPECT_EQ(win1.get(), window_util::GetActiveWindow()); |
| |
| // Enter overview mode, and expect the desk bar is shown with exactly four |
| // desks mini views, and there are exactly two windows in the overview mode |
| // grid. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(4u, desks_bar_view->mini_views().size()); |
| EXPECT_EQ(2u, overview_grid->window_list().size()); |
| |
| // Activate desk_4 (last one on the right) by clicking on its mini view. |
| const Desk* desk_4 = controller->desks()[3].get(); |
| EXPECT_FALSE(desk_4->is_active()); |
| auto* mini_view = desks_bar_view->mini_views().back(); |
| EXPECT_EQ(desk_4, mini_view->desk()); |
| EXPECT_FALSE(mini_view->close_desk_button()->GetVisible()); |
| const gfx::Point mini_view_center = |
| mini_view->GetBoundsInScreen().CenterPoint(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(mini_view_center); |
| DeskSwitchAnimationWaiter waiter; |
| event_generator->ClickLeftButton(); |
| waiter.Wait(); |
| |
| // Expect that desk_4 is now active, and overview mode exited. |
| EXPECT_TRUE(desk_4->is_active()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| // Exiting overview mode should not restore focus to a window on a |
| // now-inactive desk. Run a loop since the overview session is destroyed async |
| // and until that happens, focus will be on the dummy |
| // "OverviewModeFocusedWidget". |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| |
| // Create one window in desk_4 and enter overview mode. Expect the grid is |
| // showing exactly one window. |
| auto win2 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win2.get()); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(1u, overview_grid->window_list().size()); |
| |
| // When exiting overview mode without changing desks, the focus should be |
| // restored to the same window. |
| overview_controller->EndOverview(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| // Run a loop since the overview session is destroyed async and until that |
| // happens, focus will be on the dummy "OverviewModeFocusedWidget". |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| } |
| |
| // This test makes sure we have coverage for that desk switch animation when run |
| // with multiple displays while overview mode is active. |
| TEST_F(DesksTest, ActivateDeskFromOverviewDualDisplay) { |
| UpdateDisplay("600x600,400x500"); |
| |
| auto* controller = DesksController::Get(); |
| |
| // Create three desks other than the default initial desk. |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, controller->desks().size()); |
| |
| // Enter overview mode. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| auto roots = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, roots.size()); |
| // Use secondary display grid. |
| const auto* overview_grid = GetOverviewGridForRoot(roots[1]); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(4u, desks_bar_view->mini_views().size()); |
| |
| // Activate desk_4 (last one on the right) by clicking on its mini view. |
| const Desk* desk_4 = controller->desks()[3].get(); |
| EXPECT_FALSE(desk_4->is_active()); |
| const auto* mini_view = desks_bar_view->mini_views().back(); |
| const gfx::Point mini_view_center = |
| mini_view->GetBoundsInScreen().CenterPoint(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(mini_view_center); |
| DeskSwitchAnimationWaiter waiter; |
| event_generator->ClickLeftButton(); |
| waiter.Wait(); |
| |
| // Expect that desk_4 is now active, and overview mode exited. |
| EXPECT_TRUE(desk_4->is_active()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F(DesksTest, RemoveInactiveDeskFromOverview) { |
| auto* controller = DesksController::Get(); |
| |
| // Create three desks other than the default initial desk. |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, controller->desks().size()); |
| |
| // Create 3 windows on desk_1. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto win2 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| |
| auto* mru_tracker = Shell::Get()->mru_window_tracker(); |
| EXPECT_EQ(std::vector<aura::Window*>({win0.get(), win2.get(), win1.get()}), |
| mru_tracker->BuildMruWindowList(DesksMruType::kActiveDesk)); |
| |
| // Active desk_4 and enter overview mode, and add a single window. |
| Desk* desk_4 = controller->desks()[3].get(); |
| ActivateDesk(desk_4); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(1u, overview_grid->window_list().size()); |
| |
| // Remove desk_1 using the close button on its mini view. desk_1 is currently |
| // inactive. Its windows should be moved to desk_4 and added to the overview |
| // grid in the MRU order (win0, win2, and win1) at the end after desk_4's |
| // existing window (win3). |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(4u, desks_bar_view->mini_views().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| auto* mini_view = desks_bar_view->mini_views().front(); |
| EXPECT_EQ(desk_1, mini_view->desk()); |
| |
| // Setup observers of both the active and inactive desks to make sure |
| // refreshing the mini_view is requested only *once* for the active desk, and |
| // never for the to-be-removed inactive desk. |
| TestDeskObserver desk_4_observer; |
| desk_4->AddObserver(&desk_4_observer); |
| TestDeskObserver desk_1_observer; |
| desk_1->AddObserver(&desk_1_observer); |
| |
| CloseDeskFromMiniView(mini_view, GetEventGenerator()); |
| |
| EXPECT_EQ(0, desk_1_observer.notify_counts()); |
| EXPECT_EQ(1, desk_4_observer.notify_counts()); |
| |
| ASSERT_EQ(3u, desks_bar_view->mini_views().size()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| ASSERT_EQ(4u, overview_grid->window_list().size()); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win0.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win1.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win2.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win3.get())); |
| // Expected order of items: win3, win0, win2, win1. |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win3.get()), |
| overview_grid->window_list()[0].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win0.get()), |
| overview_grid->window_list()[1].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win2.get()), |
| overview_grid->window_list()[2].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win1.get()), |
| overview_grid->window_list()[3].get()); |
| |
| // Make sure overview mode remains active. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Removing a desk with no windows should not result in any new mini_views |
| // updates. |
| mini_view = desks_bar_view->mini_views().front(); |
| EXPECT_TRUE(mini_view->desk()->windows().empty()); |
| CloseDeskFromMiniView(mini_view, GetEventGenerator()); |
| EXPECT_EQ(1, desk_4_observer.notify_counts()); |
| |
| // Exiting overview mode should not cause any mini_views refreshes, since the |
| // destroyed overview-specific windows do not show up in the mini_view. |
| overview_controller->EndOverview(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(1, desk_4_observer.notify_counts()); |
| desk_4->RemoveObserver(&desk_4_observer); |
| |
| // Verify that the stacking order is correct (top-most comes last, and |
| // top-most is the same as MRU). |
| EXPECT_EQ(std::vector<aura::Window*>( |
| {win3.get(), win0.get(), win2.get(), win1.get()}), |
| mru_tracker->BuildMruWindowList(DesksMruType::kActiveDesk)); |
| EXPECT_EQ(std::vector<aura::Window*>( |
| {win1.get(), win2.get(), win0.get(), win3.get()}), |
| desk_4->GetDeskContainerForRoot(Shell::GetPrimaryRootWindow()) |
| ->children()); |
| } |
| |
| TEST_F(DesksTest, RemoveActiveDeskFromOverview) { |
| auto* controller = DesksController::Get(); |
| |
| // Create one desk other than the default initial desk. |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| |
| // Create two windows on desk_1. |
| Desk* desk_1 = controller->desks()[0].get(); |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| |
| // Activate desk_2 and create one more window. |
| Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| auto win2 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win2.get()); |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| |
| // The MRU across all desks is now {win2, win3, win0, win1}. |
| auto* mru_tracker = Shell::Get()->mru_window_tracker(); |
| EXPECT_EQ(std::vector<aura::Window*>( |
| {win2.get(), win3.get(), win0.get(), win1.get()}), |
| mru_tracker->BuildMruWindowList(DesksMruType::kAllDesks)); |
| |
| // Enter overview mode, and remove desk_2 from its mini-view close button. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(2u, overview_grid->window_list().size()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| auto* mini_view = desks_bar_view->mini_views().back(); |
| EXPECT_EQ(desk_2, mini_view->desk()); |
| |
| // Setup observers of both the active and inactive desks to make sure |
| // refreshing the mini_view is requested only *once* for the soon-to-be active |
| // desk_1, and never for the to-be-removed currently active desk_2. |
| TestDeskObserver desk_1_observer; |
| desk_1->AddObserver(&desk_1_observer); |
| TestDeskObserver desk_2_observer; |
| desk_2->AddObserver(&desk_2_observer); |
| |
| CloseDeskFromMiniView(mini_view, GetEventGenerator()); |
| |
| EXPECT_EQ(1, desk_1_observer.notify_counts()); |
| EXPECT_EQ(0, desk_2_observer.notify_counts()); |
| |
| // Make sure that the desks_bar_view window is still visible, i.e. it moved to |
| // the newly-activated desk's container. |
| EXPECT_TRUE(desks_bar_view->GetWidget()->IsVisible()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow( |
| desks_bar_view->GetWidget()->GetNativeWindow())); |
| |
| // desk_1 will become active, and windows from desk_2 will move to desk_1 such |
| // that they become last in MRU order, and therefore appended at the end of |
| // the overview grid. |
| ASSERT_EQ(1u, controller->desks().size()); |
| ASSERT_EQ(1u, desks_bar_view->mini_views().size()); |
| EXPECT_TRUE(desk_1->is_active()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(4u, overview_grid->window_list().size()); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win0.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win1.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win2.get())); |
| EXPECT_TRUE(overview_grid->GetOverviewItemContaining(win3.get())); |
| |
| // The new MRU order is {win0, win1, win2, win3}. |
| EXPECT_EQ(std::vector<aura::Window*>( |
| {win0.get(), win1.get(), win2.get(), win3.get()}), |
| mru_tracker->BuildMruWindowList(DesksMruType::kActiveDesk)); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win0.get()), |
| overview_grid->window_list()[0].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win1.get()), |
| overview_grid->window_list()[1].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win2.get()), |
| overview_grid->window_list()[2].get()); |
| EXPECT_EQ(overview_grid->GetOverviewItemContaining(win3.get()), |
| overview_grid->window_list()[3].get()); |
| |
| // Make sure overview mode remains active. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Exiting overview mode should not cause any mini_views refreshes, since the |
| // destroyed overview-specific windows do not show up in the mini_view. |
| overview_controller->EndOverview(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(1, desk_1_observer.notify_counts()); |
| desk_1->RemoveObserver(&desk_1_observer); |
| } |
| |
| TEST_F(DesksTest, ActivateActiveDeskFromOverview) { |
| auto* controller = DesksController::Get(); |
| |
| // Create one more desk other than the default initial desk, so the desks bar |
| // shows up in overview mode. |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| |
| // Enter overview mode, and click on `desk_1`'s mini_view, and expect that |
| // overview mode exits since this is the already active desk. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const auto* mini_view = desks_bar_view->mini_views().front(); |
| ClickOnMiniView(mini_view, GetEventGenerator()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(desk_1->is_active()); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| } |
| |
| TEST_F(DesksTest, MinimizedWindow) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| |
| auto* window_state = WindowState::Get(win0.get()); |
| window_state->Minimize(); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| |
| // Minimized windows on the active desk show up in the MRU list... |
| EXPECT_EQ(1u, Shell::Get() |
| ->mru_window_tracker() |
| ->BuildMruWindowList(kActiveDesk) |
| .size()); |
| |
| ActivateDesk(desk_2); |
| |
| // ... But they don't once their desk is inactive. |
| EXPECT_TRUE(Shell::Get() |
| ->mru_window_tracker() |
| ->BuildMruWindowList(kActiveDesk) |
| .empty()); |
| |
| // Switching back to their desk should neither activate them nor unminimize |
| // them. |
| ActivateDesk(desk_1); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| EXPECT_NE(win0.get(), window_util::GetActiveWindow()); |
| } |
| |
| // Tests that the app list stays open when switching desks. Regression test for |
| // http://crbug.com/1138982. |
| TEST_F(DesksTest, AppListStaysOpen) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| |
| // Create one app window on each desk. |
| auto win0 = CreateAppWindow(gfx::Rect(400, 400)); |
| ActivateDesk(controller->desks()[1].get()); |
| auto win1 = CreateAppWindow(gfx::Rect(400, 400)); |
| |
| // Open the app list. |
| auto* app_list_controller = Shell::Get()->app_list_controller(); |
| app_list_controller->ShowAppList(); |
| ASSERT_TRUE(app_list_controller->IsVisible(base::nullopt)); |
| |
| // Switch back to desk 1. Test that the app list is still open. |
| ActivateDesk(controller->desks()[0].get()); |
| EXPECT_TRUE(app_list_controller->IsVisible(base::nullopt)); |
| } |
| |
| TEST_P(DesksTest, DragWindowToDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 200, 150)); |
| wm::ActivateWindow(win1.get()); |
| EXPECT_EQ(win1.get(), window_util::GetActiveWindow()); |
| |
| ui::Shadow* shadow = ::wm::ShadowController::GetShadowForWindow(win1.get()); |
| ASSERT_TRUE(shadow); |
| ASSERT_TRUE(shadow->layer()); |
| EXPECT_TRUE(shadow->layer()->GetTargetVisibility()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(2u, overview_grid->size()); |
| |
| // While in overview mode, the window's shadow is hidden. |
| EXPECT_FALSE(shadow->layer()->GetTargetVisibility()); |
| |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = overview_session->GetOverviewItemForWindow(win1.get()); |
| ASSERT_TRUE(overview_item); |
| const gfx::RectF target_bounds_before_drag = overview_item->target_bounds(); |
| |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| auto* desk_1_mini_view = desks_bar_view->mini_views()[0]; |
| EXPECT_EQ(desk_1, desk_1_mini_view->desk()); |
| // Drag it and drop it on its same desk's mini_view. Nothing happens, it |
| // should be returned back to its original target bounds. |
| auto* event_generator = GetEventGenerator(); |
| DragItemToPoint(overview_item, |
| desk_1_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator, |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(2u, overview_grid->size()); |
| EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win1.get())); |
| |
| // Now drag it to desk_2's mini_view. The overview grid should now have only |
| // `win2`, and `win1` should move to desk_2. |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| EXPECT_EQ(desk_2, desk_2_mini_view->desk()); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator, |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win1.get())); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), win1.get())); |
| EXPECT_FALSE(overview_grid->drop_target_widget()); |
| |
| // After dragging an item outside of overview to another desk, the focus |
| // should not be given to another window in overview, and should remain on the |
| // OverviewModeFocusedWidget. |
| EXPECT_EQ(overview_session->GetOverviewFocusWindow(), |
| window_util::GetActiveWindow()); |
| |
| // It is possible to select the remaining window which will activate it and |
| // exit overview. |
| overview_item = overview_session->GetOverviewItemForWindow(win2.get()); |
| ASSERT_TRUE(overview_item); |
| const auto window_center = |
| gfx::ToFlooredPoint(overview_item->target_bounds().CenterPoint()); |
| if (GetParam() /* uses gestures */) { |
| event_generator->GestureTapAt(window_center); |
| } else { |
| event_generator->MoveMouseTo(window_center); |
| event_generator->ClickLeftButton(); |
| } |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| |
| // After the window is dropped onto another desk, its shadow should be |
| // restored properly. |
| EXPECT_TRUE(shadow->layer()->GetTargetVisibility()); |
| } |
| |
| TEST_P(DesksTest, DragMinimizedWindowToDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Minimize the window before entering Overview Mode. |
| auto* window_state = WindowState::Get(window.get()); |
| window_state->Minimize(); |
| ASSERT_TRUE(window_state->IsMinimized()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| ASSERT_TRUE(overview_item); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| |
| // Drag the window to desk_2's mini_view and activate desk_2. Expect that the |
| // window will be in an unminimized state and all its visibility and layer |
| // opacity attributes are correct. |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| EXPECT_EQ(desk_2, desk_2_mini_view->desk()); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator(), |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(overview_grid->empty()); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| EXPECT_FALSE(overview_grid->drop_target_widget()); |
| DeskSwitchAnimationWaiter waiter; |
| ClickOnMiniView(desk_2_mini_view, GetEventGenerator()); |
| waiter.Wait(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(desk_2->is_active()); |
| |
| EXPECT_FALSE(window_state->IsMinimized()); |
| EXPECT_TRUE(window->IsVisible()); |
| EXPECT_TRUE(window->layer()->GetTargetVisibility()); |
| EXPECT_EQ(1.f, window->layer()->GetTargetOpacity()); |
| } |
| |
| TEST_P(DesksTest, DragAllOverviewWindowsToOtherDesksNotEndOverview) { |
| NewDesk(); |
| ASSERT_EQ(2u, DesksController::Get()->desks().size()); |
| auto win = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| ASSERT_TRUE(overview_controller->StartOverview()); |
| auto* overview_session = overview_controller->overview_session(); |
| DragItemToPoint(overview_session->GetOverviewItemForWindow(win.get()), |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()) |
| ->desks_bar_view() |
| ->mini_views()[1] |
| ->GetBoundsInScreen() |
| .CenterPoint(), |
| GetEventGenerator(), |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win.get())); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(overview_session->IsEmpty()); |
| } |
| |
| TEST_P(DesksTest, DragWindowToNonMiniViewPoints) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| ASSERT_TRUE(overview_item); |
| const gfx::RectF target_bounds_before_drag = overview_item->target_bounds(); |
| |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| |
| // Drag it and drop it on the new desk button. Nothing happens, it should be |
| // returned back to its original target bounds. |
| DragItemToPoint( |
| overview_item, |
| desks_bar_view->new_desk_button()->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator(), |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(window.get())); |
| |
| // Drag it and drop it on the center of the bottom of the display. Also, |
| // nothing should happen. |
| DragItemToPoint(overview_item, |
| window->GetRootWindow()->GetBoundsInScreen().bottom_center(), |
| GetEventGenerator(), |
| /*by_touch_gestures=*/GetParam()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| EXPECT_EQ(target_bounds_before_drag, overview_item->target_bounds()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(window.get())); |
| } |
| |
| TEST_F(DesksTest, MruWindowTracker) { |
| // Create two desks with two windows in each. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200)); |
| auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200)); |
| |
| // Build active desk's MRU window list. |
| auto* mru_window_tracker = Shell::Get()->mru_window_tracker(); |
| auto window_list = mru_window_tracker->BuildWindowForCycleList(kActiveDesk); |
| ASSERT_EQ(2u, window_list.size()); |
| EXPECT_EQ(win3.get(), window_list[0]); |
| EXPECT_EQ(win2.get(), window_list[1]); |
| |
| // Build the global MRU window list. |
| window_list = mru_window_tracker->BuildWindowForCycleList(kAllDesks); |
| ASSERT_EQ(4u, window_list.size()); |
| EXPECT_EQ(win3.get(), window_list[0]); |
| EXPECT_EQ(win2.get(), window_list[1]); |
| EXPECT_EQ(win1.get(), window_list[2]); |
| EXPECT_EQ(win0.get(), window_list[3]); |
| |
| // Switch back to desk_1 and test both MRU list types. |
| Desk* desk_1 = controller->desks()[0].get(); |
| ActivateDesk(desk_1); |
| window_list = mru_window_tracker->BuildWindowForCycleList(kActiveDesk); |
| ASSERT_EQ(2u, window_list.size()); |
| EXPECT_EQ(win1.get(), window_list[0]); |
| EXPECT_EQ(win0.get(), window_list[1]); |
| // TODO(afakhry): Check with UX if we should favor active desk's windows in |
| // the global MRU list (i.e. put them first in the list). |
| window_list = mru_window_tracker->BuildWindowForCycleList(kAllDesks); |
| ASSERT_EQ(4u, window_list.size()); |
| EXPECT_EQ(win1.get(), window_list[0]); |
| EXPECT_EQ(win3.get(), window_list[1]); |
| EXPECT_EQ(win2.get(), window_list[2]); |
| EXPECT_EQ(win0.get(), window_list[3]); |
| } |
| |
| TEST_F(DesksTest, NextActivatable) { |
| // Create two desks with two windows in each. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 300, 200)); |
| auto win3 = CreateAppWindow(gfx::Rect(10, 30, 400, 200)); |
| EXPECT_EQ(win3.get(), window_util::GetActiveWindow()); |
| |
| // When deactivating a window, the next activatable window should be on the |
| // same desk. |
| wm::DeactivateWindow(win3.get()); |
| EXPECT_EQ(win2.get(), window_util::GetActiveWindow()); |
| wm::DeactivateWindow(win2.get()); |
| EXPECT_EQ(win3.get(), window_util::GetActiveWindow()); |
| |
| // Similarly for desk_1. |
| Desk* desk_1 = controller->desks()[0].get(); |
| ActivateDesk(desk_1); |
| EXPECT_EQ(win1.get(), window_util::GetActiveWindow()); |
| win1.reset(); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| win0.reset(); |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| } |
| |
| TEST_F(DesksTest, NoMiniViewsUpdateOnOverviewEnter) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| auto* desk_1 = controller->desks()[0].get(); |
| auto* desk_2 = controller->desks()[1].get(); |
| |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| wm::ActivateWindow(win1.get()); |
| EXPECT_EQ(win1.get(), window_util::GetActiveWindow()); |
| |
| TestDeskObserver desk_1_observer; |
| TestDeskObserver desk_2_observer; |
| desk_1->AddObserver(&desk_1_observer); |
| desk_2->AddObserver(&desk_2_observer); |
| EXPECT_EQ(0, desk_1_observer.notify_counts()); |
| EXPECT_EQ(0, desk_2_observer.notify_counts()); |
| |
| // The widgets created by overview mode, whose windows are added to the active |
| // desk's container, should never result in mini_views updates since they're |
| // not mirrored there at all. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| EXPECT_EQ(0, desk_1_observer.notify_counts()); |
| EXPECT_EQ(0, desk_2_observer.notify_counts()); |
| |
| desk_1->RemoveObserver(&desk_1_observer); |
| desk_2->RemoveObserver(&desk_2_observer); |
| } |
| |
| // Tests that the new desk button's state and color are as expected. |
| TEST_F(DesksTest, NewDeskButtonStateAndColor) { |
| auto* controller = DesksController::Get(); |
| ASSERT_EQ(1u, controller->desks().size()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| const auto* new_desk_button = desks_bar_view->new_desk_button(); |
| |
| // Tests that with one or two desks, the new desk button has an enabled state |
| // and color. |
| const SkColor background_color = |
| AshColorProvider::Get()->GetControlsLayerColor( |
| AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive); |
| const SkColor disabled_background_color = |
| AshColorProvider::GetDisabledColor(background_color); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(background_color, new_desk_button->GetBackgroundColorForTesting()); |
| |
| const gfx::Point button_center = |
| new_desk_button->GetBoundsInScreen().CenterPoint(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo(button_center); |
| event_generator->ClickLeftButton(); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(background_color, new_desk_button->GetBackgroundColorForTesting()); |
| |
| // Tests that adding desks until we reach the desks limit should change the |
| // state and color of the new desk button. |
| size_t prev_size = controller->desks().size(); |
| while (controller->CanCreateDesks()) { |
| event_generator->ClickLeftButton(); |
| EXPECT_EQ(prev_size + 1, controller->desks().size()); |
| prev_size = controller->desks().size(); |
| } |
| EXPECT_FALSE(new_desk_button->GetEnabled()); |
| EXPECT_EQ(disabled_background_color, |
| new_desk_button->GetBackgroundColorForTesting()); |
| } |
| |
| class DesksWithMultiDisplayOverview : public AshTestBase { |
| public: |
| DesksWithMultiDisplayOverview() = default; |
| ~DesksWithMultiDisplayOverview() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| |
| // Start the test with two displays and two desks. |
| UpdateDisplay("600x600,400x500"); |
| NewDesk(); |
| } |
| }; |
| |
| TEST_F(DesksWithMultiDisplayOverview, DropOnSameDeskInOtherDisplay) { |
| auto roots = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, roots.size()); |
| |
| auto win = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = overview_session->GetOverviewItemForWindow(win.get()); |
| ASSERT_TRUE(overview_item); |
| |
| // The window should exist on the grid of the first display. |
| auto* grid1 = GetOverviewGridForRoot(roots[0]); |
| auto* grid2 = GetOverviewGridForRoot(roots[1]); |
| EXPECT_EQ(1u, grid1->size()); |
| EXPECT_EQ(grid1, overview_item->overview_grid()); |
| EXPECT_EQ(0u, grid2->size()); |
| |
| // Drag the item and drop it on the mini view of the same desk (i.e. desk 1) |
| // on the second display. The window should not change desks, but it should |
| // change displays (i.e. it should be removed from |grid1| and added to |
| // |grid2|). |
| const auto* desks_bar_view = grid2->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| auto* desk_1_mini_view = desks_bar_view->mini_views()[0]; |
| auto* event_generator = GetEventGenerator(); |
| DragItemToPoint(overview_item, |
| desk_1_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator, |
| /*by_touch_gestures=*/false); |
| // A new item should have been created for the window on the second grid. |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| overview_item = overview_session->GetOverviewItemForWindow(win.get()); |
| ASSERT_TRUE(overview_item); |
| EXPECT_EQ(0u, grid1->size()); |
| EXPECT_EQ(grid2, overview_item->overview_grid()); |
| EXPECT_EQ(1u, grid2->size()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(win.get())); |
| EXPECT_EQ(roots[1], win->GetRootWindow()); |
| } |
| |
| TEST_F(DesksWithMultiDisplayOverview, DropOnOtherDeskInOtherDisplay) { |
| auto roots = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, roots.size()); |
| |
| auto win = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = overview_session->GetOverviewItemForWindow(win.get()); |
| ASSERT_TRUE(overview_item); |
| |
| // The window should exist on the grid of the first display. |
| auto* grid1 = GetOverviewGridForRoot(roots[0]); |
| auto* grid2 = GetOverviewGridForRoot(roots[1]); |
| EXPECT_EQ(1u, grid1->size()); |
| EXPECT_EQ(grid1, overview_item->overview_grid()); |
| EXPECT_EQ(0u, grid2->size()); |
| |
| // Drag the item and drop it on the mini view of the second desk on the second |
| // display. The window should change desks as well as displays. |
| const auto* desks_bar_view = grid2->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| auto* event_generator = GetEventGenerator(); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator, |
| /*by_touch_gestures=*/false); |
| // The item should no longer be in any grid, since it moved to an inactive |
| // desk. |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| overview_item = overview_session->GetOverviewItemForWindow(win.get()); |
| ASSERT_FALSE(overview_item); |
| EXPECT_EQ(0u, grid1->size()); |
| EXPECT_EQ(0u, grid2->size()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win.get())); |
| EXPECT_EQ(roots[1], win->GetRootWindow()); |
| EXPECT_FALSE(win->IsVisible()); |
| auto* controller = DesksController::Get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), win.get())); |
| } |
| |
| namespace { |
| |
| PrefService* GetPrimaryUserPrefService() { |
| return Shell::Get()->session_controller()->GetPrimaryUserPrefService(); |
| } |
| |
| // Verifies that the desks restore prefs in the given |user_prefs| matches the |
| // given list of |desks_names|. |
| void VerifyDesksRestoreData(PrefService* user_prefs, |
| const std::vector<std::string>& desks_names) { |
| const base::ListValue* desks_restore_names = |
| user_prefs->GetList(prefs::kDesksNamesList); |
| ASSERT_EQ(desks_names.size(), desks_restore_names->GetSize()); |
| |
| size_t index = 0; |
| for (const auto& value : desks_restore_names->GetList()) |
| EXPECT_EQ(desks_names[index++], value.GetString()); |
| } |
| |
| } // namespace |
| |
| class DesksEditableNamesTest : public AshTestBase { |
| public: |
| DesksEditableNamesTest() = default; |
| DesksEditableNamesTest(const DesksEditableNamesTest&) = delete; |
| DesksEditableNamesTest& operator=(const DesksEditableNamesTest&) = delete; |
| ~DesksEditableNamesTest() override = default; |
| |
| DesksController* controller() { return controller_; } |
| OverviewGrid* overview_grid() { return overview_grid_; } |
| const DesksBarView* desks_bar_view() { return desks_bar_view_; } |
| |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| |
| // Begin all tests with two desks and start overview. |
| NewDesk(); |
| controller_ = DesksController::Get(); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| overview_grid_ = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| desks_bar_view_ = overview_grid_->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view_); |
| } |
| |
| void ClickOnDeskNameViewAtIndex(size_t index) { |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| ASSERT_LT(index, desks_bar_view_->mini_views().size()); |
| |
| auto* desk_name_view = |
| desks_bar_view_->mini_views()[index]->desk_name_view(); |
| auto* generator = GetEventGenerator(); |
| generator->MoveMouseTo(desk_name_view->GetBoundsInScreen().CenterPoint()); |
| generator->ClickLeftButton(); |
| } |
| |
| void SendKey(ui::KeyboardCode key_code, int flags = 0) { |
| auto* generator = GetEventGenerator(); |
| generator->PressKey(key_code, flags); |
| generator->ReleaseKey(key_code, flags); |
| } |
| |
| private: |
| DesksController* controller_ = nullptr; |
| OverviewGrid* overview_grid_ = nullptr; |
| const DesksBarView* desks_bar_view_ = nullptr; |
| }; |
| |
| TEST_F(DesksEditableNamesTest, DefaultNameChangeAborted) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Click on the desk name view of the second mini view. |
| auto* desk_name_view_2 = desks_bar_view()->mini_views()[1]->desk_name_view(); |
| EXPECT_FALSE(overview_grid()->IsDeskNameBeingModified()); |
| ClickOnDeskNameViewAtIndex(1); |
| EXPECT_TRUE(overview_grid()->IsDeskNameBeingModified()); |
| EXPECT_TRUE(desk_name_view_2->HasFocus()); |
| |
| // Pressing enter now without making any changes should not change the |
| // `is_name_set_by_user_` bit. |
| SendKey(ui::VKEY_RETURN); |
| EXPECT_FALSE(overview_grid()->IsDeskNameBeingModified()); |
| EXPECT_FALSE(desk_name_view_2->HasFocus()); |
| auto* desk_2 = controller()->desks()[1].get(); |
| EXPECT_FALSE(desk_2->is_name_set_by_user()); |
| |
| // Desks restore data should reflect two default-named desks. |
| VerifyDesksRestoreData(GetPrimaryUserPrefService(), |
| {std::string(), std::string()}); |
| } |
| |
| TEST_F(DesksEditableNamesTest, NamesSetByUsersAreNotOverwritten) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Change the name of the first desk. Adding/removing desks or |
| // exiting/reentering overview should not cause changes to the desk's name. |
| // All other names should be the default. |
| ClickOnDeskNameViewAtIndex(0); |
| // Select all and delete. |
| SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| SendKey(ui::VKEY_BACK); |
| // Type " code " (with one space at the beginning and two spaces at the end) |
| // and hit enter. |
| SendKey(ui::VKEY_SPACE); |
| SendKey(ui::VKEY_C); |
| SendKey(ui::VKEY_O); |
| SendKey(ui::VKEY_D); |
| SendKey(ui::VKEY_E); |
| SendKey(ui::VKEY_SPACE); |
| SendKey(ui::VKEY_SPACE); |
| SendKey(ui::VKEY_RETURN); |
| |
| // Extra whitespace should be trimmed. |
| auto* desk_1 = controller()->desks()[0].get(); |
| auto* desk_2 = controller()->desks()[1].get(); |
| EXPECT_EQ(base::UTF8ToUTF16("code"), desk_1->name()); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 2"), desk_2->name()); |
| EXPECT_TRUE(desk_1->is_name_set_by_user()); |
| EXPECT_FALSE(desk_2->is_name_set_by_user()); |
| |
| // Renaming desks via the mini views trigger an update to the desks restore |
| // prefs. |
| VerifyDesksRestoreData(GetPrimaryUserPrefService(), |
| {std::string("code"), std::string()}); |
| |
| // Add a third desk and remove the second. Both operations should not affect |
| // the user-modified desk names. |
| NewDesk(); |
| auto* desk_3 = controller()->desks()[2].get(); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 3"), desk_3->name()); |
| EXPECT_TRUE(desk_1->is_name_set_by_user()); |
| EXPECT_FALSE(desk_2->is_name_set_by_user()); |
| EXPECT_FALSE(desk_3->is_name_set_by_user()); |
| |
| // Adding a desk triggers an update to the restore prefs. |
| VerifyDesksRestoreData(GetPrimaryUserPrefService(), |
| {std::string("code"), std::string(), std::string()}); |
| |
| RemoveDesk(desk_2); |
| EXPECT_TRUE(desk_1->is_name_set_by_user()); |
| EXPECT_FALSE(desk_3->is_name_set_by_user()); |
| // Desk 3 will now be renamed to "Desk 2". |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 2"), desk_3->name()); |
| VerifyDesksRestoreData(GetPrimaryUserPrefService(), |
| {std::string("code"), std::string()}); |
| |
| overview_controller->EndOverview(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(desk_1->is_name_set_by_user()); |
| EXPECT_FALSE(desk_3->is_name_set_by_user()); |
| EXPECT_EQ(base::UTF8ToUTF16("code"), desk_1->name()); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 2"), desk_3->name()); |
| } |
| |
| TEST_F(DesksEditableNamesTest, DontAllowEmptyNames) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Change the name of a desk to an empty string. |
| ClickOnDeskNameViewAtIndex(0); |
| // Select all and delete. |
| SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| SendKey(ui::VKEY_BACK); |
| // At this point the desk's name is empty, but editing hasn't committed yet, |
| // so it's ok. |
| auto* desk_1 = controller()->desks()[0].get(); |
| EXPECT_TRUE(desk_1->name().empty()); |
| // Committing also works with the ESC key. |
| SendKey(ui::VKEY_ESCAPE); |
| // The name should now revert back to the default value. |
| EXPECT_FALSE(desk_1->name().empty()); |
| EXPECT_FALSE(desk_1->is_name_set_by_user()); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 1"), desk_1->name()); |
| VerifyDesksRestoreData(GetPrimaryUserPrefService(), |
| {std::string(), std::string()}); |
| } |
| |
| TEST_F(DesksEditableNamesTest, SelectAllOnFocus) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| ClickOnDeskNameViewAtIndex(0); |
| |
| auto* desk_name_view = desks_bar_view()->mini_views()[0]->desk_name_view(); |
| EXPECT_TRUE(desk_name_view->HasFocus()); |
| EXPECT_TRUE(desk_name_view->HasSelection()); |
| auto* desk_1 = controller()->desks()[0].get(); |
| EXPECT_EQ(desk_1->name(), desk_name_view->GetSelectedText()); |
| } |
| |
| TEST_F(DesksEditableNamesTest, EventsThatCommitChanges) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| ClickOnDeskNameViewAtIndex(0); |
| auto* desk_name_view = desks_bar_view()->mini_views()[0]->desk_name_view(); |
| EXPECT_TRUE(desk_name_view->HasFocus()); |
| |
| // Creating a new desk commits the changes. |
| auto* new_desk_button = desks_bar_view()->new_desk_button(); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo( |
| new_desk_button->GetBoundsInScreen().CenterPoint()); |
| event_generator->ClickLeftButton(); |
| ASSERT_EQ(3u, controller()->desks().size()); |
| EXPECT_FALSE(desk_name_view->HasFocus()); |
| |
| // Deleting a desk commits the changes. |
| ClickOnDeskNameViewAtIndex(0); |
| EXPECT_TRUE(desk_name_view->HasFocus()); |
| CloseDeskFromMiniView(desks_bar_view()->mini_views()[2], event_generator); |
| ASSERT_EQ(2u, controller()->desks().size()); |
| EXPECT_FALSE(desk_name_view->HasFocus()); |
| |
| // Clicking in the empty area of the desks bar also commits the changes. |
| ClickOnDeskNameViewAtIndex(0); |
| EXPECT_TRUE(desk_name_view->HasFocus()); |
| event_generator->MoveMouseTo(gfx::Point(2, 2)); |
| event_generator->ClickLeftButton(); |
| EXPECT_FALSE(desk_name_view->HasFocus()); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| } |
| |
| TEST_F(DesksEditableNamesTest, MaxLength) { |
| ASSERT_EQ(2u, controller()->desks().size()); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| |
| ClickOnDeskNameViewAtIndex(0); |
| // Select all and delete. |
| SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| SendKey(ui::VKEY_BACK); |
| |
| // Simulate user is typing text beyond the max length. |
| base::string16 expected_desk_name(DeskNameView::kMaxLength, L'a'); |
| for (size_t i = 0; i < DeskNameView::kMaxLength + 10; ++i) |
| SendKey(ui::VKEY_A); |
| SendKey(ui::VKEY_RETURN); |
| |
| // Desk name has been trimmed. |
| auto* desk_1 = controller()->desks()[0].get(); |
| EXPECT_EQ(DeskNameView::kMaxLength, desk_1->name().size()); |
| EXPECT_EQ(expected_desk_name, desk_1->name()); |
| EXPECT_TRUE(desk_1->is_name_set_by_user()); |
| |
| // Test that pasting a large amount of text is trimmed at the max length. |
| base::string16 clipboard_text(DeskNameView::kMaxLength + 10, L'b'); |
| expected_desk_name = base::string16(DeskNameView::kMaxLength, L'b'); |
| EXPECT_GT(clipboard_text.size(), DeskNameView::kMaxLength); |
| ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste) |
| .WriteText(clipboard_text); |
| |
| ClickOnDeskNameViewAtIndex(0); |
| // Select all and delete. |
| SendKey(ui::VKEY_A, ui::EF_CONTROL_DOWN); |
| SendKey(ui::VKEY_BACK); |
| |
| // Paste text. |
| SendKey(ui::VKEY_V, ui::EF_CONTROL_DOWN); |
| SendKey(ui::VKEY_RETURN); |
| EXPECT_EQ(DeskNameView::kMaxLength, desk_1->name().size()); |
| EXPECT_EQ(expected_desk_name, desk_1->name()); |
| } |
| |
| class TabletModeDesksTest : public DesksTest { |
| public: |
| TabletModeDesksTest() = default; |
| ~TabletModeDesksTest() override = default; |
| |
| // DesksTest: |
| void SetUp() override { |
| DesksTest::SetUp(); |
| |
| // Enter tablet mode. Avoid TabletModeController::OnGetSwitchStates() from |
| // disabling tablet mode. |
| base::RunLoop().RunUntilIdle(); |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true); |
| } |
| |
| SplitViewController* split_view_controller() { |
| return SplitViewController::Get(Shell::GetPrimaryRootWindow()); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TabletModeDesksTest); |
| }; |
| |
| TEST_F(TabletModeDesksTest, Backdrops) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Enter tablet mode and expect that the backdrop is created only for desk_1, |
| // since it's the one that has a window in it. |
| auto* desk_1_backdrop_controller = |
| GetDeskBackdropController(desk_1, Shell::GetPrimaryRootWindow()); |
| auto* desk_2_backdrop_controller = |
| GetDeskBackdropController(desk_2, Shell::GetPrimaryRootWindow()); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_TRUE(desk_1_backdrop_controller->backdrop_window()->IsVisible()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| |
| // Enter overview and expect that the backdrop is still present for desk_1 but |
| // hidden. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()->IsVisible()); |
| |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| ASSERT_TRUE(overview_item); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| |
| // Now drag it to desk_2's mini_view, so that it moves to desk_2. Expect that |
| // desk_1's backdrop is destroyed, while created (but still hidden) for |
| // desk_2. |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| EXPECT_EQ(desk_2, desk_2_mini_view->desk()); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()->IsVisible()); |
| |
| // Exit overview, and expect that desk_2's backdrop remains hidden since the |
| // desk is not activated yet. |
| overview_controller->EndOverview(OverviewEnterExitType::kImmediateExit); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()->IsVisible()); |
| |
| // Activate desk_2 and expect that its backdrop is now visible. |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_TRUE(desk_2_backdrop_controller->backdrop_window()->IsVisible()); |
| |
| // No backdrops after exiting tablet mode. |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| } |
| |
| TEST_F(TabletModeDesksTest, |
| BackdropStackingAndMiniviewsUpdatesWithOverviewDragDrop) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| auto* desk_1_backdrop_controller = |
| GetDeskBackdropController(desk_1, Shell::GetPrimaryRootWindow()); |
| auto* desk_2_backdrop_controller = |
| GetDeskBackdropController(desk_2, Shell::GetPrimaryRootWindow()); |
| |
| // Enter overview and expect that |desk_1| has a backdrop stacked under |
| // |window| while desk_2 has none. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_EQ(window.get(), desk_1_backdrop_controller->window_having_backdrop()); |
| EXPECT_FALSE(desk_2_backdrop_controller->window_having_backdrop()); |
| EXPECT_TRUE(IsStackedBelow(desk_1_backdrop_controller->backdrop_window(), |
| window.get())); |
| |
| // Prepare to drag and drop |window| on desk_2's mini view. |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(1u, overview_grid->size()); |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| ASSERT_TRUE(overview_item); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| |
| // Observe how many times a drag and drop operation updates the mini views. |
| TestDeskObserver observer1; |
| TestDeskObserver observer2; |
| desk_1->AddObserver(&observer1); |
| desk_2->AddObserver(&observer2); |
| { |
| // For this test to fail the stacking test below, we need to drag and drop |
| // while animations are enabled. https://crbug.com/1055732. |
| ui::ScopedAnimationDurationScaleMode normal_anim( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator()); |
| } |
| // The backdrop should be destroyed for |desk_1|, and a new one should be |
| // created for |window| in desk_2 and should be stacked below it. |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_1_backdrop_controller->window_having_backdrop()); |
| EXPECT_EQ(window.get(), desk_2_backdrop_controller->window_having_backdrop()); |
| EXPECT_TRUE(IsStackedBelow(desk_2_backdrop_controller->backdrop_window(), |
| window.get())); |
| |
| // The mini views should only be updated once for both desks. |
| EXPECT_EQ(1, observer1.notify_counts()); |
| EXPECT_EQ(1, observer2.notify_counts()); |
| desk_1->RemoveObserver(&observer1); |
| desk_2->RemoveObserver(&observer2); |
| } |
| |
| TEST_F(TabletModeDesksTest, NoDesksBarInTabletModeWithOneDesk) { |
| // Initially there's only one desk. |
| auto* controller = DesksController::Get(); |
| ASSERT_EQ(1u, controller->desks().size()); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Enter overview and expect that the DesksBar widget won't be created. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_FALSE(desks_bar_view); |
| |
| // It's possible to drag the window without any crashes. |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| DragItemToPoint(overview_item, window->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator(), /*by_touch_gestures=*/true); |
| |
| // Exit overview and add a new desk, then re-enter overview. Expect that now |
| // the desks bar is visible. |
| overview_controller->EndOverview(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| NewDesk(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| } |
| |
| TEST_F(TabletModeDesksTest, DesksCreationRemovalCycle) { |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Create and remove desks in a cycle while in overview mode. Expect as the |
| // containers are reused for new desks, their backdrop state are always |
| // correct, and there are no crashes as desks are removed. |
| auto* desks_controller = DesksController::Get(); |
| for (size_t i = 0; i < 2 * desks_util::GetMaxNumberOfDesks(); ++i) { |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| const Desk* desk_1 = desks_controller->desks()[0].get(); |
| const Desk* desk_2 = desks_controller->desks()[1].get(); |
| auto* desk_1_backdrop_controller = |
| GetDeskBackdropController(desk_1, Shell::GetPrimaryRootWindow()); |
| auto* desk_2_backdrop_controller = |
| GetDeskBackdropController(desk_2, Shell::GetPrimaryRootWindow()); |
| { |
| SCOPED_TRACE("Check backdrops after desk creation"); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_TRUE(desk_1_backdrop_controller->backdrop_window()->IsVisible()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| } |
| // Remove the active desk, and expect that now desk_2 should have a hidden |
| // backdrop, while the container of the removed desk_1 should have none. |
| RemoveDesk(desk_1); |
| { |
| SCOPED_TRACE("Check backdrops after desk removal"); |
| EXPECT_TRUE(desk_2->is_active()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(window.get())); |
| EXPECT_FALSE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_TRUE(desk_2_backdrop_controller->backdrop_window()->IsVisible()); |
| } |
| } |
| } |
| |
| TEST_F(TabletModeDesksTest, RestoreSplitViewOnDeskSwitch) { |
| // Create two desks with two snapped windows in each. |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win2.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| |
| // Desk 2 has no windows, so the SplitViewController should be tracking no |
| // windows. |
| ActivateDesk(desk_2); |
| EXPECT_EQ(nullptr, split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| // However, the snapped windows on desk 1 should retain their snapped state. |
| EXPECT_TRUE(WindowState::Get(win1.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win2.get())->IsSnapped()); |
| |
| // Snap two other windows in desk 2. |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win3.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win4.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(win3.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win4.get(), split_view_controller()->right_window()); |
| |
| // Switch back to desk 1, and expect the snapped windows are restored. |
| ActivateDesk(desk_1); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| EXPECT_TRUE(WindowState::Get(win3.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win4.get())->IsSnapped()); |
| } |
| |
| TEST_F(TabletModeDesksTest, SnappedStateRetainedOnSwitchingDesksFromOverview) { |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win2.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| |
| // Enter overview and switch to desk_2 using its mini_view. Overview should |
| // end, but TabletModeWindowManager should not maximize the snapped windows |
| // and they should retain their snapped state. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| auto* desks_bar_view = overview_grid->desks_bar_view(); |
| auto* mini_view = desks_bar_view->mini_views()[1]; |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| EXPECT_EQ(desk_2, mini_view->desk()); |
| { |
| DeskSwitchAnimationWaiter waiter; |
| ClickOnMiniView(mini_view, GetEventGenerator()); |
| waiter.Wait(); |
| } |
| EXPECT_TRUE(WindowState::Get(win1.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win2.get())->IsSnapped()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| |
| // Snap two other windows in desk_2, and switch back to desk_1 from overview. |
| // The snapped state should be retained for windows in both source and |
| // destination desks. |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win3.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win4.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(win3.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win4.get(), split_view_controller()->right_window()); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| desks_bar_view = overview_grid->desks_bar_view(); |
| mini_view = desks_bar_view->mini_views()[0]; |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| EXPECT_EQ(desk_1, mini_view->desk()); |
| { |
| DeskSwitchAnimationWaiter waiter; |
| ClickOnMiniView(mini_view, GetEventGenerator()); |
| waiter.Wait(); |
| } |
| EXPECT_TRUE(WindowState::Get(win1.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win2.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win3.get())->IsSnapped()); |
| EXPECT_TRUE(WindowState::Get(win4.get())->IsSnapped()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F( |
| TabletModeDesksTest, |
| SnappedStateRetainedOnSwitchingDesksWithOverviewFullOfUnsnappableWindows) { |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| const gfx::Rect work_area = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| Shell::GetPrimaryRootWindow()); |
| const gfx::Size big(work_area.width() * 2 / 3, work_area.height() * 2 / 3); |
| const gfx::Size small(250, 100); |
| std::unique_ptr<aura::Window> win1 = CreateTestWindow(gfx::Rect(small)); |
| aura::test::TestWindowDelegate win2_delegate; |
| win2_delegate.set_minimum_size(big); |
| std::unique_ptr<aura::Window> win2(CreateTestWindowInShellWithDelegate( |
| &win2_delegate, /*id=*/-1, gfx::Rect(big))); |
| aura::test::TestWindowDelegate win3_delegate; |
| win3_delegate.set_minimum_size(big); |
| std::unique_ptr<aura::Window> win3(CreateTestWindowInShellWithDelegate( |
| &win3_delegate, /*id=*/-1, gfx::Rect(big))); |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| EXPECT_TRUE(overview_controller->StartOverview()); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_FALSE(split_view_controller()->CanSnapWindow(win2.get())); |
| EXPECT_FALSE(split_view_controller()->CanSnapWindow(win3.get())); |
| |
| // Switch to |desk_2| using its |mini_view|. Split view and overview should |
| // end, but |win1| should retain its snapped state. |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| auto* desks_bar_view = overview_grid->desks_bar_view(); |
| auto* mini_view = desks_bar_view->mini_views()[1]; |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| EXPECT_EQ(desk_2, mini_view->desk()); |
| { |
| DeskSwitchAnimationWaiter waiter; |
| ClickOnMiniView(mini_view, GetEventGenerator()); |
| waiter.Wait(); |
| } |
| EXPECT_TRUE(WindowState::Get(win1.get())->IsSnapped()); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| |
| // Switch back to |desk_1| and verify that split view is arranged as before. |
| EXPECT_TRUE(overview_controller->StartOverview()); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| desks_bar_view = overview_grid->desks_bar_view(); |
| mini_view = desks_bar_view->mini_views()[0]; |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| EXPECT_EQ(desk_1, mini_view->desk()); |
| { |
| DeskSwitchAnimationWaiter waiter; |
| ClickOnMiniView(mini_view, GetEventGenerator()); |
| waiter.Wait(); |
| } |
| EXPECT_TRUE(WindowState::Get(win1.get())->IsSnapped()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F(TabletModeDesksTest, OverviewStateOnSwitchToDeskWithSplitView) { |
| // Setup two desks, one (desk_1) with two snapped windows, and the other |
| // (desk_2) with only one snapped window. |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win2.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win3.get(), SplitViewController::LEFT); |
| EXPECT_EQ(win3.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| |
| // Switching to the desk that has only one snapped window to be restored in |
| // SplitView should enter overview mode, whereas switching to one that has two |
| // snapped windows should exit overview. |
| ActivateDesk(desk_1); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| ActivateDesk(desk_2); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| ActivateDesk(desk_1); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F(TabletModeDesksTest, RemovingDesksWithSplitView) { |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| ActivateDesk(desk_2); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win2.get(), SplitViewController::RIGHT); |
| EXPECT_EQ(nullptr, split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| |
| // Removing desk_2 will cause both snapped windows to merge in SplitView. |
| RemoveDesk(desk_2); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(win2.get(), split_view_controller()->right_window()); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| } |
| |
| TEST_F(TabletModeDesksTest, RemoveDeskWithMaximizedWindowAndMergeWithSnapped) { |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| ActivateDesk(desk_2); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| EXPECT_EQ(nullptr, split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| EXPECT_TRUE(WindowState::Get(win2.get())->IsMaximized()); |
| |
| // Removing desk_2 will cause us to enter overview mode without any crashes. |
| // SplitView will remain left snapped. |
| RemoveDesk(desk_2); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| EXPECT_EQ(win1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(nullptr, split_view_controller()->right_window()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| } |
| |
| TEST_F(TabletModeDesksTest, BackdropsStacking) { |
| auto* desks_controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win1.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win2.get(), SplitViewController::RIGHT); |
| auto* desk_1_backdrop_controller = |
| GetDeskBackdropController(desk_1, Shell::GetPrimaryRootWindow()); |
| auto* desk_2_backdrop_controller = |
| GetDeskBackdropController(desk_2, Shell::GetPrimaryRootWindow()); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| |
| // The backdrop window should be stacked below both snapped windows. |
| auto* desk_1_backdrop = desk_1_backdrop_controller->backdrop_window(); |
| EXPECT_TRUE(IsStackedBelow(desk_1_backdrop, win1.get())); |
| EXPECT_TRUE(IsStackedBelow(desk_1_backdrop, win2.get())); |
| |
| // Switching to another desk doesn't change the backdrop state of the inactive |
| // desk. |
| ActivateDesk(desk_2); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| EXPECT_FALSE(desk_2_backdrop_controller->backdrop_window()); |
| EXPECT_TRUE(IsStackedBelow(desk_1_backdrop, win1.get())); |
| EXPECT_TRUE(IsStackedBelow(desk_1_backdrop, win2.get())); |
| |
| // Snapping new windows in desk_2 should update the backdrop state of desk_2, |
| // but should not affect desk_1. |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| split_view_controller()->SnapWindow(win3.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(win4.get(), SplitViewController::RIGHT); |
| ASSERT_TRUE(desk_1_backdrop_controller->backdrop_window()); |
| ASSERT_TRUE(desk_2_backdrop_controller->backdrop_window()); |
| auto* desk_2_backdrop = desk_2_backdrop_controller->backdrop_window(); |
| EXPECT_TRUE(IsStackedBelow(desk_2_backdrop, win3.get())); |
| EXPECT_TRUE(IsStackedBelow(desk_2_backdrop, win4.get())); |
| } |
| |
| TEST_F(TabletModeDesksTest, HotSeatStateAfterMovingAWindowToAnotherDesk) { |
| // Create 2 desks, and 2 windows, one of them is minimized and the other is |
| // normal. Test that dragging and dropping them to another desk does not hide |
| // the hotseat. https://crbug.com/1063536. |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto* window_state = WindowState::Get(win0.get()); |
| window_state->Minimize(); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| wm::ActivateWindow(win1.get()); |
| EXPECT_EQ(win1.get(), window_util::GetActiveWindow()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_session = overview_controller->overview_session(); |
| |
| HotseatWidget* hotseat_widget = |
| Shelf::ForWindow(win1.get())->hotseat_widget(); |
| EXPECT_EQ(HotseatState::kExtended, hotseat_widget->state()); |
| |
| const struct { |
| aura::Window* window; |
| const char* trace_message; |
| } kTestTable[] = {{win0.get(), "Minimized window"}, |
| {win1.get(), "Normal window"}}; |
| |
| for (const auto& test_case : kTestTable) { |
| SCOPED_TRACE(test_case.trace_message); |
| auto* win = test_case.window; |
| auto* overview_item = overview_session->GetOverviewItemForWindow(win); |
| ASSERT_TRUE(overview_item); |
| |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| auto* desk_2 = controller->desks()[1].get(); |
| EXPECT_EQ(desk_2, desk_2_mini_view->desk()); |
| auto* event_generator = GetEventGenerator(); |
| |
| { |
| // For this test to fail without the fix, we need to test while animations |
| // are not disabled. |
| ui::ScopedAnimationDurationScaleMode normal_anim( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator, |
| /*by_touch_gestures=*/false); |
| } |
| |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win)); |
| EXPECT_EQ(HotseatState::kExtended, hotseat_widget->state()); |
| } |
| } |
| |
| TEST_F(TabletModeDesksTest, RestoringUnsnappableWindowsInSplitView) { |
| UpdateDisplay("600x400"); |
| display::test::DisplayManagerTestApi(display_manager()) |
| .SetFirstDisplayAsInternalDisplay(); |
| |
| // Setup an app window that cannot be snapped in landscape orientation, but |
| // can be snapped in portrait orientation. |
| auto window = CreateAppWindow(gfx::Rect(350, 350)); |
| views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| widget->widget_delegate()->GetContentsView()->SetPreferredSize( |
| gfx::Size(350, 100)); |
| EXPECT_FALSE(split_view_controller()->CanSnapWindow(window.get())); |
| |
| // Change to a portrait orientation and expect it's possible to snap the |
| // window. |
| ScreenOrientationControllerTestApi test_api( |
| Shell::Get()->screen_orientation_controller()); |
| test_api.SetDisplayRotation(display::Display::ROTATE_270, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| OrientationLockType::kPortraitPrimary); |
| EXPECT_TRUE(split_view_controller()->CanSnapWindow(window.get())); |
| |
| // Snap the window in this orientation. |
| split_view_controller()->SnapWindow(window.get(), SplitViewController::LEFT); |
| EXPECT_EQ(window.get(), split_view_controller()->left_window()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| // Create a second desk, switch to it, and change back the orientation to |
| // landscape, in which the window is not snappable. The window still exists on |
| // the first desk, so nothing should change. |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| EXPECT_EQ(desk_2, controller->active_desk()); |
| test_api.SetDisplayRotation(display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| OrientationLockType::kLandscapePrimary); |
| |
| // Switch back to the first desk, and expect that SplitView is not restored, |
| // since the only available window on that desk is not snappable. |
| const Desk* desk_1 = controller->desks()[0].get(); |
| ActivateDesk(desk_1); |
| EXPECT_EQ(desk_1, controller->active_desk()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized()); |
| } |
| |
| TEST_F(DesksTest, MiniViewsTouchGestures) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(3u, desks_bar_view->mini_views().size()); |
| auto* desk_1_mini_view = desks_bar_view->mini_views()[0]; |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| auto* desk_3_mini_view = desks_bar_view->mini_views()[2]; |
| |
| // Long gesture tapping on one mini_view shows its close button, and hides |
| // those of other mini_views. |
| auto* event_generator = GetEventGenerator(); |
| LongGestureTap(desk_1_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator); |
| EXPECT_TRUE(desk_1_mini_view->close_desk_button()->GetVisible()); |
| EXPECT_FALSE(desk_2_mini_view->close_desk_button()->GetVisible()); |
| EXPECT_FALSE(desk_3_mini_view->close_desk_button()->GetVisible()); |
| LongGestureTap(desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| event_generator); |
| EXPECT_FALSE(desk_1_mini_view->close_desk_button()->GetVisible()); |
| EXPECT_TRUE(desk_2_mini_view->close_desk_button()->GetVisible()); |
| EXPECT_FALSE(desk_3_mini_view->close_desk_button()->GetVisible()); |
| |
| // Tapping on the visible close button, closes the desk rather than switches |
| // to that desk. |
| GestureTapOnView(desk_2_mini_view->close_desk_button(), event_generator); |
| ASSERT_EQ(2u, controller->desks().size()); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Tapping on the invisible close button should not result in closing that |
| // desk; rather activating that desk. |
| EXPECT_FALSE(desk_1_mini_view->close_desk_button()->GetVisible()); |
| GestureTapOnView(desk_1_mini_view->close_desk_button(), event_generator); |
| ASSERT_EQ(2u, controller->desks().size()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(controller->desks()[0]->is_active()); |
| } |
| |
| TEST_F(DesksTest, AutohiddenShelfAnimatesAfterDeskSwitch) { |
| Shelf* shelf = GetPrimaryShelf(); |
| ShelfWidget* shelf_widget = shelf->shelf_widget(); |
| const gfx::Rect shown_shelf_bounds = shelf_widget->GetWindowBoundsInScreen(); |
| |
| shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); |
| |
| // Enable animations so that we can make sure that they occur. |
| ui::ScopedAnimationDurationScaleMode regular_animations( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| NewDesk(); |
| |
| // Create a window on the first desk so that the shelf will auto-hide there. |
| std::unique_ptr<views::Widget> widget = CreateTestWidget(); |
| widget->Maximize(); |
| // LayoutShelf() forces the animation to completion, at which point the |
| // shelf should go off the screen. |
| shelf->shelf_layout_manager()->LayoutShelf(); |
| EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState()); |
| EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState()); |
| const gfx::Rect hidden_shelf_bounds = shelf_widget->GetWindowBoundsInScreen(); |
| EXPECT_NE(shown_shelf_bounds, hidden_shelf_bounds); |
| |
| // Go to the second desk. |
| ActivateDesk(DesksController::Get()->desks()[1].get()); |
| // The shelf should now want to show itself. |
| EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState()); |
| |
| // Since the layer transform animation is just starting, the transformed |
| // bounds should still be hidden. If this fails, the change was not animated. |
| gfx::RectF transformed_bounds(shelf_widget->GetWindowBoundsInScreen()); |
| shelf_widget->GetLayer()->transform().TransformRect(&transformed_bounds); |
| EXPECT_EQ(gfx::ToEnclosedRect(transformed_bounds), hidden_shelf_bounds); |
| EXPECT_EQ(shelf_widget->GetWindowBoundsInScreen(), shown_shelf_bounds); |
| |
| // Let's wait until the shelf animates to a fully shown state. |
| while (shelf_widget->GetLayer()->transform() != gfx::Transform()) { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), |
| base::TimeDelta::FromMilliseconds(200)); |
| run_loop.Run(); |
| } |
| EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState()); |
| } |
| |
| TEST_F(DesksTest, SwitchToDeskWithSnappedActiveWindow) { |
| auto* desks_controller = DesksController::Get(); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| |
| // Two virtual desks: |desk_1| (active) and |desk_2|. |
| NewDesk(); |
| ASSERT_EQ(2u, desks_controller->desks().size()); |
| Desk* desk_1 = desks_controller->desks()[0].get(); |
| Desk* desk_2 = desks_controller->desks()[1].get(); |
| |
| // Two windows on |desk_1|: |win0| (snapped) and |win1|. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| WindowState* win0_state = WindowState::Get(win0.get()); |
| WMEvent snap_to_left(WM_EVENT_CYCLE_SNAP_LEFT); |
| win0_state->OnWMEvent(&snap_to_left); |
| EXPECT_EQ(chromeos::WindowStateType::kLeftSnapped, |
| win0_state->GetStateType()); |
| |
| // Switch to |desk_2| and then back to |desk_1|. Verify that neither split |
| // view nor overview arises. |
| auto* split_view_controller = |
| SplitViewController::Get(Shell::GetPrimaryRootWindow()); |
| EXPECT_FALSE(split_view_controller->InSplitViewMode()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| ActivateDesk(desk_2); |
| EXPECT_FALSE(split_view_controller->InSplitViewMode()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| ActivateDesk(desk_1); |
| EXPECT_FALSE(split_view_controller->InSplitViewMode()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F(DesksTest, SuccessfulDragToDeskRemovesSplitViewIndicators) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| auto* overview_grid = GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| |
| auto* overview_session = overview_controller->overview_session(); |
| auto* overview_item = |
| overview_session->GetOverviewItemForWindow(window.get()); |
| ASSERT_TRUE(overview_item); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| ASSERT_TRUE(desks_bar_view); |
| ASSERT_EQ(2u, desks_bar_view->mini_views().size()); |
| |
| // Drag it to desk_2's mini_view. The overview grid should now show the |
| // "no-windows" widget, and the window should move to desk_2. |
| auto* desk_2_mini_view = desks_bar_view->mini_views()[1]; |
| DragItemToPoint(overview_item, |
| desk_2_mini_view->GetBoundsInScreen().CenterPoint(), |
| GetEventGenerator(), |
| /*by_touch_gestures=*/false, |
| /*drop=*/false); |
| // Validate that before dropping, the SplitView indicators and the drop target |
| // widget are created. |
| EXPECT_TRUE(overview_grid->drop_target_widget()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| overview_session->grid_list()[0] |
| ->split_view_drag_indicators() |
| ->current_window_dragging_state()); |
| // Now drop the window, and validate the indicators and the drop target were |
| // removed. |
| GetEventGenerator()->ReleaseLeftButton(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(overview_grid->empty()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(window.get())); |
| EXPECT_TRUE(overview_session->no_windows_widget_for_testing()); |
| EXPECT_FALSE(overview_grid->drop_target_widget()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag, |
| overview_session->grid_list()[0] |
| ->split_view_drag_indicators() |
| ->current_window_dragging_state()); |
| } |
| |
| TEST_F(DesksTest, DragAllOverviewWindowsToOtherDesksNotEndClamshellSplitView) { |
| // Two virtual desks. |
| NewDesk(); |
| ASSERT_EQ(2u, DesksController::Get()->desks().size()); |
| |
| // Two windows: |win0| (in clamshell split view) and |win1| (in overview). |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| ASSERT_TRUE(overview_controller->StartOverview()); |
| auto* overview_session = overview_controller->overview_session(); |
| auto* generator = GetEventGenerator(); |
| DragItemToPoint(overview_session->GetOverviewItemForWindow(win0.get()), |
| gfx::Point(0, 0), generator); |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| auto* split_view_controller = |
| SplitViewController::Get(Shell::GetPrimaryRootWindow()); |
| EXPECT_TRUE(split_view_controller->InSplitViewMode()); |
| |
| // Drag |win1| to the other desk. |
| DragItemToPoint(overview_session->GetOverviewItemForWindow(win1.get()), |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()) |
| ->desks_bar_view() |
| ->mini_views()[1] |
| ->GetBoundsInScreen() |
| .CenterPoint(), |
| generator); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win1.get())); |
| |
| // Overview should now be empty, but split view should still be active. |
| ASSERT_TRUE(overview_controller->InOverviewSession()); |
| EXPECT_TRUE(overview_session->IsEmpty()); |
| EXPECT_TRUE(split_view_controller->InSplitViewMode()); |
| } |
| |
| namespace { |
| |
| constexpr char kUser1Email[] = "user1@desks"; |
| constexpr char kUser2Email[] = "user2@desks"; |
| |
| } // namespace |
| |
| class DesksMultiUserTest : public NoSessionAshTestBase, |
| public MultiUserWindowManagerDelegate, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| DesksMultiUserTest() = default; |
| ~DesksMultiUserTest() override = default; |
| |
| MultiUserWindowManager* multi_user_window_manager() { |
| return multi_user_window_manager_.get(); |
| } |
| TestingPrefServiceSimple* user_1_prefs() { return user_1_prefs_; } |
| TestingPrefServiceSimple* user_2_prefs() { return user_2_prefs_; } |
| |
| // AshTestBase: |
| void SetUp() override { |
| if (GetParam()) |
| scoped_feature_list_.InitAndEnableFeature(features::kBento); |
| NoSessionAshTestBase::SetUp(); |
| |
| TestSessionControllerClient* session_controller = |
| GetSessionControllerClient(); |
| session_controller->Reset(); |
| |
| // Inject our own PrefServices for each user which enables us to setup the |
| // desks restore data before the user signs in. |
| auto user_1_prefs = std::make_unique<TestingPrefServiceSimple>(); |
| user_1_prefs_ = user_1_prefs.get(); |
| RegisterUserProfilePrefs(user_1_prefs_->registry(), /*for_test=*/true); |
| auto user_2_prefs = std::make_unique<TestingPrefServiceSimple>(); |
| user_2_prefs_ = user_2_prefs.get(); |
| RegisterUserProfilePrefs(user_2_prefs_->registry(), /*for_test=*/true); |
| session_controller->AddUserSession(kUser1Email, |
| user_manager::USER_TYPE_REGULAR, |
| /*provide_pref_service=*/false); |
| session_controller->SetUserPrefService(GetUser1AccountId(), |
| std::move(user_1_prefs)); |
| session_controller->AddUserSession(kUser2Email, |
| user_manager::USER_TYPE_REGULAR, |
| /*provide_pref_service=*/false); |
| session_controller->SetUserPrefService(GetUser2AccountId(), |
| std::move(user_2_prefs)); |
| } |
| |
| void TearDown() override { |
| multi_user_window_manager_.reset(); |
| NoSessionAshTestBase::TearDown(); |
| } |
| |
| // MultiUserWindowManagerDelegate: |
| void OnWindowOwnerEntryChanged(aura::Window* window, |
| const AccountId& account_id, |
| bool was_minimized, |
| bool teleported) override {} |
| void OnTransitionUserShelfToNewAccount() override {} |
| |
| bool IsBentoEnabled() const { return GetParam(); } |
| |
| AccountId GetUser1AccountId() const { |
| return AccountId::FromUserEmail(kUser1Email); |
| } |
| |
| AccountId GetUser2AccountId() const { |
| return AccountId::FromUserEmail(kUser2Email); |
| } |
| |
| void SwitchActiveUser(const AccountId& account_id) { |
| GetSessionControllerClient()->SwitchActiveUser(account_id); |
| } |
| |
| // Initializes the given |prefs| with a desks restore data of 3 desks, with |
| // the third desk named "code", and the rest are default-named. |
| void InitPrefsWithDesksRestoreData(PrefService* prefs) { |
| DCHECK(prefs); |
| ListPrefUpdate update(prefs, prefs::kDesksNamesList); |
| base::ListValue* pref_data = update.Get(); |
| ASSERT_TRUE(pref_data->empty()); |
| pref_data->Append(std::string()); |
| pref_data->Append(std::string()); |
| pref_data->Append(std::string("code")); |
| } |
| |
| void SimulateUserLogin(const AccountId& account_id) { |
| SwitchActiveUser(account_id); |
| multi_user_window_manager_ = |
| MultiUserWindowManager::Create(this, account_id); |
| MultiUserWindowManagerImpl::Get()->SetAnimationSpeedForTest( |
| MultiUserWindowManagerImpl::ANIMATION_SPEED_DISABLED); |
| GetSessionControllerClient()->SetSessionState( |
| session_manager::SessionState::ACTIVE); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| std::unique_ptr<MultiUserWindowManager> multi_user_window_manager_; |
| |
| TestingPrefServiceSimple* user_1_prefs_ = nullptr; |
| TestingPrefServiceSimple* user_2_prefs_ = nullptr; |
| |
| DISALLOW_COPY_AND_ASSIGN(DesksMultiUserTest); |
| }; |
| |
| TEST_P(DesksMultiUserTest, SwitchUsersBackAndForth) { |
| SimulateUserLogin(GetUser1AccountId()); |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| Desk* desk_3 = controller->desks()[2].get(); |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| multi_user_window_manager()->SetWindowOwner(win0.get(), GetUser1AccountId()); |
| EXPECT_TRUE(win0->IsVisible()); |
| ActivateDesk(desk_2); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| multi_user_window_manager()->SetWindowOwner(win1.get(), GetUser1AccountId()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_TRUE(win1->IsVisible()); |
| |
| // Switch to user_2 and expect no windows from user_1 is visible regardless of |
| // the desk. |
| SwitchActiveUser(GetUser2AccountId()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_FALSE(win1->IsVisible()); |
| |
| // Since this is the first time this user logs in, desk_1 will be activated |
| // for this user. |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 200)); |
| multi_user_window_manager()->SetWindowOwner(win2.get(), GetUser2AccountId()); |
| EXPECT_TRUE(win2->IsVisible()); |
| ActivateDesk(desk_3); |
| auto win3 = CreateAppWindow(gfx::Rect(0, 0, 250, 200)); |
| multi_user_window_manager()->SetWindowOwner(win3.get(), GetUser2AccountId()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_FALSE(win1->IsVisible()); |
| EXPECT_FALSE(win2->IsVisible()); |
| EXPECT_TRUE(win3->IsVisible()); |
| |
| // When switching back to user_1, the active desk should be restored to |
| // desk_2. |
| SwitchActiveUser(GetUser1AccountId()); |
| EXPECT_TRUE(desk_2->is_active()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_TRUE(win1->IsVisible()); |
| EXPECT_FALSE(win2->IsVisible()); |
| EXPECT_FALSE(win3->IsVisible()); |
| |
| // When switching to user_2, the active desk should be restored to desk_3. |
| SwitchActiveUser(GetUser2AccountId()); |
| EXPECT_TRUE(desk_3->is_active()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_FALSE(win1->IsVisible()); |
| EXPECT_FALSE(win2->IsVisible()); |
| EXPECT_TRUE(win3->IsVisible()); |
| } |
| |
| TEST_P(DesksMultiUserTest, RemoveDesks) { |
| SimulateUserLogin(GetUser1AccountId()); |
| // Create two desks with several windows with different app types that |
| // belong to different users. |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| multi_user_window_manager()->SetWindowOwner(win0.get(), GetUser1AccountId()); |
| EXPECT_TRUE(win0->IsVisible()); |
| ActivateDesk(desk_2); |
| auto win1 = CreateAppWindow(gfx::Rect(50, 50, 200, 200)); |
| auto win2 = CreateAppWindow(gfx::Rect(50, 50, 200, 200), AppType::ARC_APP); |
| // Non-app window. |
| auto win3 = CreateAppWindow(gfx::Rect(50, 50, 200, 200), AppType::NON_APP); |
| multi_user_window_manager()->SetWindowOwner(win2.get(), GetUser1AccountId()); |
| multi_user_window_manager()->SetWindowOwner(win1.get(), GetUser1AccountId()); |
| multi_user_window_manager()->SetWindowOwner(win3.get(), GetUser1AccountId()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_TRUE(win1->IsVisible()); |
| EXPECT_TRUE(win2->IsVisible()); |
| EXPECT_TRUE(win3->IsVisible()); |
| |
| // Switch to user_2 and expect no windows from user_1 is visible regardless of |
| // the desk. |
| SwitchActiveUser(GetUser2AccountId()); |
| EXPECT_TRUE(desk_1->is_active()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_FALSE(win1->IsVisible()); |
| EXPECT_FALSE(win2->IsVisible()); |
| EXPECT_FALSE(win3->IsVisible()); |
| |
| auto win4 = CreateAppWindow(gfx::Rect(0, 0, 250, 200)); |
| multi_user_window_manager()->SetWindowOwner(win4.get(), GetUser2AccountId()); |
| EXPECT_TRUE(win4->IsVisible()); |
| ActivateDesk(desk_2); |
| auto win5 = CreateAppWindow(gfx::Rect(0, 0, 250, 200)); |
| multi_user_window_manager()->SetWindowOwner(win5.get(), GetUser2AccountId()); |
| EXPECT_FALSE(win0->IsVisible()); |
| EXPECT_FALSE(win1->IsVisible()); |
| EXPECT_FALSE(win2->IsVisible()); |
| EXPECT_FALSE(win3->IsVisible()); |
| EXPECT_FALSE(win4->IsVisible()); |
| EXPECT_TRUE(win5->IsVisible()); |
| |
| // Delete desk_2, and expect all app windows move to desk_1. |
| RemoveDesk(desk_2); |
| EXPECT_TRUE(desk_1->is_active()); |
| auto* desk_1_container = |
| desk_1->GetDeskContainerForRoot(Shell::GetPrimaryRootWindow()); |
| EXPECT_EQ(desk_1_container, win0->parent()); |
| EXPECT_EQ(desk_1_container, win1->parent()); |
| EXPECT_EQ(desk_1_container, win2->parent()); |
| // The non-app window didn't move. |
| EXPECT_NE(desk_1_container, win3->parent()); |
| EXPECT_EQ(desk_1_container, win4->parent()); |
| EXPECT_EQ(desk_1_container, win5->parent()); |
| |
| // Only user_2's window are visible. |
| EXPECT_TRUE(win4->IsVisible()); |
| EXPECT_TRUE(win5->IsVisible()); |
| |
| // The non-app window will always be hidden. |
| EXPECT_FALSE(win3->IsVisible()); |
| |
| // Switch to user_1 and expect the correct windows' visibility. |
| SwitchActiveUser(GetUser1AccountId()); |
| EXPECT_TRUE(desk_1->is_active()); |
| EXPECT_TRUE(win0->IsVisible()); |
| EXPECT_TRUE(win1->IsVisible()); |
| EXPECT_TRUE(win2->IsVisible()); |
| EXPECT_FALSE(win3->IsVisible()); |
| |
| // Create two more desks, switch to user_2, and activate the third desk. |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| desk_2 = controller->desks()[1].get(); |
| Desk* desk_3 = controller->desks()[2].get(); |
| SwitchActiveUser(GetUser2AccountId()); |
| ActivateDesk(desk_3); |
| auto win6 = CreateAppWindow(gfx::Rect(0, 0, 250, 200)); |
| multi_user_window_manager()->SetWindowOwner(win5.get(), GetUser2AccountId()); |
| |
| // Switch back to user_1, and remove the first desk. When switching back to |
| // user_2 after that, we should see that what used to be the third desk is now |
| // active. |
| SwitchActiveUser(GetUser1AccountId()); |
| EXPECT_TRUE(desk_1->is_active()); |
| RemoveDesk(desk_1); |
| SwitchActiveUser(GetUser2AccountId()); |
| EXPECT_TRUE(desk_3->is_active()); |
| EXPECT_TRUE(win6->IsVisible()); |
| } |
| |
| TEST_P(DesksMultiUserTest, SwitchingUsersEndsOverview) { |
| SimulateUserLogin(GetUser1AccountId()); |
| OverviewController* overview_controller = Shell::Get()->overview_controller(); |
| EXPECT_TRUE(overview_controller->StartOverview()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| SwitchActiveUser(GetUser2AccountId()); |
| EXPECT_FALSE(overview_controller->InOverviewSession()); |
| } |
| |
| using DesksRestoreMultiUserTest = DesksMultiUserTest; |
| |
| TEST_P(DesksRestoreMultiUserTest, DesksRestoredFromPrimaryUserPrefsOnly) { |
| constexpr int kDefaultActiveDesk = 0; |
| constexpr int kUser1StoredActiveDesk = 2; |
| InitPrefsWithDesksRestoreData(user_1_prefs()); |
| |
| // Set the primary user1's active desk prefs to kUser1StoredActiveDesk. |
| if (IsBentoEnabled()) |
| user_1_prefs()->SetInteger(prefs::kDesksActiveDesk, kUser1StoredActiveDesk); |
| |
| SimulateUserLogin(GetUser1AccountId()); |
| // User 1 is the first to login, hence the primary user. |
| auto* controller = DesksController::Get(); |
| const auto& desks = controller->desks(); |
| |
| auto verify_desks = [&](const std::string& trace_name) { |
| SCOPED_TRACE(trace_name); |
| EXPECT_EQ(3u, desks.size()); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 1"), desks[0]->name()); |
| EXPECT_EQ(base::UTF8ToUTF16("Desk 2"), desks[1]->name()); |
| EXPECT_EQ(base::UTF8ToUTF16("code"), desks[2]->name()); |
| // Restored non-default names should be marked as `set_by_user`. |
| EXPECT_FALSE(desks[0]->is_name_set_by_user()); |
| EXPECT_FALSE(desks[1]->is_name_set_by_user()); |
| EXPECT_TRUE(desks[2]->is_name_set_by_user()); |
| }; |
| |
| verify_desks("Before switching users"); |
| if (IsBentoEnabled()) { |
| // The primary user1 should restore the saved active desk from its pref. |
| EXPECT_EQ(desks[kUser1StoredActiveDesk]->container_id(), |
| desks_util::GetActiveDeskContainerId()); |
| } |
| |
| // Switching users should not change anything as restoring happens only at |
| // the time when the first user signs in. |
| SwitchActiveUser(GetUser2AccountId()); |
| verify_desks("After switching users"); |
| if (IsBentoEnabled()) { |
| // The secondary user2 should start with a default active desk. |
| EXPECT_EQ(desks[kDefaultActiveDesk]->container_id(), |
| desks_util::GetActiveDeskContainerId()); |
| |
| // Activating the second desk in the secondary user session should not |
| // affect the primary user1's active desk pref. Moreover, switching back to |
| // user1 session should activate the user1's previously active desk |
| // correctly. |
| ActivateDesk(desks[1].get()); |
| EXPECT_EQ(user_1_prefs()->GetInteger(prefs::kDesksActiveDesk), |
| kUser1StoredActiveDesk); |
| SwitchActiveUser(GetUser1AccountId()); |
| EXPECT_EQ(desks[kUser1StoredActiveDesk]->container_id(), |
| desks_util::GetActiveDeskContainerId()); |
| } |
| } |
| |
| TEST_P(DesksRestoreMultiUserTest, |
| ChangesMadeBySecondaryUserAffectsOnlyPrimaryUserPrefs) { |
| InitPrefsWithDesksRestoreData(user_1_prefs()); |
| SimulateUserLogin(GetUser1AccountId()); |
| // Switch to user 2 (secondary) and make some desks changes. Those changes |
| // should be persisted to user 1's prefs only. |
| SwitchActiveUser(GetUser2AccountId()); |
| |
| auto* controller = DesksController::Get(); |
| const auto& desks = controller->desks(); |
| ASSERT_EQ(3u, desks.size()); |
| |
| // Create a fourth desk. |
| NewDesk(); |
| VerifyDesksRestoreData(user_1_prefs(), {std::string(), std::string(), |
| std::string("code"), std::string()}); |
| // User 2's prefs are unaffected (empty list of desks). |
| VerifyDesksRestoreData(user_2_prefs(), {}); |
| |
| // Delete the second desk. |
| RemoveDesk(desks[1].get()); |
| VerifyDesksRestoreData(user_1_prefs(), |
| {std::string(), std::string("code"), std::string()}); |
| VerifyDesksRestoreData(user_2_prefs(), {}); |
| } |
| |
| } // namespace |
| |
| // Simulates the same behavior of event rewriting that key presses go through. |
| class DesksAcceleratorsTest : public DesksTest, |
| public ui::EventRewriterChromeOS::Delegate { |
| public: |
| DesksAcceleratorsTest() = default; |
| ~DesksAcceleratorsTest() override = default; |
| |
| // DesksTest: |
| void SetUp() override { |
| DesksTest::SetUp(); |
| |
| auto* event_rewriter_controller = EventRewriterController::Get(); |
| event_rewriter_controller->AddEventRewriter( |
| std::make_unique<ui::EventRewriterChromeOS>( |
| this, Shell::Get()->sticky_keys_controller(), false)); |
| } |
| |
| // ui::EventRewriterChromeOS::Delegate: |
| bool RewriteModifierKeys() override { return true; } |
| bool GetKeyboardRemappedPrefValue(const std::string& pref_name, |
| int* result) const override { |
| return false; |
| } |
| bool TopRowKeysAreFunctionKeys() const override { return false; } |
| bool IsExtensionCommandRegistered(ui::KeyboardCode key_code, |
| int flags) const override { |
| return false; |
| } |
| bool IsSearchKeyAcceleratorReserved() const override { return true; } |
| |
| void SendAccelerator(ui::KeyboardCode key_code, int flags) { |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->PressKey(key_code, flags); |
| generator->ReleaseKey(key_code, flags); |
| } |
| |
| // Moves the overview highlight to the next item. |
| void MoveOverviewHighlighter(OverviewSession* session) { |
| session->Move(/*reverse=*/false); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DesksAcceleratorsTest); |
| }; |
| |
| namespace { |
| |
| TEST_F(DesksAcceleratorsTest, NewDesk) { |
| auto* controller = DesksController::Get(); |
| // It's possible to add up to `GetMaxNumberOfDesks()` desks using the |
| // shortcut. |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| for (size_t num_desks = 1; num_desks < desks_util::GetMaxNumberOfDesks(); |
| ++num_desks) { |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_PLUS, flags); |
| waiter.Wait(); |
| // The newly created desk should be activated. |
| ASSERT_EQ(num_desks + 1, controller->desks().size()); |
| EXPECT_TRUE(controller->desks().back()->is_active()); |
| } |
| |
| // When we reach the limit, the shortcut does nothing. |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks(), controller->desks().size()); |
| SendAccelerator(ui::VKEY_OEM_PLUS, flags); |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks(), controller->desks().size()); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, CannotRemoveLastDesk) { |
| auto* controller = DesksController::Get(); |
| // Removing the last desk is not possible. |
| ASSERT_EQ(1u, controller->desks().size()); |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| SendAccelerator(ui::VKEY_OEM_MINUS, flags); |
| ASSERT_EQ(1u, controller->desks().size()); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, RemoveDesk) { |
| auto* controller = DesksController::Get(); |
| // Create a few desks and remove them outside and inside overview using the |
| // shortcut. |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| Desk* desk_3 = controller->desks()[2].get(); |
| EXPECT_TRUE(desk_1->is_active()); |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_MINUS, flags); |
| waiter.Wait(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| EXPECT_TRUE(desk_2->is_active()); |
| |
| // Using the accelerator doesn't result in exiting overview. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| SendAccelerator(ui::VKEY_OEM_MINUS, flags); |
| ASSERT_EQ(1u, controller->desks().size()); |
| EXPECT_TRUE(desk_3->is_active()); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, RemoveRightmostDesk) { |
| auto* controller = DesksController::Get(); |
| // Create a few desks and remove them outside and inside overview using the |
| // shortcut. |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(3u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| Desk* desk_3 = controller->desks()[2].get(); |
| ActivateDesk(desk_3); |
| EXPECT_TRUE(desk_3->is_active()); |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| { |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_MINUS, flags); |
| waiter.Wait(); |
| } |
| ASSERT_EQ(2u, controller->desks().size()); |
| EXPECT_TRUE(desk_2->is_active()); |
| { |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_MINUS, flags); |
| waiter.Wait(); |
| } |
| ASSERT_EQ(1u, controller->desks().size()); |
| EXPECT_TRUE(desk_1->is_active()); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, LeftRightDeskActivation) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(desk_1->is_active()); |
| // No desk on left, nothing should happen. |
| const int flags = ui::EF_COMMAND_DOWN; |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| // Go right until there're no more desks on the right. |
| { |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| waiter.Wait(); |
| EXPECT_TRUE(desk_2->is_active()); |
| } |
| |
| // Nothing happens. |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_TRUE(desk_2->is_active()); |
| |
| // Go back left. |
| { |
| DeskSwitchAnimationWaiter waiter; |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| waiter.Wait(); |
| EXPECT_TRUE(desk_1->is_active()); |
| } |
| } |
| |
| TEST_F(DesksAcceleratorsTest, MoveWindowLeftRightDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Moving window left when this is the left-most desk. Nothing happens. |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| EXPECT_TRUE(DoesActiveDeskContainWindow(window.get())); |
| |
| // Move window right, it should be deactivated. |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| EXPECT_TRUE(desk_1->windows().empty()); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| |
| // No more active windows on this desk, nothing happens. |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_TRUE(desk_1->windows().empty()); |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| |
| // Activate desk 2, and do the same set of tests. |
| ActivateDesk(desk_2); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| // Move right does nothing. |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_EQ(window.get(), window_util::GetActiveWindow()); |
| |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| EXPECT_EQ(nullptr, window_util::GetActiveWindow()); |
| EXPECT_TRUE(base::Contains(desk_1->windows(), window.get())); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, MoveWindowLeftRightDeskOverview) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| // In overview, while no window is highlighted, nothing should happen. |
| const size_t num_windows_before = desk_1->windows().size(); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| ASSERT_EQ(num_windows_before, desk_1->windows().size()); |
| EXPECT_TRUE(desk_2->windows().empty()); |
| |
| auto* overview_session = overview_controller->overview_session(); |
| ASSERT_TRUE(overview_session); |
| // It's possible to move the highlighted window. |Move()| will cycle through |
| // the desk items first, so call it until we are highlighting an OverviewItem. |
| while (!overview_session->GetHighlightedWindow()) |
| MoveOverviewHighlighter(overview_session); |
| EXPECT_EQ(win0.get(), overview_session->GetHighlightedWindow()); |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win0.get())); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), win0.get())); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // The highlight widget should move to the next window if we call |
| // |MoveOverviewHighlighter()| again. |
| MoveOverviewHighlighter(overview_session); |
| EXPECT_EQ(win1.get(), overview_session->GetHighlightedWindow()); |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win1.get())); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), win1.get())); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // No more highlighted windows. |
| EXPECT_FALSE(overview_session->GetHighlightedWindow()); |
| } |
| |
| TEST_F(DesksAcceleratorsTest, CannotMoveAlwaysOnTopWindows) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| ASSERT_EQ(2u, controller->desks().size()); |
| Desk* desk_1 = controller->desks()[0].get(); |
| Desk* desk_2 = controller->desks()[1].get(); |
| EXPECT_TRUE(desk_1->is_active()); |
| |
| // An always-on-top window does not belong to any desk and hence cannot be |
| // removed. |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| win0->SetProperty(aura::client::kZOrderingKey, |
| ui::ZOrderLevel::kFloatingWindow); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| EXPECT_FALSE(DoesActiveDeskContainWindow(win0.get())); |
| EXPECT_FALSE(controller->MoveWindowFromActiveDeskTo( |
| win0.get(), desk_2, win0->GetRootWindow(), |
| DesksMoveWindowFromActiveDeskSource::kDragAndDrop)); |
| const int flags = ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN; |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| EXPECT_EQ(win0.get(), window_util::GetActiveWindow()); |
| EXPECT_TRUE(win0->IsVisible()); |
| |
| // It remains visible even after switching desks. |
| ActivateDesk(desk_2); |
| EXPECT_TRUE(win0->IsVisible()); |
| } |
| |
| // Tests that hitting an acclerator to switch desks does not cause a crash if we |
| // are already at an edge desk. Regression test for https://crbug.com/1159068. |
| TEST_F(DesksAcceleratorsTest, HitAcceleratorWhenAlreadyAtEdge) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature(features::kEnhancedDeskAnimations); |
| |
| NewDesk(); |
| |
| // Enable animations so that we can make sure that they occur. |
| ui::ScopedAnimationDurationScaleMode regular_animations( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // First go right. Wait until the ending screenshot is taken. |
| const int flags = ui::EF_COMMAND_DOWN; |
| SendAccelerator(ui::VKEY_OEM_6, flags); |
| |
| DeskAnimationBase* animation = DesksController::Get()->animation(); |
| ASSERT_TRUE(animation); |
| base::RunLoop run_loop; |
| auto* desk_switch_animator = |
| animation->GetDeskSwitchAnimatorAtIndexForTesting(0); |
| ASSERT_TRUE(desk_switch_animator); |
| RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator) |
| .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| // Tap the accelerator to go left to desk 1, then tap again. There should be |
| // no crash. |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| SendAccelerator(ui::VKEY_OEM_4, flags); |
| } |
| |
| class PerDeskShelfTest : public AshTestBase, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| PerDeskShelfTest() = default; |
| PerDeskShelfTest(const PerDeskShelfTest&) = delete; |
| PerDeskShelfTest& operator=(const PerDeskShelfTest&) = delete; |
| ~PerDeskShelfTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| if (GetParam()) { |
| scoped_feature_list_.InitAndEnableFeature(features::kPerDeskShelf); |
| } else { |
| scoped_feature_list_.InitAndDisableFeature(features::kPerDeskShelf); |
| } |
| |
| AshTestBase::SetUp(); |
| } |
| |
| // Creates and returns an app window that is asscoaited with a shelf item with |
| // |type|. |
| std::unique_ptr<aura::Window> CreateAppWithShelfItem(ShelfItemType type) { |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 250, 100)); |
| const ash::ShelfID shelf_id(base::StringPrintf("%d", current_shelf_id_++)); |
| window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize()); |
| window->SetProperty(ash::kAppIDKey, shelf_id.app_id); |
| window->SetProperty<int>(ash::kShelfItemTypeKey, type); |
| ShelfItem item; |
| item.status = ShelfItemStatus::STATUS_RUNNING; |
| item.type = type; |
| item.id = shelf_id; |
| ShelfModel::Get()->Add(item); |
| return window; |
| } |
| |
| bool IsPerDeskShelfEnabled() const { return GetParam(); } |
| |
| ShelfView* GetShelfView() const { |
| return GetPrimaryShelf()->GetShelfViewForTesting(); |
| } |
| |
| // Returns the index of the shelf item associated with the given |window| or |
| // -1 if no such item exists. |
| int GetShelfItemIndexForWindow(aura::Window* window) const { |
| const auto shelf_id = |
| ShelfID::Deserialize(window->GetProperty(kShelfIDKey)); |
| EXPECT_FALSE(shelf_id.IsNull()); |
| return ShelfModel::Get()->ItemIndexByID(shelf_id); |
| } |
| |
| // Verifies that the visibility of the shelf item view associated with the |
| // given |window| is equal to the given |expected_visibility|. |
| void VerifyViewVisibility(aura::Window* window, |
| bool expected_visibility) const { |
| const int index = GetShelfItemIndexForWindow(window); |
| EXPECT_GE(index, 0); |
| auto* shelf_view = GetShelfView(); |
| auto* view_model = shelf_view->view_model(); |
| |
| views::View* item_view = view_model->view_at(index); |
| const bool contained_in_visible_indices = |
| base::Contains(shelf_view->visible_views_indices(), index); |
| |
| EXPECT_EQ(expected_visibility, item_view->GetVisible()); |
| EXPECT_EQ(expected_visibility, contained_in_visible_indices); |
| } |
| |
| void MoveWindowFromActiveDeskTo(aura::Window* window, |
| Desk* target_desk) const { |
| DesksController::Get()->MoveWindowFromActiveDeskTo( |
| window, target_desk, window->GetRootWindow(), |
| DesksMoveWindowFromActiveDeskSource::kDragAndDrop); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| int current_shelf_id_ = 0; |
| }; |
| |
| TEST_P(PerDeskShelfTest, MoveWindowOutOfActiveDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| |
| // Create three app windows; a browser window, a pinned app, and a normal app. |
| auto win0 = CreateAppWithShelfItem(ShelfItemType::TYPE_BROWSER_SHORTCUT); |
| aura::Window* browser = win0.get(); |
| auto win1 = CreateAppWithShelfItem(ShelfItemType::TYPE_PINNED_APP); |
| aura::Window* pinned = win1.get(); |
| auto win2 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app = win2.get(); |
| |
| // All items should be visible. |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(pinned, true); |
| VerifyViewVisibility(app, true); |
| |
| // Move the app window, it should be removed from the shelf if the feature is |
| // enabled. |
| const bool visible_in_per_desk_shelf = IsPerDeskShelfEnabled() ? false : true; |
| Desk* desk_2 = controller->desks()[1].get(); |
| MoveWindowFromActiveDeskTo(app, desk_2); |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(pinned, true); |
| VerifyViewVisibility(app, visible_in_per_desk_shelf); |
| |
| // Move the pinned app and the browser window, they should remain visible on |
| // the shelf even though they're on an inactive desk now. |
| MoveWindowFromActiveDeskTo(pinned, desk_2); |
| MoveWindowFromActiveDeskTo(browser, desk_2); |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(pinned, true); |
| VerifyViewVisibility(app, visible_in_per_desk_shelf); |
| } |
| |
| TEST_P(PerDeskShelfTest, DeskSwitching) { |
| // Create two more desks, so total is three. |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| NewDesk(); |
| |
| // On desk_1, create a browser and a normal app. |
| auto win0 = CreateAppWithShelfItem(ShelfItemType::TYPE_BROWSER_SHORTCUT); |
| aura::Window* browser = win0.get(); |
| auto win1 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app1 = win1.get(); |
| |
| // Switch to desk_2, only the browser app should be visible on the shelf if |
| // the feature is enabled. |
| const bool visible_in_per_desk_shelf = IsPerDeskShelfEnabled() ? false : true; |
| Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(app1, visible_in_per_desk_shelf); |
| |
| // On desk_2, create a pinned app. |
| auto win2 = CreateAppWithShelfItem(ShelfItemType::TYPE_PINNED_APP); |
| aura::Window* pinned = win2.get(); |
| VerifyViewVisibility(pinned, true); |
| |
| // Switch to desk_3, only the browser and the pinned app should be visible on |
| // the shelf if the feature is enabled. |
| Desk* desk_3 = controller->desks()[2].get(); |
| ActivateDesk(desk_3); |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(app1, visible_in_per_desk_shelf); |
| VerifyViewVisibility(pinned, true); |
| |
| // On desk_3, create a normal app, then switch back to desk_1. app1 should |
| // show again on the shelf. |
| auto win3 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app2 = win3.get(); |
| Desk* desk_1 = controller->desks()[0].get(); |
| ActivateDesk(desk_1); |
| VerifyViewVisibility(browser, true); |
| VerifyViewVisibility(app1, true); |
| VerifyViewVisibility(pinned, true); |
| VerifyViewVisibility(app2, visible_in_per_desk_shelf); |
| } |
| |
| TEST_P(PerDeskShelfTest, RemoveInactiveDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| |
| // On desk_1, create two apps. |
| auto win0 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app1 = win0.get(); |
| auto win1 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app2 = win1.get(); |
| |
| // Switch to desk_2, no apps should show on the shelf if the feature is |
| // enabled. |
| const bool visible_in_per_desk_shelf = IsPerDeskShelfEnabled() ? false : true; |
| Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| VerifyViewVisibility(app1, visible_in_per_desk_shelf); |
| VerifyViewVisibility(app2, visible_in_per_desk_shelf); |
| |
| // Remove desk_1 (inactive), apps should now show on the shelf. |
| Desk* desk_1 = controller->desks()[0].get(); |
| RemoveDesk(desk_1); |
| VerifyViewVisibility(app1, true); |
| VerifyViewVisibility(app2, true); |
| } |
| |
| TEST_P(PerDeskShelfTest, RemoveActiveDesk) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| |
| // On desk_1, create an app. |
| auto win0 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app1 = win0.get(); |
| |
| // Switch to desk_2, no apps should show on the shelf if the feature is |
| // enabled. |
| const bool visible_in_per_desk_shelf = IsPerDeskShelfEnabled() ? false : true; |
| Desk* desk_2 = controller->desks()[1].get(); |
| ActivateDesk(desk_2); |
| VerifyViewVisibility(app1, visible_in_per_desk_shelf); |
| |
| // Create another app on desk_2. |
| auto win1 = CreateAppWithShelfItem(ShelfItemType::TYPE_APP); |
| aura::Window* app2 = win1.get(); |
| |
| // Remove desk_2 (active), all apps should now show on the shelf. |
| RemoveDesk(desk_2); |
| VerifyViewVisibility(app1, true); |
| VerifyViewVisibility(app2, true); |
| } |
| |
| // A test class that uses a mock time task environment. |
| class DesksMockTimeTest : public AshTestBase { |
| public: |
| DesksMockTimeTest() |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| DesksMockTimeTest(const DesksMockTimeTest&) = delete; |
| DesksMockTimeTest& operator=(const DesksMockTimeTest&) = delete; |
| ~DesksMockTimeTest() override = default; |
| }; |
| |
| // crbug.com/1163489 |
| TEST_F(DesksMockTimeTest, DISABLED_DeskTraversalNonTouchpadMetrics) { |
| NewDesk(); |
| NewDesk(); |
| NewDesk(); |
| ASSERT_EQ(4u, DesksController::Get()->desks().size()); |
| |
| constexpr char kDeskTraversalsHistogramName[] = |
| "Ash.Desks.NumberOfDeskTraversals"; |
| |
| base::HistogramTester histogram_tester; |
| auto* controller = DesksController::Get(); |
| const auto& desks = controller->desks(); |
| ASSERT_EQ(controller->active_desk(), desks[0].get()); |
| |
| // Move 5 desks. There is nothing recorded at the end since the timer is still |
| // running. |
| ActivateDesk(desks[1].get()); |
| ActivateDesk(desks[0].get()); |
| ActivateDesk(desks[1].get()); |
| ActivateDesk(desks[2].get()); |
| ActivateDesk(desks[3].get()); |
| histogram_tester.ExpectBucketCount(kDeskTraversalsHistogramName, 5, 0); |
| |
| // Advance the time to end the timer. There should be 5 desks recorded. |
| task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(8)); |
| histogram_tester.ExpectBucketCount(kDeskTraversalsHistogramName, 5, 1); |
| } |
| |
| // A test class for testing Bento features. |
| class DesksBentoTest : public DesksTest { |
| public: |
| DesksBentoTest() = default; |
| DesksBentoTest(const DesksBentoTest&) = delete; |
| DesksBentoTest& operator=(const DesksBentoTest&) = delete; |
| ~DesksBentoTest() override = default; |
| |
| // DesksTest: |
| void SetUp() override { |
| scoped_feature_list_.InitAndEnableFeature(features::kBento); |
| DesksTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests desks name nudges, i.e. when a user creates a new desk, focus + clear |
| // the new desk's renaming textfield. |
| TEST_F(DesksBentoTest, NameNudges) { |
| // Make sure the display is large enough to hold the max number of desks. |
| UpdateDisplay("1200x800"); |
| auto* controller = DesksController::Get(); |
| |
| // Start overview. |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| // Hover over the new desk button. |
| const auto* overview_grid = |
| GetOverviewGridForRoot(Shell::GetPrimaryRootWindow()); |
| const auto* desks_bar_view = overview_grid->desks_bar_view(); |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| EXPECT_TRUE(new_desk_button->GetEnabled()); |
| auto* event_generator = GetEventGenerator(); |
| event_generator->MoveMouseTo( |
| new_desk_button->GetBoundsInScreen().CenterPoint()); |
| |
| // Click on the new desk button until the max number of desks is created. Each |
| // time a new desk is created the new desk's name view should have focus, be |
| // empty and have its accessible name set to the default desk name. Also, the |
| // previous desk should be left with a default name. |
| for (size_t i = 1; i < desks_util::GetMaxNumberOfDesks(); ++i) { |
| event_generator->ClickLeftButton(); |
| auto* desk_name_view = desks_bar_view->mini_views()[i]->desk_name_view(); |
| EXPECT_TRUE(desk_name_view->HasFocus()); |
| EXPECT_EQ(base::string16(), controller->desks()[i]->name()); |
| EXPECT_EQ(DesksController::GetDeskDefaultName(i), |
| desk_name_view->GetAccessibleName()); |
| EXPECT_EQ(DesksController::GetDeskDefaultName(i - 1), |
| controller->desks()[i - 1]->name()); |
| } |
| } |
| |
| TEST_F(DesksBentoTest, ScrollableDesks) { |
| UpdateDisplay("301x600"); |
| auto* overview_controller = Shell::Get()->overview_controller(); |
| overview_controller->StartOverview(); |
| EXPECT_TRUE(overview_controller->InOverviewSession()); |
| |
| auto* root_window = Shell::GetPrimaryRootWindow(); |
| const auto* desks_bar_view = |
| GetOverviewGridForRoot(root_window)->desks_bar_view(); |
| auto* new_desk_button = desks_bar_view->new_desk_button(); |
| |
| // Set the scroll delta large enough to make sure the desks bar can be |
| // scrolled to the end each time. |
| const int x_scroll_delta = 200; |
| gfx::Rect display_bounds = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| root_window); |
| auto* event_generator = GetEventGenerator(); |
| for (size_t i = 1; i < desks_util::GetMaxNumberOfDesks(); i++) { |
| gfx::Rect new_desk_button_bounds = new_desk_button->GetBoundsInScreen(); |
| event_generator->MoveMouseTo(new_desk_button_bounds.CenterPoint()); |
| EXPECT_TRUE(display_bounds.Contains(new_desk_button_bounds)); |
| event_generator->ClickLeftButton(); |
| // Scroll right to make sure the new desk button is always inside the |
| // display. |
| event_generator->MoveMouseWheel(-x_scroll_delta, 0); |
| } |
| |
| auto* controller = DesksController::Get(); |
| EXPECT_EQ(desks_util::GetMaxNumberOfDesks(), controller->desks().size()); |
| EXPECT_FALSE(controller->CanCreateDesks()); |
| |
| EXPECT_TRUE(display_bounds.Contains(new_desk_button->GetBoundsInScreen())); |
| EXPECT_FALSE(display_bounds.Contains( |
| desks_bar_view->mini_views()[0]->GetBoundsInScreen())); |
| event_generator->MoveMouseWheel(x_scroll_delta, 0); |
| // Tests that scroll to the left will put the first desk inside the display. |
| EXPECT_TRUE(display_bounds.Contains( |
| desks_bar_view->mini_views()[0]->GetBoundsInScreen())); |
| EXPECT_FALSE(display_bounds.Contains(new_desk_button->GetBoundsInScreen())); |
| } |
| |
| // Tests that the bounds of a window that is visible on all desks is shared |
| // across desks. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksGlobalBounds) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| const gfx::Rect window_initial_bounds(1, 1, 200, 200); |
| const gfx::Rect window_moved_bounds(200, 200, 250, 250); |
| |
| auto window = CreateAppWindow(window_initial_bounds); |
| auto* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| ASSERT_EQ(window_initial_bounds, window->bounds()); |
| |
| // Assign |window| to all desks. It shouldn't change bounds. |
| widget->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_EQ(window_initial_bounds, window->bounds()); |
| EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size()); |
| |
| // Move to desk 2. The only window on the new desk should be |window| |
| // and it should have the same bounds. |
| ActivateDesk(desk_2); |
| auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(1u, desk_2_children.size()); |
| EXPECT_EQ(window.get(), desk_2_children[0]); |
| EXPECT_EQ(window_initial_bounds, window->bounds()); |
| |
| // Change |window|'s bounds and move to desk 1. It should retain its moved |
| // bounds. |
| window->SetBounds(window_moved_bounds); |
| EXPECT_EQ(window_moved_bounds, window->bounds()); |
| ActivateDesk(desk_1); |
| auto desk_1_children = desk_1->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(1u, desk_1_children.size()); |
| EXPECT_EQ(window.get(), desk_1_children[0]); |
| EXPECT_EQ(window_moved_bounds, window->bounds()); |
| } |
| |
| // Tests that the z-ordering of windows that are visible on all desks respects |
| // its global MRU ordering. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksGlobalZOrder) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(1, 1, 150, 150)); |
| auto win2 = CreateAppWindow(gfx::Rect(2, 2, 200, 200)); |
| auto* widget0 = views::Widget::GetWidgetForNativeWindow(win0.get()); |
| auto* widget1 = views::Widget::GetWidgetForNativeWindow(win1.get()); |
| auto* widget2 = views::Widget::GetWidgetForNativeWindow(win2.get()); |
| ASSERT_TRUE(IsStackedBelow(win0.get(), win1.get())); |
| ASSERT_TRUE(IsStackedBelow(win1.get(), win2.get())); |
| |
| // Assign |win1| to all desks. It shouldn't change stacking order. |
| widget1->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_TRUE(IsStackedBelow(win0.get(), win1.get())); |
| EXPECT_TRUE(IsStackedBelow(win1.get(), win2.get())); |
| EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size()); |
| |
| // Move to desk 2. The only window on the new desk should be |win1|. |
| ActivateDesk(desk_2); |
| auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(1u, desk_2_children.size()); |
| EXPECT_EQ(win1.get(), desk_2_children[0]); |
| |
| // Move to desk 1. Since |win1| was activated last, |win1| should be on top |
| // of the stacking order. |
| ActivateDesk(desk_1); |
| auto desk_1_children = desk_1->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(3u, desk_1_children.size()); |
| EXPECT_TRUE(IsStackedBelow(win0.get(), win2.get())); |
| EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get())); |
| |
| // Assign all the other windows and rearrange the order by activating the |
| // windows. |
| widget0->SetVisibleOnAllWorkspaces(true); |
| widget2->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(win0->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| wm::ActivateWindow(win2.get()); |
| wm::ActivateWindow(win1.get()); |
| wm::ActivateWindow(win0.get()); |
| EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get())); |
| EXPECT_TRUE(IsStackedBelow(win1.get(), win0.get())); |
| EXPECT_EQ(3u, controller->visible_on_all_desks_windows().size()); |
| |
| // Move to desk 2. All the windows should move to the new desk and maintain |
| // their order. |
| ActivateDesk(desk_2); |
| desk_2_children = desk_2->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(3u, desk_2_children.size()); |
| EXPECT_TRUE(IsStackedBelow(win2.get(), win1.get())); |
| EXPECT_TRUE(IsStackedBelow(win1.get(), win0.get())); |
| } |
| |
| // Tests the behavior of windows that are visible on all desks when the active |
| // desk is removed. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksActiveDeskRemoval) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| |
| auto win0 = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto win1 = CreateAppWindow(gfx::Rect(1, 1, 150, 150)); |
| auto* widget0 = views::Widget::GetWidgetForNativeWindow(win0.get()); |
| auto* widget1 = views::Widget::GetWidgetForNativeWindow(win1.get()); |
| |
| // Assign |win0| and |win1| to all desks. |
| widget0->SetVisibleOnAllWorkspaces(true); |
| widget1->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(win0->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| ASSERT_TRUE(win1->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| |
| // Remove the active desk. The visible on all desks windows should be on |
| // |desk_2|. |
| RemoveDesk(desk_1); |
| auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(2u, desk_2_children.size()); |
| EXPECT_TRUE(IsStackedBelow(win0.get(), win1.get())); |
| EXPECT_EQ(2u, controller->visible_on_all_desks_windows().size()); |
| } |
| |
| // Tests the behavior of a minimized window that is visible on all desks. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksMinimizedWindow) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| |
| // Minimize |window| and then assign it to all desks. This shouldn't |
| // unminimize it. |
| auto* window_state = WindowState::Get(window.get()); |
| window_state->Minimize(); |
| ASSERT_TRUE(window_state->IsMinimized()); |
| widget->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| |
| // Switch desks. |window| should be on the newly active desk and should still |
| // be minimized. |
| ActivateDesk(desk_2); |
| auto desk_2_children = desk_2->GetDeskContainerForRoot(root)->children(); |
| EXPECT_EQ(1u, desk_2_children.size()); |
| EXPECT_EQ(window.get(), desk_2_children[0]); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| } |
| |
| // Tests the behavior of a window that is visible on all desks when a user tries |
| // to move it to another desk using drag and drop (overview mode). |
| TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaDragAndDrop) { |
| auto* controller = DesksController::Get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| NewDesk(); |
| const Desk* desk_1 = controller->desks()[0].get(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| |
| // Assign |window| to all desks. |
| widget->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| |
| // Try to move |window| to |desk_2| via drag and drop. It should not be moved. |
| EXPECT_FALSE(controller->MoveWindowFromActiveDeskTo( |
| window.get(), const_cast<Desk*>(desk_2), root, |
| DesksMoveWindowFromActiveDeskSource::kDragAndDrop)); |
| EXPECT_TRUE(desks_util::BelongsToActiveDesk(window.get())); |
| EXPECT_EQ(1u, controller->visible_on_all_desks_windows().size()); |
| EXPECT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_TRUE(base::Contains(desk_1->windows(), window.get())); |
| } |
| |
| // Tests the behavior of a window that is visible on all desks when a user tries |
| // to move it to another desk using keyboard shorcuts. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaShortcuts) { |
| auto* controller = DesksController::Get(); |
| auto* root = Shell::GetPrimaryRootWindow(); |
| NewDesk(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| |
| // Assign |window| to all desks. |
| widget->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| |
| // Move |window| to |desk_2| via keyboard shortcut. It should be on |desk_2| |
| // and should no longer be visible on all desks. |
| EXPECT_TRUE(controller->MoveWindowFromActiveDeskTo( |
| window.get(), const_cast<Desk*>(desk_2), root, |
| DesksMoveWindowFromActiveDeskSource::kShortcut)); |
| EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get())); |
| EXPECT_EQ(0u, controller->visible_on_all_desks_windows().size()); |
| EXPECT_FALSE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| } |
| |
| // Tests the behavior of a window that is visible on all desks when a user tries |
| // to move it using the context menu. |
| TEST_F(DesksBentoTest, VisibleOnAllDesksMoveWindowToDeskViaContextMenu) { |
| auto* controller = DesksController::Get(); |
| NewDesk(); |
| const Desk* desk_2 = controller->desks()[1].get(); |
| |
| auto window = CreateAppWindow(gfx::Rect(0, 0, 100, 100)); |
| auto* widget = views::Widget::GetWidgetForNativeWindow(window.get()); |
| |
| // Assign |window| to all desks. |
| widget->SetVisibleOnAllWorkspaces(true); |
| ASSERT_TRUE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| |
| // Move |window| to |desk_2| via keyboard shortcut. It should be on |desk_2| |
| // and should no longer be visible on all desks. |
| controller->SendToDeskAtIndex(window.get(), controller->GetDeskIndex(desk_2)); |
| EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get())); |
| EXPECT_EQ(0u, controller->visible_on_all_desks_windows().size()); |
| EXPECT_FALSE(window->GetProperty(aura::client::kVisibleOnAllWorkspacesKey)); |
| EXPECT_TRUE(base::Contains(desk_2->windows(), window.get())); |
| } |
| |
| // TODO(afakhry): Add more tests: |
| // - Always on top windows are not tracked by any desk. |
| // - Reusing containers when desks are removed and created. |
| |
| // Instantiate the parametrized tests. |
| INSTANTIATE_TEST_SUITE_P(All, DesksTest, ::testing::Bool()); |
| INSTANTIATE_TEST_SUITE_P(All, PerDeskShelfTest, ::testing::Bool()); |
| INSTANTIATE_TEST_SUITE_P(All, DesksMultiUserTest, ::testing::Bool()); |
| INSTANTIATE_TEST_SUITE_P(All, DesksRestoreMultiUserTest, ::testing::Bool()); |
| |
| } // namespace |
| |
| } // namespace ash |