| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/wm/overview/overview_session.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "ash/accelerators/accelerator_controller_impl.h" |
| #include "ash/accelerators/exit_warning_handler.h" |
| #include "ash/accessibility/accessibility_controller_impl.h" |
| #include "ash/accessibility/magnifier/docked_magnifier_controller.h" |
| #include "ash/accessibility/test_accessibility_controller_client.h" |
| #include "ash/app_list/app_list_controller_impl.h" |
| #include "ash/constants/app_types.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/display/screen_orientation_controller.h" |
| #include "ash/display/screen_orientation_controller_test_api.h" |
| #include "ash/drag_drop/drag_drop_controller.h" |
| #include "ash/frame_throttler/frame_throttling_controller.h" |
| #include "ash/frame_throttler/mock_frame_throttling_observer.h" |
| #include "ash/public/cpp/shelf_config.h" |
| #include "ash/public/cpp/test/shell_test_api.h" |
| #include "ash/public/cpp/window_properties.h" |
| #include "ash/screen_util.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shelf/shelf_view_test_api.h" |
| #include "ash/shell.h" |
| #include "ash/style/close_button.h" |
| #include "ash/test/ash_test_base.h" |
| #include "ash/test/test_window_builder.h" |
| #include "ash/wm/desks/desks_bar_view.h" |
| #include "ash/wm/desks/desks_test_util.h" |
| #include "ash/wm/desks/desks_util.h" |
| #include "ash/wm/desks/templates/saved_desk_save_desk_button.h" |
| #include "ash/wm/desks/templates/saved_desk_util.h" |
| #include "ash/wm/drag_window_resizer.h" |
| #include "ash/wm/gestures/back_gesture/back_gesture_event_handler.h" |
| #include "ash/wm/mru_window_tracker.h" |
| #include "ash/wm/overview/overview_constants.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_grid.h" |
| #include "ash/wm/overview/overview_grid_event_handler.h" |
| #include "ash/wm/overview/overview_highlight_controller.h" |
| #include "ash/wm/overview/overview_item.h" |
| #include "ash/wm/overview/overview_item_view.h" |
| #include "ash/wm/overview/overview_test_base.h" |
| #include "ash/wm/overview/overview_test_util.h" |
| #include "ash/wm/overview/overview_utils.h" |
| #include "ash/wm/overview/overview_wallpaper_controller.h" |
| #include "ash/wm/overview/overview_window_drag_controller.h" |
| #include "ash/wm/overview/rounded_label_widget.h" |
| #include "ash/wm/overview/scoped_overview_transform_window.h" |
| #include "ash/wm/resize_shadow.h" |
| #include "ash/wm/resize_shadow_controller.h" |
| #include "ash/wm/splitview/split_view_controller.h" |
| #include "ash/wm/splitview/split_view_divider.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_browser_window_drag_delegate.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller.h" |
| #include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h" |
| #include "ash/wm/tablet_mode/tablet_mode_window_resizer.h" |
| #include "ash/wm/window_preview_view.h" |
| #include "ash/wm/window_state.h" |
| #include "ash/wm/window_state_delegate.h" |
| #include "ash/wm/window_util.h" |
| #include "ash/wm/wm_event.h" |
| #include "ash/wm/workspace/workspace_window_resizer.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/cxx20_erase.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/user_action_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "chromeos/ui/wm/features.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/aura/client/window_types.h" |
| #include "ui/aura/test/test_window_delegate.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_element.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/test/draw_waiter_for_test.h" |
| #include "ui/compositor/test/layer_animator_test_controller.h" |
| #include "ui/compositor/test/test_utils.h" |
| #include "ui/display/display_layout.h" |
| #include "ui/display/manager/display_manager.h" |
| #include "ui/display/test/display_manager_test_api.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/gesture_detection/gesture_configuration.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/gfx/geometry/transform_util.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| #include "ui/wm/core/cursor_manager.h" |
| #include "ui/wm/core/shadow_controller.h" |
| #include "ui/wm/core/window_util.h" |
| |
| namespace ash { |
| namespace { |
| |
| using ::chromeos::WindowStateType; |
| |
| constexpr const char kActiveWindowChangedFromOverview[] = |
| "WindowSelector_ActiveWindowChanged"; |
| |
| // Helper function to get the index of |child|, given its parent window |
| // |parent|. |
| int IndexOf(aura::Window* child, aura::Window* parent) { |
| aura::Window::Windows children = parent->children(); |
| auto it = std::find(children.begin(), children.end(), child); |
| DCHECK(it != children.end()); |
| |
| return static_cast<int>(std::distance(children.begin(), it)); |
| } |
| |
| class TweenTester : public ui::LayerAnimationObserver { |
| public: |
| explicit TweenTester(aura::Window* window) : window_(window) { |
| window->layer()->GetAnimator()->AddObserver(this); |
| } |
| |
| TweenTester(const TweenTester&) = delete; |
| TweenTester& operator=(const TweenTester&) = delete; |
| |
| ~TweenTester() override { |
| window_->layer()->GetAnimator()->RemoveObserver(this); |
| EXPECT_TRUE(will_animate_); |
| } |
| |
| // ui::LayerAnimationObserver: |
| void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {} |
| void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {} |
| void OnLayerAnimationScheduled( |
| ui::LayerAnimationSequence* sequence) override {} |
| void OnAttachedToSequence(ui::LayerAnimationSequence* sequence) override { |
| ui::LayerAnimationObserver::OnAttachedToSequence(sequence); |
| if (!will_animate_) { |
| tween_type_ = sequence->FirstElement()->tween_type(); |
| will_animate_ = true; |
| } |
| } |
| |
| gfx::Tween::Type tween_type() const { return tween_type_; } |
| |
| private: |
| gfx::Tween::Type tween_type_ = gfx::Tween::LINEAR; |
| aura::Window* window_; |
| bool will_animate_ = false; |
| }; |
| |
| // Class which tracks if a given widget has been destroyed. |
| class TestDestroyedWidgetObserver : public views::WidgetObserver { |
| public: |
| explicit TestDestroyedWidgetObserver(views::Widget* widget) { |
| DCHECK(widget); |
| observation_.Observe(widget); |
| } |
| TestDestroyedWidgetObserver(const TestDestroyedWidgetObserver&) = delete; |
| TestDestroyedWidgetObserver& operator=(const TestDestroyedWidgetObserver&) = |
| delete; |
| ~TestDestroyedWidgetObserver() override = default; |
| |
| // views::WidgetObserver: |
| void OnWidgetDestroyed(views::Widget* widget) override { |
| DCHECK(!widget_destroyed_); |
| widget_destroyed_ = true; |
| observation_.Reset(); |
| } |
| |
| bool widget_destroyed() const { return widget_destroyed_; } |
| |
| private: |
| bool widget_destroyed_ = false; |
| |
| base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{ |
| this}; |
| }; |
| |
| } // namespace |
| |
| class OverviewSessionTest : public OverviewTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| OverviewSessionTest() = default; |
| OverviewSessionTest(const OverviewSessionTest&) = delete; |
| OverviewSessionTest& operator=(const OverviewSessionTest&) = delete; |
| ~OverviewSessionTest() override = default; |
| |
| // Used for tests regarding the exit warning popup. |
| void StubForTest(ExitWarningHandler* ewh) { |
| ewh->stub_timer_for_test_ = true; |
| } |
| bool IsUIShown(ExitWarningHandler* ewh) { return !!ewh->widget_; } |
| |
| // OverviewTestBase: |
| void SetUp() override { |
| scoped_feature_list_.InitWithFeatureState(features::kDesksTemplates, |
| GetParam()); |
| |
| OverviewTestBase::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Tests that close buttons on windows in overview do not work |
| // when one window is being dragged. |
| TEST_P(OverviewSessionTest, CloseButtonDisabledOnDrag) { |
| std::unique_ptr<views::Widget> widget1(CreateTestWidget()); |
| std::unique_ptr<views::Widget> widget2(CreateTestWidget()); |
| |
| aura::Window* window1 = widget1->GetNativeWindow(); |
| aura::Window* window2 = widget2->GetNativeWindow(); |
| |
| ToggleOverview(); |
| |
| ASSERT_FALSE(widget1->IsClosed()); |
| ASSERT_FALSE(widget2->IsClosed()); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2); |
| |
| // Get location of close button on `window1` before drag. |
| const gfx::Point item1_close_button_position = |
| GetCloseButton(item1)->GetBoundsInScreen().CenterPoint(); |
| |
| // Drag `window1` in overview to trigger drag animations. |
| GetEventGenerator()->PressTouchId( |
| /*touch_id=*/0, item1->GetBoundsOfSelectedItem().CenterPoint()); |
| GetEventGenerator()->MoveTouchIdBy(/*touch_id=*/0, -100, 0); |
| |
| // Make sure the drag event triggered the fade animations. |
| EXPECT_EQ(0.f, GetTitlebarOpacity(item1)); |
| EXPECT_EQ(1.f, GetCloseButtonOpacity(item1)); |
| EXPECT_EQ(1.f, GetTitlebarOpacity(item2)); |
| EXPECT_EQ(0.f, GetCloseButtonOpacity(item2)); |
| |
| // Both close buttons should be disabled at this point. |
| EXPECT_FALSE(GetCloseButton(item1)->GetEnabled()); |
| EXPECT_FALSE(GetCloseButton(item2)->GetEnabled()); |
| |
| // Try to close `window2` and `window1`. |
| GetEventGenerator()->GestureTapAt( |
| GetCloseButton(item2)->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->GestureTapAt(item1_close_button_position); |
| GetEventGenerator()->GestureTapAt( |
| GetCloseButton(item1)->GetBoundsInScreen().CenterPoint()); |
| |
| // Check that both windows are still open. |
| ASSERT_FALSE(widget1->IsClosed()); |
| ASSERT_FALSE(widget2->IsClosed()); |
| |
| // Release touch 0 to exit drag. |
| GetEventGenerator()->ReleaseTouchId(0); |
| |
| // We should still be in an overview session. |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // The windows should now be closable. |
| GetEventGenerator()->GestureTapAt( |
| GetCloseButton(item2)->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->GestureTapAt( |
| GetCloseButton(item1)->GetBoundsInScreen().CenterPoint()); |
| |
| EXPECT_TRUE(widget1->IsClosed()); |
| EXPECT_TRUE(widget2->IsClosed()); |
| } |
| |
| // Tests that close buttons on windows in overview are re-enabled |
| // when one window is snapped to a side of the screen. |
| TEST_P(OverviewSessionTest, CloseButtonEnabledOnSnap) { |
| std::unique_ptr<views::Widget> widget2 = CreateTestWidget(); |
| |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| aura::Window* window2 = widget2->GetNativeWindow(); |
| |
| ToggleOverview(); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2); |
| |
| ASSERT_FALSE(widget2->IsClosed()); |
| |
| ASSERT_TRUE(GetSplitViewController()->CanSnapWindow(window1.get())); |
| |
| // Snap `window1` to the left side of the screen while in |
| // overview. |
| GetEventGenerator()->PressTouchId( |
| /*touch_id=*/0, item1->GetBoundsOfSelectedItem().CenterPoint()); |
| |
| GetEventGenerator()->MoveTouchId(gfx::Point(0, 0), /*touch_id=*/0); |
| |
| EXPECT_FALSE(GetCloseButton(item1)->GetEnabled()); |
| EXPECT_FALSE(GetCloseButton(item2)->GetEnabled()); |
| |
| GetEventGenerator()->ReleaseTouchId(0); |
| |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| GetSplitViewController()->state()); |
| |
| // The close button for `window2` should be enabled. |
| EXPECT_TRUE(GetCloseButton(item2)->GetEnabled()); |
| |
| // Try to close `window2`. |
| GetEventGenerator()->GestureTapAt( |
| GetCloseButton(item2)->GetBoundsInScreen().CenterPoint()); |
| |
| // Check that `window2` is closed. |
| EXPECT_TRUE(widget2->IsClosed()); |
| } |
| |
| // Tests that an a11y alert is sent on entering overview mode. |
| TEST_P(OverviewSessionTest, A11yAlertOnOverviewMode) { |
| TestAccessibilityControllerClient client; |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| EXPECT_NE(AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED, |
| client.last_a11y_alert()); |
| ToggleOverview(); |
| EXPECT_EQ(AccessibilityAlert::WINDOW_OVERVIEW_MODE_ENTERED, |
| client.last_a11y_alert()); |
| } |
| |
| // Tests that there are no crashes when there is not enough screen space |
| // available to show all of the windows. |
| TEST_P(OverviewSessionTest, SmallDisplay) { |
| UpdateDisplay("3x1"); |
| gfx::Rect bounds(0, 0, 1, 1); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds)); |
| window1->SetProperty(aura::client::kTopViewInset, 0); |
| window2->SetProperty(aura::client::kTopViewInset, 0); |
| window3->SetProperty(aura::client::kTopViewInset, 0); |
| window4->SetProperty(aura::client::kTopViewInset, 0); |
| ToggleOverview(); |
| } |
| |
| // Tests entering overview mode with two windows and selecting one by clicking. |
| TEST_P(OverviewSessionTest, Basic) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Overview disabled by default. |
| EXPECT_FALSE(InOverviewSession()); |
| |
| aura::Window* root_window = Shell::GetPrimaryRootWindow(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| EXPECT_TRUE(WindowsOverlapping(window1.get(), window2.get())); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_FALSE(wm::IsActiveWindow(window1.get())); |
| EXPECT_TRUE(wm::IsActiveWindow(window2.get())); |
| EXPECT_EQ(window2.get(), window_util::GetFocusedWindow()); |
| |
| // Hide the cursor before entering overview to test that it will be shown. |
| aura::client::GetCursorClient(root_window)->HideCursor(); |
| |
| CheckOverviewEnterExitHistogram("Init", {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}); |
| // In overview mode the windows should no longer overlap and the overview |
| // focus window should be focused. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(), |
| window_util::GetFocusedWindow()); |
| EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); |
| CheckOverviewEnterExitHistogram("Enter", {1, 0, 0, 0, 0}, {0, 0, 0, 0, 0}); |
| |
| // Clicking window 1 should activate it. |
| ClickWindow(window1.get()); |
| WaitForOverviewExitAnimation(); |
| |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| EXPECT_FALSE(wm::IsActiveWindow(window2.get())); |
| EXPECT_EQ(window1.get(), window_util::GetFocusedWindow()); |
| |
| // Cursor should have been unlocked. |
| EXPECT_FALSE(aura::client::GetCursorClient(root_window)->IsCursorLocked()); |
| |
| CheckOverviewEnterExitHistogram("Exit", {1, 0, 0, 0, 0}, {1, 0, 0, 0, 0}); |
| } |
| |
| // Tests activating minimized window. |
| TEST_P(OverviewSessionTest, ActivateMinimized) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| WindowState* window_state = WindowState::Get(window.get()); |
| WMEvent minimize_event(WM_EVENT_MINIMIZE); |
| window_state->OnWMEvent(&minimize_event); |
| EXPECT_FALSE(window->IsVisible()); |
| EXPECT_EQ(0.f, window->layer()->GetTargetOpacity()); |
| EXPECT_EQ(WindowStateType::kMinimized, |
| WindowState::Get(window.get())->GetStateType()); |
| |
| ToggleOverview(); |
| |
| EXPECT_FALSE(window->IsVisible()); |
| EXPECT_EQ(0.f, window->layer()->GetTargetOpacity()); |
| EXPECT_EQ(WindowStateType::kMinimized, window_state->GetStateType()); |
| WindowPreviewView* preview_view = |
| GetPreviewView(GetOverviewItemForWindow(window.get())); |
| EXPECT_TRUE(preview_view); |
| |
| const gfx::Point point = preview_view->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->set_current_screen_location(point); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| EXPECT_FALSE(InOverviewSession()); |
| |
| EXPECT_TRUE(window->IsVisible()); |
| EXPECT_EQ(1.f, window->layer()->GetTargetOpacity()); |
| EXPECT_EQ(WindowStateType::kNormal, window_state->GetStateType()); |
| } |
| |
| // A window can be minimized when losing a focus upon entering overview. |
| // If such window was active, it will be unminimized when exiting overview. |
| // b/163551595. |
| TEST_P(OverviewSessionTest, MinimizeDuringOverview) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| ToggleOverview(); |
| WindowState* window_state = WindowState::Get(window.get()); |
| WMEvent minimize_event(WM_EVENT_MINIMIZE); |
| window_state->OnWMEvent(&minimize_event); |
| EXPECT_FALSE(window->IsVisible()); |
| EXPECT_EQ(WindowStateType::kMinimized, |
| WindowState::Get(window.get())->GetStateType()); |
| ToggleOverview(); |
| } |
| |
| // Tests that the ordering of windows is stable across different overview |
| // sessions even when the windows have the same bounds. |
| TEST_P(OverviewSessionTest, WindowsOrder) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindowInShellWithId(1)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindowInShellWithId(2)); |
| std::unique_ptr<aura::Window> window3(CreateTestWindowInShellWithId(3)); |
| |
| // The order of windows in overview mode is MRU. |
| WindowState::Get(window1.get())->Activate(); |
| ToggleOverview(); |
| const std::vector<std::unique_ptr<OverviewItem>>& overview1 = |
| GetOverviewItemsForRoot(0); |
| EXPECT_EQ(1, overview1[0]->GetWindow()->GetId()); |
| EXPECT_EQ(3, overview1[1]->GetWindow()->GetId()); |
| EXPECT_EQ(2, overview1[2]->GetWindow()->GetId()); |
| ToggleOverview(); |
| |
| // Activate the second window. |
| WindowState::Get(window2.get())->Activate(); |
| ToggleOverview(); |
| const std::vector<std::unique_ptr<OverviewItem>>& overview2 = |
| GetOverviewItemsForRoot(0); |
| |
| // The order should be MRU. |
| EXPECT_EQ(2, overview2[0]->GetWindow()->GetId()); |
| EXPECT_EQ(1, overview2[1]->GetWindow()->GetId()); |
| EXPECT_EQ(3, overview2[2]->GetWindow()->GetId()); |
| ToggleOverview(); |
| } |
| |
| // Tests selecting a window by tapping on it. |
| TEST_P(OverviewSessionTest, BasicGesture) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window1.get()); |
| EXPECT_EQ(window1.get(), window_util::GetFocusedWindow()); |
| ToggleOverview(); |
| EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(), |
| window_util::GetFocusedWindow()); |
| GetEventGenerator()->GestureTapAt( |
| GetTransformedTargetBounds(window2.get()).CenterPoint()); |
| EXPECT_EQ(window2.get(), window_util::GetFocusedWindow()); |
| } |
| |
| // Tests that the user action WindowSelector_ActiveWindowChanged is |
| // recorded when the mouse/touchscreen/keyboard are used to select a window |
| // in overview mode which is different from the previously-active window. |
| TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionRecorded) { |
| base::UserActionTester user_action_tester; |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window1.get()); |
| ToggleOverview(); |
| |
| // Tap on |window2| to activate it and exit overview. |
| GetEventGenerator()->GestureTapAt( |
| GetTransformedTargetBounds(window2.get()).CenterPoint()); |
| EXPECT_EQ( |
| 1, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // Click on |window2| to activate it and exit overview. |
| wm::ActivateWindow(window1.get()); |
| ToggleOverview(); |
| ClickWindow(window2.get()); |
| EXPECT_EQ( |
| 2, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // Highlight |window2| using the arrow keys. Activate it (and exit overview) |
| // by pressing the return key. |
| wm::ActivateWindow(window1.get()); |
| ToggleOverview(); |
| ASSERT_TRUE(HighlightOverviewWindow(window2.get())); |
| SendKey(ui::VKEY_RETURN); |
| EXPECT_EQ( |
| 3, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| } |
| |
| // Tests that the user action WindowSelector_ActiveWindowChanged is not |
| // recorded when the mouse/touchscreen/keyboard are used to select the |
| // already-active window from overview mode. Also verifies that entering and |
| // exiting overview without selecting a window does not record the action. |
| TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionNotRecorded) { |
| base::UserActionTester user_action_tester; |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window1.get()); |
| ToggleOverview(); |
| |
| // Tap on |window1| to exit overview. |
| GetEventGenerator()->GestureTapAt( |
| GetTransformedTargetBounds(window1.get()).CenterPoint()); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // |window1| remains active. Click on it to exit overview. |
| ASSERT_EQ(window1.get(), window_util::GetFocusedWindow()); |
| ToggleOverview(); |
| ClickWindow(window1.get()); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // |window1| remains active. Select using the keyboard. |
| ASSERT_EQ(window1.get(), window_util::GetFocusedWindow()); |
| ToggleOverview(); |
| ASSERT_TRUE(HighlightOverviewWindow(window1.get())); |
| SendKey(ui::VKEY_RETURN); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // Entering and exiting overview without user input should not record |
| // the action. |
| ToggleOverview(); |
| ToggleOverview(); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| } |
| |
| // Tests that the user action WindowSelector_ActiveWindowChanged is not |
| // recorded when overview mode exits as a result of closing its only window. |
| TEST_P(OverviewSessionTest, ActiveWindowChangedUserActionWindowClose) { |
| base::UserActionTester user_action_tester; |
| std::unique_ptr<views::Widget> widget(CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(400, 400))); |
| |
| ToggleOverview(); |
| aura::Window* window = widget->GetNativeWindow(); |
| const gfx::Point point = GetCloseButton(GetOverviewItemForWindow(window)) |
| ->GetBoundsInScreen() |
| .CenterPoint(); |
| ASSERT_FALSE(widget->IsClosed()); |
| GetEventGenerator()->set_current_screen_location(point); |
| GetEventGenerator()->ClickLeftButton(); |
| ASSERT_TRUE(widget->IsClosed()); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| } |
| |
| // Tests that we do not crash and overview mode remains engaged if the desktop |
| // is tapped while a finger is already down over a window. |
| TEST_P(OverviewSessionTest, NoCrashWithDesktopTap) { |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindow(gfx::Rect(200, 300, 250, 450))); |
| |
| ToggleOverview(); |
| |
| const gfx::Rect bounds = GetTransformedBoundsInRootWindow(window.get()); |
| GetEventGenerator()->set_current_screen_location(bounds.CenterPoint()); |
| |
| // Press down on the window. |
| const int kTouchId = 19; |
| GetEventGenerator()->PressTouchId(kTouchId); |
| |
| // Tap on the desktop, which should not cause a crash. Overview mode should |
| // remain engaged. |
| GetEventGenerator()->GestureTapAt(GetGridBounds().CenterPoint()); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| GetEventGenerator()->ReleaseTouchId(kTouchId); |
| } |
| |
| // Tests that we do not crash and a window is selected when appropriate when |
| // we click on a window during touch. |
| TEST_P(OverviewSessionTest, ClickOnWindowDuringTouch) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_FALSE(wm::IsActiveWindow(window1.get())); |
| EXPECT_TRUE(wm::IsActiveWindow(window2.get())); |
| |
| ToggleOverview(); |
| |
| gfx::Rect window1_bounds = GetTransformedBoundsInRootWindow(window1.get()); |
| GetEventGenerator()->set_current_screen_location( |
| window1_bounds.CenterPoint()); |
| |
| // Clicking on |window2| while touching on |window1| should not cause a |
| // crash, it should do nothing since overview only handles one click or touch |
| // at a time. |
| const int kTouchId = 19; |
| GetEventGenerator()->PressTouchId(kTouchId); |
| GetEventGenerator()->MoveMouseToCenterOf(window2.get()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_FALSE(wm::IsActiveWindow(window2.get())); |
| |
| // Clicking on |window1| while touching on |window1| should not cause |
| // a crash, overview mode should be disengaged, and |window1| should |
| // be active. |
| GetEventGenerator()->MoveMouseToCenterOf(window1.get()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| GetEventGenerator()->ReleaseTouchId(kTouchId); |
| } |
| |
| // Tests that a window does not receive located events when in overview mode. |
| TEST_P(OverviewSessionTest, WindowDoesNotReceiveEvents) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400))); |
| const gfx::Point point1 = window->bounds().CenterPoint(); |
| ui::MouseEvent event1(ui::ET_MOUSE_PRESSED, point1, point1, |
| ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| |
| aura::Window* root_window = Shell::GetPrimaryRootWindow(); |
| ui::EventTarget* root_target = root_window; |
| ui::EventTargeter* targeter = |
| root_window->GetHost()->dispatcher()->GetDefaultEventTargeter(); |
| |
| // The event should target the window because we are still not in overview |
| // mode. |
| EXPECT_EQ(window.get(), targeter->FindTargetForEvent(root_target, &event1)); |
| |
| ToggleOverview(); |
| |
| // The bounds have changed, take that into account. |
| const gfx::Point point2 = |
| GetTransformedBoundsInRootWindow(window.get()).CenterPoint(); |
| ui::MouseEvent event2(ui::ET_MOUSE_PRESSED, point2, point2, |
| ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); |
| |
| // Now the transparent window should be intercepting this event. |
| EXPECT_NE(window.get(), targeter->FindTargetForEvent(root_target, &event2)); |
| } |
| |
| // Tests that clicking on the close button effectively closes the window. |
| TEST_P(OverviewSessionTest, CloseButton) { |
| std::unique_ptr<views::Widget> widget(CreateTestWidget()); |
| std::unique_ptr<views::Widget> minimized_widget(CreateTestWidget()); |
| minimized_widget->Minimize(); |
| |
| ToggleOverview(); |
| aura::Window* window = widget->GetNativeWindow(); |
| const gfx::Point point = GetCloseButton(GetOverviewItemForWindow(window)) |
| ->GetBoundsInScreen() |
| .CenterPoint(); |
| GetEventGenerator()->set_current_screen_location(point); |
| |
| EXPECT_FALSE(widget->IsClosed()); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(widget->IsClosed()); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| aura::Window* minimized_window = minimized_widget->GetNativeWindow(); |
| WindowPreviewView* preview_view = |
| GetPreviewView(GetOverviewItemForWindow(minimized_window)); |
| EXPECT_TRUE(preview_view); |
| const gfx::Point point2 = |
| GetCloseButton(GetOverviewItemForWindow(minimized_window)) |
| ->GetBoundsInScreen() |
| .CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(point2); |
| EXPECT_FALSE(minimized_widget->IsClosed()); |
| |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_TRUE(minimized_widget->IsClosed()); |
| |
| // All minimized windows are closed, so it should exit overview mode. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that the shadow disappears before the close animation starts. |
| // Regression test for https://crbug.com/981509. |
| TEST_P(OverviewSessionTest, CloseAnimationShadow) { |
| // Give us some time to check if the shadow has disappeared. |
| ScopedOverviewTransformWindow::SetImmediateCloseForTests(/*immediate=*/false); |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| std::unique_ptr<views::Widget> widget = CreateTestWidget(); |
| |
| ToggleOverview(); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kEnterAnimationComplete); |
| // Click the close button. |
| OverviewItem* item = GetOverviewItemForWindow(widget->GetNativeWindow()); |
| const gfx::Point point = |
| GetCloseButton(item)->GetBoundsInScreen().CenterPoint(); |
| GetEventGenerator()->set_current_screen_location(point); |
| GetEventGenerator()->ClickLeftButton(); |
| ASSERT_FALSE(widget->IsClosed()); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // The shadow bounds are empty, which means its not visible. |
| EXPECT_EQ(gfx::Rect(), item->GetShadowBoundsForTesting()); |
| } |
| |
| // Tests minimizing/unminimizing in overview mode. |
| TEST_P(OverviewSessionTest, MinimizeUnminimize) { |
| std::unique_ptr<views::Widget> widget(CreateTestWidget()); |
| aura::Window* window = widget->GetNativeWindow(); |
| |
| ToggleOverview(); |
| EXPECT_FALSE(GetPreviewView(GetOverviewItemForWindow(window))); |
| |
| widget->Minimize(); |
| EXPECT_TRUE(widget->IsMinimized()); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetPreviewView(GetOverviewItemForWindow(window))); |
| |
| widget->Restore(); |
| EXPECT_FALSE(widget->IsMinimized()); |
| EXPECT_FALSE(GetPreviewView(GetOverviewItemForWindow(window))); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests that clicking on the close button on a secondary display effectively |
| // closes the window. |
| TEST_P(OverviewSessionTest, CloseButtonOnMultipleDisplay) { |
| UpdateDisplay("600x400,600x400"); |
| |
| // We need a widget for the close button to work because windows are closed |
| // via the widget. We also use the widget to determine if the window has been |
| // closed or not. Parent the window to a window in a non-primary root window. |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindow(gfx::Rect(650, 300, 250, 450))); |
| std::unique_ptr<views::Widget> widget(CreateTestWidget()); |
| widget->SetBounds(gfx::Rect(650, 0, 400, 400)); |
| aura::Window* window2 = widget->GetNativeWindow(); |
| window2->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp); |
| views::Widget::ReparentNativeView(window2, window->parent()); |
| ASSERT_EQ(Shell::GetAllRootWindows()[1], window2->GetRootWindow()); |
| |
| ToggleOverview(); |
| gfx::Rect bounds = GetTransformedBoundsInRootWindow(window2); |
| gfx::Point point(bounds.right() - 5, bounds.y() + 5); |
| ui::test::EventGenerator event_generator(window2->GetRootWindow(), point); |
| |
| EXPECT_FALSE(widget->IsClosed()); |
| event_generator.ClickLeftButton(); |
| EXPECT_TRUE(widget->IsClosed()); |
| } |
| |
| // Tests entering overview mode with two windows and selecting one. |
| // TODO(crbug.com/1323145): Flaky. |
| TEST_P(OverviewSessionTest, DISABLED_FullscreenWindow) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window1.get()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window1.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen()); |
| |
| // Enter overview and select the fullscreen window. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("FullscreenWindowEnter1", {0, 1, 0, 0, 0}, |
| {0, 0, 0, 0, 0}); |
| ClickWindow(window1.get()); |
| WaitForOverviewExitAnimation(); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowExit1", {0, 1, 0, 0, 0}, |
| {0, 1, 0, 0, 0}); |
| |
| // Entering overview and selecting another window, the previous window remains |
| // fullscreen. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("FullscreenWindowEnter2", {0, 2, 0, 0, 0}, |
| {0, 1, 0, 0, 0}); |
| ClickWindow(window2.get()); |
| WaitForOverviewExitAnimation(); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsFullscreen()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowExit2", {0, 2, 0, 0, 0}, |
| {1, 1, 0, 0, 0}); |
| } |
| |
| // Tests entering overview mode with maximized window. |
| // TODO(crbug.com/1325386): Flaky. |
| TEST_P(OverviewSessionTest, DISABLED_MaximizedWindow) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window1.get()); |
| |
| const WMEvent maximize_event(WM_EVENT_MAXIMIZE); |
| WindowState::Get(window1.get())->OnWMEvent(&maximize_event); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized()); |
| |
| // Enter overview and select the fullscreen window. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("MaximizedWindowEnter1", {0, 1, 0, 0, 0}, |
| {0, 0, 0, 0, 0}); |
| ClickWindow(window1.get()); |
| WaitForOverviewExitAnimation(); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized()); |
| CheckOverviewEnterExitHistogram("MaximizedWindowExit1", {0, 1, 0, 0, 0}, |
| {0, 1, 0, 0, 0}); |
| |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("MaximizedWindowEnter2", {0, 2, 0, 0, 0}, |
| {0, 1, 0, 0, 0}); |
| ClickWindow(window2.get()); |
| WaitForOverviewExitAnimation(); |
| EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized()); |
| CheckOverviewEnterExitHistogram("MaximizedWindowExit2", {0, 2, 0, 0, 0}, |
| {1, 1, 0, 0, 0}); |
| } |
| |
| TEST_P(OverviewSessionTest, TabletModeHistograms) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| |
| // Enter overview with the window maximized. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("MaximizedWindowTabletEnter", {0, 0, 1, 0, 0}, |
| {0, 0, 0, 0, 0}); |
| |
| ToggleOverview(); |
| WaitForOverviewExitAnimation(); |
| CheckOverviewEnterExitHistogram("MaximizedWindowTabletExit", {0, 0, 1, 0, 0}, |
| {0, 0, 1, 0, 0}); |
| |
| WindowState::Get(window1.get())->Minimize(); |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("MinimizedWindowTabletEnter", {0, 0, 1, 1, 0}, |
| {0, 0, 1, 0, 0}); |
| |
| ToggleOverview(); |
| WaitForOverviewExitAnimation(); |
| CheckOverviewEnterExitHistogram("MinimizedWindowTabletExit", {0, 0, 1, 1, 0}, |
| {0, 0, 1, 1, 0}); |
| } |
| |
| // Tests that entering overview when a fullscreen window is active in maximized |
| // mode correctly applies the transformations to the window and correctly |
| // updates the window bounds on exiting overview mode: http://crbug.com/401664. |
| TEST_P(OverviewSessionTest, FullscreenWindowTabletMode) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| UpdateDisplay("800x600"); |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds)); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EnterTabletMode(); |
| gfx::Rect normal_window_bounds(window1->bounds()); |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window1.get())->OnWMEvent(&toggle_fullscreen_event); |
| |
| // Finish fullscreen state change animation since it is irrelevant. |
| window1->layer()->GetAnimator()->StopAnimating(); |
| |
| gfx::Rect fullscreen_window_bounds(window1->bounds()); |
| EXPECT_NE(normal_window_bounds, fullscreen_window_bounds); |
| EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds()); |
| |
| const gfx::Rect fullscreen(800, 600); |
| const int shelf_inset = 600 - ShelfConfig::Get()->shelf_size(); |
| const gfx::Rect normal_work_area(800, shelf_inset); |
| display::Screen* screen = display::Screen::GetScreen(); |
| EXPECT_EQ(gfx::Rect(800, 600), |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| EXPECT_EQ(fullscreen, |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter1", |
| {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0}); |
| |
| // Window 2 would normally resize to normal window bounds on showing the shelf |
| // for overview but this is deferred until overview is exited. |
| EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds()); |
| EXPECT_FALSE(WindowsOverlapping(window1.get(), window2.get())); |
| ToggleOverview(); |
| WaitForOverviewExitAnimation(); |
| EXPECT_EQ(fullscreen, |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| // Since the fullscreen window is still active, window2 will still have the |
| // larger bounds. |
| EXPECT_EQ(fullscreen_window_bounds, window2->GetTargetBounds()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit1", |
| {0, 0, 1, 0, 0}, {0, 0, 1, 0, 0}); |
| |
| // Enter overview again and select window 2. Selecting window 2 should show |
| // the shelf bringing window2 back to the normal bounds. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter2", |
| {0, 0, 2, 0, 0}, {0, 0, 1, 0, 0}); |
| |
| ClickWindow(window2.get()); |
| WaitForOverviewExitAnimation(); |
| // Selecting non fullscreen window should set the work area back to normal. |
| EXPECT_EQ(normal_work_area, |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| EXPECT_EQ(normal_window_bounds, window2->GetTargetBounds()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit2", |
| {0, 0, 2, 0, 0}, {0, 0, 2, 0, 0}); |
| |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletEnter3", |
| {0, 0, 3, 0, 0}, {0, 0, 2, 0, 0}); |
| EXPECT_EQ(normal_work_area, |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| ClickWindow(window1.get()); |
| WaitForOverviewExitAnimation(); |
| // Selecting fullscreen. The work area should be updated to fullscreen as |
| // well. |
| EXPECT_EQ(fullscreen, |
| screen->GetDisplayNearestWindow(window1.get()).work_area()); |
| CheckOverviewEnterExitHistogram("FullscreenWindowTabletExit3", |
| {0, 0, 3, 0, 0}, {0, 0, 3, 0, 0}); |
| } |
| |
| // Tests that when disabling ChromeVox, desks widget bounds on overview mode |
| // should be updated. Desks widget will be moved to the top of the screen. |
| TEST_P(OverviewSessionTest, DesksWidgetBoundsChangeWhenDisableChromeVox) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| |
| AccessibilityControllerImpl* accessibility_controller = |
| Shell::Get()->accessibility_controller(); |
| |
| // Enable ChromeVox. |
| const int kAccessibilityPanelHeight = 45; |
| // ChromeVox layout manager relies on the widget to validate ChromaVox panel's |
| // exist. Check AccessibilityPanelLayoutManager::SetPanelBounds. |
| std::unique_ptr<views::Widget> widget = |
| CreateTestWidget(nullptr, kShellWindowId_AccessibilityPanelContainer); |
| SetAccessibilityPanelHeight(kAccessibilityPanelHeight); |
| accessibility_controller->SetSpokenFeedbackEnabled(true, |
| A11Y_NOTIFICATION_NONE); |
| // Enable overview mode. |
| ToggleOverview(); |
| |
| const views::Widget* desks_widget = |
| GetOverviewSession()->grid_list()[0].get()->desks_widget(); |
| |
| const gfx::Rect desks_widget_bounds = desks_widget->GetWindowBoundsInScreen(); |
| // Desks widget should lay out right below ChromeVox panel. |
| EXPECT_EQ(desks_widget_bounds.y(), kAccessibilityPanelHeight); |
| |
| // Disable ChromeVox panel. |
| accessibility_controller->SetSpokenFeedbackEnabled(false, |
| A11Y_NOTIFICATION_NONE); |
| SetAccessibilityPanelHeight(0); |
| |
| const gfx::Rect desks_widget_bounds_after_disable_chromeVox = |
| desks_widget->GetWindowBoundsInScreen(); |
| // Desks widget should be moved to the top of the screen after |
| // disabling ChromeVox panel. |
| EXPECT_EQ(desks_widget_bounds_after_disable_chromeVox.y(), 0); |
| |
| EXPECT_NE(desks_widget_bounds, desks_widget_bounds_after_disable_chromeVox); |
| } |
| |
| TEST_P(OverviewSessionTest, SkipOverviewWindow) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| window2->SetProperty(kHideInOverviewKey, true); |
| |
| // Enter overview. |
| ToggleOverview(); |
| EXPECT_TRUE(window1->IsVisible()); |
| EXPECT_FALSE(window2->IsVisible()); |
| |
| // Exit overview. |
| ToggleOverview(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(window1->IsVisible()); |
| EXPECT_TRUE(window2->IsVisible()); |
| } |
| |
| // Tests that a minimized window's visibility and layer visibility |
| // stay invisible (A minimized window is cloned during overview). |
| TEST_P(OverviewSessionTest, MinimizedWindowState) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| WindowState::Get(window1.get())->Minimize(); |
| EXPECT_FALSE(window1->IsVisible()); |
| EXPECT_FALSE(window1->layer()->GetTargetVisibility()); |
| |
| ToggleOverview(); |
| EXPECT_FALSE(window1->IsVisible()); |
| EXPECT_FALSE(window1->layer()->GetTargetVisibility()); |
| |
| ToggleOverview(); |
| EXPECT_FALSE(window1->IsVisible()); |
| EXPECT_FALSE(window1->layer()->GetTargetVisibility()); |
| } |
| |
| // Tests that a bounds change during overview is corrected for. |
| TEST_P(OverviewSessionTest, BoundsChangeDuringOverview) { |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 400))); |
| // Use overview headers above the window in this test. |
| window->SetProperty(aura::client::kTopViewInset, 0); |
| ToggleOverview(); |
| gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get()); |
| window->SetBounds(gfx::Rect(200, 0, 200, 200)); |
| gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get()); |
| EXPECT_EQ(overview_bounds, new_overview_bounds); |
| ToggleOverview(); |
| } |
| |
| // Tests that a change to the |kTopViewInset| window property during overview is |
| // corrected for. |
| TEST_P(OverviewSessionTest, TopViewInsetChangeDuringOverview) { |
| std::unique_ptr<aura::Window> window = CreateTestWindow(gfx::Rect(400, 400)); |
| window->SetProperty(aura::client::kTopViewInset, 32); |
| ToggleOverview(); |
| gfx::Rect overview_bounds = GetTransformedTargetBounds(window.get()); |
| window->SetProperty(aura::client::kTopViewInset, 0); |
| gfx::Rect new_overview_bounds = GetTransformedTargetBounds(window.get()); |
| EXPECT_NE(overview_bounds, new_overview_bounds); |
| ToggleOverview(); |
| } |
| |
| // Tests that a newly created window aborts overview. |
| TEST_P(OverviewSessionTest, NewWindowCancelsOverview) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // A window being created should exit overview mode. |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that a window activation exits overview mode. |
| TEST_P(OverviewSessionTest, ActivationCancelsOverview) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| window2->Focus(); |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // A window being activated should exit overview mode. |
| window1->Focus(); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // window1 should be focused after exiting even though window2 was focused on |
| // entering overview because we exited due to an activation. |
| EXPECT_EQ(window1.get(), window_util::GetFocusedWindow()); |
| } |
| |
| // Tests that if a window is dragged while overview is open, the activation |
| // of the dragged window does not cancel overview. |
| TEST_P(OverviewSessionTest, ActivateDraggedWindowNotCancelOverview) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| window1->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // Start drag on |window1|. |
| std::unique_ptr<WindowResizer> resizer(CreateWindowResizer( |
| window1.get(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_TOUCH)); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| resizer->Drag(gfx::PointF(400, 0), 0); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| wm::ActivateWindow(window1.get()); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| resizer->CompleteDrag(); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that activate a non-dragged window during window drag will not cancel |
| // overview mode. |
| TEST_P(OverviewSessionTest, ActivateAnotherWindowDuringDragNotCancelOverview) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| window1->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // Start drag on |window1|. |
| wm::ActivateWindow(window1.get()); |
| std::unique_ptr<WindowResizer> resizer(CreateWindowResizer( |
| window1.get(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_TOUCH)); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // Activate |window2| should not cancel overview mode. |
| wm::ActivateWindow(window2.get()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->is_dragged()); |
| EXPECT_TRUE(wm::IsActiveWindow(window2.get())); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests that if an overview item is dragged, the activation of the |
| // corresponding window does not cancel overview. |
| TEST_P(OverviewSessionTest, ActivateDraggedOverviewWindowNotCancelOverview) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| ToggleOverview(); |
| OverviewItem* item = GetOverviewItemForWindow(window.get()); |
| gfx::PointF drag_point = item->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(item, drag_point, |
| /*is_touch_dragging=*/false); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(item, drag_point); |
| wm::ActivateWindow(window.get()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests that if an overview item is dragged, the activation of the window |
| // corresponding to another overview item does not cancel overview. |
| TEST_P(OverviewSessionTest, |
| ActivateAnotherOverviewWindowDuringOverviewDragNotCancelOverview) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| gfx::PointF drag_point = item1->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(item1, drag_point, |
| /*is_touch_dragging=*/false); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(item1, drag_point); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests that if an overview item is dragged, the activation of a window |
| // excluded from overview does not cancel overview. |
| TEST_P(OverviewSessionTest, |
| ActivateWindowExcludedFromOverviewDuringOverviewDragNotCancelOverview) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2( |
| CreateTestWindow(gfx::Rect(), aura::client::WINDOW_TYPE_POPUP)); |
| EXPECT_TRUE(window_util::ShouldExcludeForOverview(window2.get())); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| gfx::PointF drag_point = item1->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(item1, drag_point, |
| /*is_touch_dragging=*/false); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(item1, drag_point); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests that exiting overview mode without selecting a window restores focus |
| // to the previously focused window. |
| TEST_P(OverviewSessionTest, CancelRestoresFocus) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| wm::ActivateWindow(window.get()); |
| EXPECT_EQ(window.get(), window_util::GetFocusedWindow()); |
| |
| // In overview mode, the overview focus window should be focused. |
| ToggleOverview(); |
| EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(), |
| window_util::GetFocusedWindow()); |
| |
| // If canceling overview mode, focus should be restored. |
| ToggleOverview(); |
| EXPECT_EQ(window.get(), window_util::GetFocusedWindow()); |
| } |
| |
| // Tests that overview mode is exited if the last remaining window is destroyed. |
| TEST_P(OverviewSessionTest, LastWindowDestroyed) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| ToggleOverview(); |
| |
| window1.reset(); |
| window2.reset(); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that entering overview mode restores a window to its original |
| // target location. |
| TEST_P(OverviewSessionTest, QuickReentryRestoresInitialTransform) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(400, 400))); |
| gfx::Rect initial_bounds = GetTransformedBounds(window.get()); |
| ToggleOverview(); |
| // Quickly exit and reenter overview mode. The window should still be |
| // animating when we reenter. We cannot short circuit animations for this but |
| // we also don't have to wait for them to complete. |
| { |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| ToggleOverview(); |
| ToggleOverview(); |
| } |
| EXPECT_NE(initial_bounds, GetTransformedTargetBounds(window.get())); |
| ToggleOverview(); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_EQ(initial_bounds, GetTransformedTargetBounds(window.get())); |
| } |
| |
| // Tests that windows with modal child windows are transformed with the modal |
| // child even though not activatable themselves. |
| TEST_P(OverviewSessionTest, ModalChild) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> child(CreateTestWindow(bounds)); |
| child->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); |
| ::wm::AddTransientChild(window.get(), child.get()); |
| EXPECT_EQ(window->parent(), child->parent()); |
| ToggleOverview(); |
| EXPECT_TRUE(window->IsVisible()); |
| EXPECT_TRUE(child->IsVisible()); |
| EXPECT_EQ(GetTransformedTargetBounds(child.get()), |
| GetTransformedTargetBounds(window.get())); |
| ToggleOverview(); |
| } |
| |
| // Tests that clicking a modal window's parent activates the modal window in |
| // overview. |
| TEST_P(OverviewSessionTest, ClickModalWindowParent) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(180, 180))); |
| std::unique_ptr<aura::Window> child( |
| CreateTestWindow(gfx::Rect(200, 0, 180, 180))); |
| child->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); |
| ::wm::AddTransientChild(window.get(), child.get()); |
| EXPECT_FALSE(WindowsOverlapping(window.get(), child.get())); |
| EXPECT_EQ(window->parent(), child->parent()); |
| ToggleOverview(); |
| // Given that their relative positions are preserved, the windows should still |
| // not overlap. |
| EXPECT_FALSE(WindowsOverlapping(window.get(), child.get())); |
| ClickWindow(window.get()); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // Clicking on window1 should activate child1. |
| EXPECT_TRUE(wm::IsActiveWindow(child.get())); |
| } |
| |
| // Tests that windows remain on the display they are currently on in overview |
| // mode, and that the close buttons are on matching displays. |
| TEST_P(OverviewSessionTest, MultipleDisplays) { |
| UpdateDisplay("600x400,600x400"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| gfx::Rect bounds1(0, 0, 400, 400); |
| gfx::Rect bounds2(650, 0, 400, 400); |
| |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds1)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds1)); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds2)); |
| std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds2)); |
| EXPECT_EQ(root_windows[0], window1->GetRootWindow()); |
| EXPECT_EQ(root_windows[0], window2->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window3->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window4->GetRootWindow()); |
| |
| // In overview mode, each window remains in the same root window. |
| ToggleOverview(); |
| EXPECT_EQ(root_windows[0], window1->GetRootWindow()); |
| EXPECT_EQ(root_windows[0], window2->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window3->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window4->GetRootWindow()); |
| |
| // Window indices are based on top-down order. The reverse of our creation. |
| CheckWindowAndCloseButtonInScreen(window1.get(), |
| GetOverviewItemForWindow(window1.get())); |
| CheckWindowAndCloseButtonInScreen(window2.get(), |
| GetOverviewItemForWindow(window2.get())); |
| CheckWindowAndCloseButtonInScreen(window3.get(), |
| GetOverviewItemForWindow(window3.get())); |
| CheckWindowAndCloseButtonInScreen(window4.get(), |
| GetOverviewItemForWindow(window4.get())); |
| } |
| |
| // Tests shutting down during overview. |
| TEST_P(OverviewSessionTest, Shutdown) { |
| // These windows will be deleted when the test exits and the Shell instance |
| // is shut down. |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| ToggleOverview(); |
| } |
| |
| // Tests adding a display during overview. |
| TEST_P(OverviewSessionTest, AddDisplay) { |
| UpdateDisplay("500x400"); |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| UpdateDisplay("500x400,500x400"); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests removing a display during overview. |
| TEST_P(OverviewSessionTest, RemoveDisplay) { |
| UpdateDisplay("500x400,500x400"); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100))); |
| std::unique_ptr<aura::Window> window2( |
| CreateTestWindow(gfx::Rect(550, 0, 100, 100))); |
| |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| EXPECT_EQ(root_windows[0], window1->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window2->GetRootWindow()); |
| |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| UpdateDisplay("500x400"); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests removing a display during overview with NON_ZERO_DURATION animation. |
| TEST_P(OverviewSessionTest, RemoveDisplayWithAnimation) { |
| UpdateDisplay("500x400,500x400"); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(gfx::Rect(100, 100))); |
| std::unique_ptr<aura::Window> window2( |
| CreateTestWindow(gfx::Rect(550, 0, 100, 100))); |
| |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| EXPECT_EQ(root_windows[0], window1->GetRootWindow()); |
| EXPECT_EQ(root_windows[1], window2->GetRootWindow()); |
| |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| UpdateDisplay("500x400"); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that tab key does not cause crash if pressed just after overview |
| // session exits. |
| TEST_P(OverviewSessionTest, NoCrashOnTabAfterExit) { |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| wm::ActivateWindow(window.get()); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| ToggleOverview(); |
| SendKey(ui::VKEY_TAB); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that tab key does not cause crash if pressed just after overview |
| // session exits, and a child window was active before session start. |
| TEST_P(OverviewSessionTest, |
| NoCrashOnTabAfterExitWithChildWindowInitiallyFocused) { |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> child_window = |
| ChildTestWindowBuilder(window.get()).Build(); |
| |
| wm::ActivateWindow(child_window.get()); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| ToggleOverview(); |
| SendKey(ui::VKEY_TAB); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that tab key does not cause crash if pressed just after overview |
| // session exits when no windows existed before starting overview session. |
| TEST_P(OverviewSessionTest, NoCrashOnTabAfterExitWithNoWindows) { |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| ToggleOverview(); |
| SendKey(ui::VKEY_TAB); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests that dragging a window from overview creates a drop target on the same |
| // display. |
| TEST_P(OverviewSessionTest, DropTargetOnCorrectDisplayForDraggingFromOverview) { |
| UpdateDisplay("600x500,600x500"); |
| EnterTabletMode(); |
| // DisplayConfigurationObserver enables mirror mode when tablet mode is |
| // enabled. Disable mirror mode to test multiple displays. |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| |
| const aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| |
| std::unique_ptr<aura::Window> primary_screen_window = |
| CreateTestWindow(gfx::Rect(0, 0, 600, 500)); |
| ASSERT_EQ(root_windows[0], primary_screen_window->GetRootWindow()); |
| std::unique_ptr<aura::Window> secondary_screen_window = |
| CreateTestWindow(gfx::Rect(600, 0, 600, 500)); |
| ASSERT_EQ(root_windows[1], secondary_screen_window->GetRootWindow()); |
| |
| ToggleOverview(); |
| OverviewItem* primary_screen_item = |
| GetOverviewItemForWindow(primary_screen_window.get()); |
| OverviewItem* secondary_screen_item = |
| GetOverviewItemForWindow(secondary_screen_window.get()); |
| |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| gfx::PointF drag_point = primary_screen_item->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(primary_screen_item, drag_point, |
| /*is_touch_dragging=*/true); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(primary_screen_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(1)); |
| ASSERT_TRUE(GetDropTarget(0)); |
| EXPECT_EQ(root_windows[0], GetDropTarget(0)->root_window()); |
| GetOverviewSession()->CompleteDrag(primary_screen_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| drag_point = secondary_screen_item->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(secondary_screen_item, drag_point, |
| /*is_touch_dragging=*/true); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(secondary_screen_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(0)); |
| ASSERT_TRUE(GetDropTarget(1)); |
| EXPECT_EQ(root_windows[1], GetDropTarget(1)->root_window()); |
| GetOverviewSession()->CompleteDrag(secondary_screen_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| } |
| |
| // Tests that the drop target is removed if a window is destroyed while being |
| // dragged from the top. |
| TEST_P(OverviewSessionTest, |
| DropTargetRemovedIfWindowDraggedFromTopIsDestroyed) { |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| window->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| std::unique_ptr<WindowResizer> resizer = |
| CreateWindowResizer(window.get(), gfx::PointF(400, 0), HTCAPTION, |
| ::wm::WINDOW_MOVE_SOURCE_TOUCH); |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetDropTarget(0)); |
| resizer.reset(); |
| window.reset(); |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_FALSE(GetDropTarget(0)); |
| } |
| |
| namespace { |
| |
| // A simple window delegate that returns the specified hit-test code when |
| // requested and applies a minimum size constraint if there is one. |
| class TestDragWindowDelegate : public aura::test::TestWindowDelegate { |
| public: |
| TestDragWindowDelegate() { set_window_component(HTCAPTION); } |
| |
| TestDragWindowDelegate(const TestDragWindowDelegate&) = delete; |
| TestDragWindowDelegate& operator=(const TestDragWindowDelegate&) = delete; |
| |
| ~TestDragWindowDelegate() override = default; |
| |
| private: |
| // aura::Test::TestWindowDelegate: |
| void OnWindowDestroyed(aura::Window* window) override { delete this; } |
| }; |
| |
| } // namespace |
| |
| // Tests that toggling overview on and off does not cancel drag. |
| TEST_P(OverviewSessionTest, DragDropInProgress) { |
| std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate( |
| new TestDragWindowDelegate(), -1, gfx::Rect(100, 100))); |
| |
| GetEventGenerator()->set_current_screen_location( |
| window->GetBoundsInScreen().CenterPoint()); |
| GetEventGenerator()->PressLeftButton(); |
| GetEventGenerator()->MoveMouseBy(10, 10); |
| EXPECT_EQ(gfx::Rect(10, 10, 100, 100), window->bounds()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| GetEventGenerator()->MoveMouseBy(10, 10); |
| |
| ToggleOverview(); |
| ASSERT_FALSE(InOverviewSession()); |
| |
| GetEventGenerator()->MoveMouseBy(10, 10); |
| GetEventGenerator()->ReleaseLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(gfx::Rect(30, 30, 100, 100), window->bounds()); |
| } |
| |
| // Tests that toggling overview on removes any resize shadows that may have been |
| // present. |
| TEST_P(OverviewSessionTest, DragWindowShadow) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(100, 100))); |
| wm::ActivateWindow(window.get()); |
| Shell::Get()->resize_shadow_controller()->ShowShadow(window.get(), HTTOP); |
| |
| ToggleOverview(); |
| ResizeShadow* shadow = |
| Shell::Get()->resize_shadow_controller()->GetShadowForWindowForTest( |
| window.get()); |
| EXPECT_FALSE(shadow); |
| } |
| |
| // Test that a label is created under the window on entering overview mode. |
| TEST_P(OverviewSessionTest, CreateLabelUnderWindow) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(300, 500))); |
| const std::u16string window_title = u"My window"; |
| window->SetTitle(window_title); |
| ToggleOverview(); |
| OverviewItem* window_item = GetOverviewItemsForRoot(0).back().get(); |
| views::Label* label = GetLabelView(window_item); |
| ASSERT_TRUE(label); |
| |
| // Verify the label matches the window title. |
| EXPECT_EQ(window_title, label->GetText()); |
| |
| // Update the window title and check that the label is updated, too. |
| const std::u16string updated_title = u"Updated title"; |
| window->SetTitle(updated_title); |
| EXPECT_EQ(updated_title, label->GetText()); |
| |
| // Labels are located based on target_bounds, not the actual window item |
| // bounds. |
| gfx::RectF label_bounds(label->GetWidget()->GetWindowBoundsInScreen()); |
| EXPECT_EQ(label_bounds, window_item->target_bounds()); |
| } |
| |
| // Tests that overview updates the window positions if the display orientation |
| // changes. |
| TEST_P(OverviewSessionTest, DisplayOrientationChanged) { |
| aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow(); |
| UpdateDisplay("600x200"); |
| EXPECT_EQ(gfx::Rect(600, 200), root_window->bounds()); |
| std::vector<std::unique_ptr<aura::Window>> windows; |
| for (int i = 0; i < 3; i++) { |
| windows.push_back( |
| std::unique_ptr<aura::Window>(CreateTestWindow(gfx::Rect(150, 150)))); |
| } |
| |
| ToggleOverview(); |
| for (const auto& window : windows) { |
| EXPECT_TRUE(root_window->bounds().Contains( |
| GetTransformedTargetBounds(window.get()))); |
| } |
| |
| // Rotate the display, windows should be repositioned to be within the screen |
| // bounds. |
| UpdateDisplay("600x200/r"); |
| EXPECT_EQ(gfx::Rect(200, 600), root_window->bounds()); |
| for (const auto& window : windows) { |
| EXPECT_TRUE(root_window->bounds().Contains( |
| GetTransformedTargetBounds(window.get()))); |
| } |
| } |
| |
| TEST_P(OverviewSessionTest, AcceleratorInOverviewSession) { |
| ToggleOverview(); |
| auto* accelerator_controller = Shell::Get()->accelerator_controller(); |
| auto* ewh = AcceleratorControllerImpl::TestApi(accelerator_controller) |
| .GetExitWarningHandler(); |
| ASSERT_TRUE(ewh); |
| StubForTest(ewh); |
| EXPECT_FALSE(IsUIShown(ewh)); |
| |
| PressAndReleaseKey(ui::VKEY_Q, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN); |
| EXPECT_TRUE(IsUIShown(ewh)); |
| } |
| |
| // Tests hitting the escape and back keys exits overview mode. |
| TEST_P(OverviewSessionTest, ExitOverviewWithKey) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| SendKey(ui::VKEY_ESCAPE); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| SendKey(ui::VKEY_BROWSER_BACK); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| |
| // Tests that in tablet mode, if we snap the only overview window, we cannot |
| // exit overview mode. |
| EnterTabletMode(); |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| GetSplitViewController()->SnapWindow(window.get(), SplitViewController::LEFT); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| SendKey(ui::VKEY_ESCAPE); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| SendKey(ui::VKEY_BROWSER_BACK); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| } |
| |
| // Regression test for clusterfuzz crash. https://crbug.com/920568 |
| TEST_P(OverviewSessionTest, TypeThenPressEscapeTwice) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| ToggleOverview(); |
| |
| // Type some characters. |
| SendKey(ui::VKEY_A); |
| SendKey(ui::VKEY_B); |
| SendKey(ui::VKEY_C); |
| EXPECT_TRUE(GetOverviewSession()->GetOverviewFocusWindow()); |
| |
| // Pressing escape twice should not crash. |
| SendKey(ui::VKEY_ESCAPE); |
| SendKey(ui::VKEY_ESCAPE); |
| } |
| |
| TEST_P(OverviewSessionTest, CancelOverviewOnMouseClick) { |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindow(gfx::Rect(10, 10, 100, 100))); |
| // Move mouse to point in the background page. Sending an event here will pass |
| // it to the WallpaperController in both regular and overview mode. |
| GetEventGenerator()->MoveMouseTo(gfx::Point(0, 0)); |
| |
| // Clicking on the background page while not in overview should not toggle |
| // overview. |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // Switch to overview mode. Clicking should now exit overview mode. |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| // Choose a point that doesn't intersect with the window or the desks bar. |
| const gfx::Point point_in_background_page = GetGridBounds().CenterPoint(); |
| GetEventGenerator()->MoveMouseTo(point_in_background_page); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests tapping on the desktop itself to cancel overview mode. |
| TEST_P(OverviewSessionTest, CancelOverviewOnTap) { |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindow(gfx::Rect(10, 10, 100, 100))); |
| |
| // Tapping on the background page while not in overview should not toggle |
| // overview. |
| GetEventGenerator()->GestureTapAt(gfx::Point(0, 0)); |
| EXPECT_FALSE(InOverviewSession()); |
| |
| // Switch to overview mode. Tapping should now exit overview mode. |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| // A point that doesn't intersect with the window nor the desks bar. This |
| // causes events located at the point to be passed to WallpaperController, and |
| // not the window. |
| const gfx::Point point_in_background_page = GetGridBounds().CenterPoint(); |
| GetEventGenerator()->GestureTapAt(point_in_background_page); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Start dragging a window and activate overview mode. This test should not |
| // crash or DCHECK inside aura::Window::StackChildRelativeTo(). |
| TEST_P(OverviewSessionTest, OverviewWhileDragging) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| std::unique_ptr<WindowResizer> resizer(CreateWindowResizer( |
| window.get(), gfx::PointF(), HTCAPTION, ::wm::WINDOW_MOVE_SOURCE_MOUSE)); |
| ASSERT_TRUE(resizer.get()); |
| gfx::PointF location = resizer->GetInitialLocation(); |
| location.Offset(20, 20); |
| resizer->Drag(location, 0); |
| ToggleOverview(); |
| resizer->RevertDrag(); |
| } |
| |
| // Verify that the overview no windows indicator appears when entering overview |
| // mode with no windows. |
| TEST_P(OverviewSessionTest, NoWindowsIndicator) { |
| UpdateDisplay("400x300,400x300"); |
| |
| // Verify that by entering overview mode without windows, the no items |
| // indicator appears. |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewSession()); |
| ASSERT_EQ(0u, GetOverviewItemsForRoot(0).size()); |
| for (auto& grid : GetOverviewSession()->grid_list()) |
| EXPECT_TRUE(grid->no_windows_widget()); |
| } |
| |
| // Verify that the overview no windows indicator position is as expected. |
| TEST_P(OverviewSessionTest, NoWindowsIndicatorPosition) { |
| UpdateDisplay("400x300"); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewSession()); |
| |
| RoundedLabelWidget* no_windows_widget = |
| GetOverviewSession()->grid_list()[0]->no_windows_widget(); |
| ASSERT_TRUE(no_windows_widget); |
| |
| // Verify that originally the label is in the center of the workspace. |
| // Midpoint of height minus shelf. |
| int expected_y = (300 - ShelfConfig::Get()->shelf_size() + |
| DesksBarView::kZeroStateBarHeight) / |
| 2; |
| EXPECT_EQ(gfx::Point(200, expected_y), |
| no_windows_widget->GetWindowBoundsInScreen().CenterPoint()); |
| |
| // Verify that after rotating the display, the label is centered in the |
| // workspace 300x(400-shelf). |
| display::Screen* screen = display::Screen::GetScreen(); |
| const display::Display& display = screen->GetPrimaryDisplay(); |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| expected_y = (400 - ShelfConfig::Get()->shelf_size() + |
| DesksBarView::kZeroStateBarHeight) / |
| 2; |
| EXPECT_EQ(gfx::Point(150, expected_y), |
| no_windows_widget->GetWindowBoundsInScreen().CenterPoint()); |
| } |
| |
| // Tests that toggling overview on removes any resize shadows that may have been |
| // present. |
| TEST_P(OverviewSessionTest, DragMinimizedWindowHasStableSize) { |
| UpdateDisplay(base::StringPrintf("1920x1200*%s", display::kDsfStr_1_777)); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| WindowState::Get(window.get())->Minimize(); |
| ToggleOverview(); |
| OverviewItem* overview_item = GetOverviewItemForWindow(window.get()); |
| auto* widget = overview_item->item_widget(); |
| |
| gfx::Rect workarea = |
| display::Screen::GetScreen()->GetPrimaryDisplay().work_area(); |
| |
| gfx::PointF drag_point(workarea.CenterPoint()); |
| GetOverviewSession()->InitiateDrag(overview_item, drag_point, |
| /*is_touch_dragging=*/true); |
| gfx::Size target_size = |
| GetTransformedTargetBounds(widget->GetNativeWindow()).size(); |
| |
| drag_point.Offset(0, 10.5f); |
| GetOverviewSession()->Drag(overview_item, drag_point); |
| gfx::Size new_target_size = |
| GetTransformedTargetBounds(widget->GetNativeWindow()).size(); |
| EXPECT_EQ(target_size, new_target_size); |
| target_size = new_target_size; |
| |
| drag_point.Offset(0, 10.5f); |
| GetOverviewSession()->Drag(overview_item, drag_point); |
| EXPECT_EQ(target_size, |
| GetTransformedTargetBounds(widget->GetNativeWindow()).size()); |
| |
| GetOverviewSession()->CompleteDrag(overview_item, drag_point); |
| } |
| |
| // Tests that the bounds of the grid do not intersect the shelf or its hotseat. |
| TEST_P(OverviewSessionTest, OverviewGridBounds) { |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewSession()); |
| |
| Shelf* shelf = Shelf::ForWindow(Shell::GetPrimaryRootWindow()); |
| const gfx::Rect shelf_bounds = shelf->GetIdealBounds(); |
| const gfx::Rect hotseat_bounds = |
| shelf->hotseat_widget()->GetWindowBoundsInScreen(); |
| EXPECT_FALSE(GetGridBounds().Intersects(shelf_bounds)); |
| EXPECT_FALSE(GetGridBounds().Intersects(hotseat_bounds)); |
| } |
| |
| TEST_P(OverviewSessionTest, NoWindowsIndicatorPositionSplitview) { |
| UpdateDisplay("400x300"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewSession()); |
| RoundedLabelWidget* no_windows_widget = |
| GetOverviewSession()->grid_list()[0]->no_windows_widget(); |
| EXPECT_FALSE(no_windows_widget); |
| |
| // Tests that when snapping a window to the left in splitview, the no windows |
| // indicator shows up in the middle of the right side of the screen. |
| GetSplitViewController()->SnapWindow(window.get(), SplitViewController::LEFT); |
| no_windows_widget = GetOverviewSession()->grid_list()[0]->no_windows_widget(); |
| ASSERT_TRUE(no_windows_widget); |
| |
| // There is a 8dp divider in splitview, the indicator should take that into |
| // account. |
| const int bounds_left = 200 + 4; |
| int expected_x = bounds_left + (400 - (bounds_left)) / 2; |
| const int workarea_bottom_inset = |
| ShelfConfig::Get()->in_app_shelf_size() + |
| ShelfConfig::Get()->system_shelf_size() + |
| ShelfConfig::Get()->hotseat_bottom_padding(); |
| const int expected_y = (300 - workarea_bottom_inset) / 2; |
| EXPECT_EQ(gfx::Point(expected_x, expected_y), |
| no_windows_widget->GetWindowBoundsInScreen().CenterPoint()); |
| |
| // Tests that when snapping a window to the right in splitview, the no windows |
| // indicator shows up in the middle of the left side of the screen. |
| GetSplitViewController()->SnapWindow(window.get(), |
| SplitViewController::RIGHT); |
| expected_x = /*bounds_right=*/(200 - 4) / 2; |
| EXPECT_EQ(gfx::Point(expected_x, expected_y), |
| no_windows_widget->GetWindowBoundsInScreen().CenterPoint()); |
| } |
| |
| // Tests that the no windows indicator shows properly after adding an item. |
| TEST_P(OverviewSessionTest, NoWindowsIndicatorAddItem) { |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| ToggleOverview(); |
| GetSplitViewController()->SnapWindow(window.get(), SplitViewController::LEFT); |
| EXPECT_TRUE(GetOverviewSession()->grid_list()[0]->no_windows_widget()); |
| |
| GetOverviewSession()->AddItem(window.get(), /*reposition=*/true, |
| /*animate=*/false, /*ignored_items=*/{}, |
| /*index=*/0u); |
| EXPECT_FALSE(GetOverviewSession()->grid_list()[0]->no_windows_widget()); |
| } |
| |
| // Tests that we do not exit overview mode until all the grids are empty. |
| TEST_P(OverviewSessionTest, ExitOverviewWhenAllGridsEmpty) { |
| UpdateDisplay("500x400,500x400,500x400"); |
| |
| // Create two windows with widgets (widgets are needed to close the windows |
| // later in the test), one each on the first two monitors. |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| std::unique_ptr<views::Widget> widget1(CreateTestWidget()); |
| std::unique_ptr<views::Widget> widget2(CreateTestWidget()); |
| aura::Window* window1 = widget1->GetNativeWindow(); |
| aura::Window* window2 = widget2->GetNativeWindow(); |
| ASSERT_TRUE( |
| window_util::MoveWindowToDisplay(window2, GetSecondaryDisplay().id())); |
| ASSERT_EQ(root_windows[0], window1->GetRootWindow()); |
| ASSERT_EQ(root_windows[1], window2->GetRootWindow()); |
| |
| // Enter overview mode. Verify that the no windows indicator is not visible on |
| // any display. |
| ToggleOverview(); |
| auto& grids = GetOverviewSession()->grid_list(); |
| ASSERT_TRUE(GetOverviewSession()); |
| ASSERT_EQ(3u, grids.size()); |
| for (auto& grid : grids) |
| EXPECT_FALSE(grid->no_windows_widget()); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2); |
| ASSERT_TRUE(item1 && item2); |
| |
| // Close |item2|. Verify that we are still in overview mode because |window1| |
| // is still open. All the grids should not have a no windows widget. |
| item2->CloseWindow(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(GetOverviewSession()); |
| ASSERT_EQ(3u, grids.size()); |
| EXPECT_FALSE(grids[0]->empty()); |
| EXPECT_TRUE(grids[1]->empty()); |
| EXPECT_TRUE(grids[2]->empty()); |
| for (auto& grid : grids) |
| EXPECT_FALSE(grid->no_windows_widget()); |
| |
| // Close |item1|. Verify that since no windows are open, we exit overview |
| // mode. |
| item1->CloseWindow(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetOverviewSession()); |
| } |
| |
| // Tests window list animation states are correctly updated. |
| TEST_P(OverviewSessionTest, SetWindowListAnimationStates) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| // Enter overview. |
| ToggleOverview(); |
| EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating()); |
| EXPECT_FALSE(window3->layer()->GetAnimator()->is_animating()); |
| |
| ToggleOverview(); |
| } |
| |
| // Tests window list animation states are correctly updated with selected |
| // window. |
| TEST_P(OverviewSessionTest, SetWindowListAnimationStatesWithSelectedWindow) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| // Enter overview. |
| ToggleOverview(); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| // Click on |window3| to activate it and exit overview. |
| // Should only set |should_animate_when_exiting_| and |
| // |should_be_observed_when_exiting_| on window 3. |
| TweenTester tester1(window1.get()); |
| TweenTester tester2(window2.get()); |
| TweenTester tester3(window3.get()); |
| ClickWindow(window3.get()); |
| EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type()); |
| EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type()); |
| EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type()); |
| } |
| |
| // Tests OverviewWindowAnimationObserver can handle deleted window. |
| TEST_P(OverviewSessionTest, |
| OverviewWindowAnimationObserverCanHandleDeletedWindow) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| // Enter overview. |
| ToggleOverview(); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| // Click on |window3| to activate it and exit overview. |
| // Should only set |should_animate_when_exiting_| and |
| // |should_be_observed_when_exiting_| on window 3. |
| { |
| TweenTester tester1(window1.get()); |
| TweenTester tester2(window2.get()); |
| TweenTester tester3(window3.get()); |
| ClickWindow(window3.get()); |
| EXPECT_EQ(gfx::Tween::ZERO, tester1.tween_type()); |
| EXPECT_EQ(gfx::Tween::ZERO, tester2.tween_type()); |
| EXPECT_EQ(gfx::Tween::EASE_OUT, tester3.tween_type()); |
| } |
| // Destroy |window1| and |window2| before |window3| finishes animation can be |
| // handled in OverviewWindowAnimationObserver. |
| window1.reset(); |
| window2.reset(); |
| } |
| |
| // Tests can handle OverviewWindowAnimationObserver was deleted. |
| TEST_P(OverviewSessionTest, HandleOverviewWindowAnimationObserverWasDeleted) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| // Enter overview. |
| ToggleOverview(); |
| |
| // Click on |window2| to activate it and exit overview. Should only set |
| // |should_animate_when_exiting_| and |should_be_observed_when_exiting_| on |
| // window 2. Because the animation duration is zero in test, the |
| // OverviewWindowAnimationObserver will delete itself immediately before |
| // |window3| is added to it. |
| ClickWindow(window2.get()); |
| EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating()); |
| EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating()); |
| EXPECT_FALSE(window3->layer()->GetAnimator()->is_animating()); |
| } |
| |
| // Tests can handle |gained_active| window is not in the |overview_grid| when |
| // OnWindowActivated. |
| TEST_P(OverviewSessionTest, HandleActiveWindowNotInOverviewGrid) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_FALSE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window3.get())->OnWMEvent(&toggle_fullscreen_event); |
| EXPECT_FALSE(WindowState::Get(window1.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| EXPECT_TRUE(WindowState::Get(window3.get())->IsFullscreen()); |
| |
| // Enter overview. |
| ToggleOverview(); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| // Create and active a new window should exit overview without error. |
| auto widget = CreateTestWidget(); |
| |
| TweenTester tester1(window1.get()); |
| TweenTester tester2(window2.get()); |
| TweenTester tester3(window3.get()); |
| |
| ClickWindow(widget->GetNativeWindow()); |
| |
| // |window1| and |window2| should animate. |
| EXPECT_EQ(gfx::Tween::EASE_OUT, tester1.tween_type()); |
| EXPECT_EQ(gfx::Tween::EASE_OUT, tester2.tween_type()); |
| EXPECT_EQ(gfx::Tween::ZERO, tester3.tween_type()); |
| } |
| |
| // Tests that AlwaysOnTopWindow can be handled correctly in new overview |
| // animations. |
| TEST_P(OverviewSessionTest, HandleAlwaysOnTopWindow) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window4(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window5( |
| CreateTestWindow(gfx::Rect(200, 200, 400, 400))); |
| std::unique_ptr<aura::Window> window6(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window7(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window8(CreateTestWindow(bounds)); |
| window3->SetProperty(aura::client::kZOrderingKey, |
| ui::ZOrderLevel::kFloatingWindow); |
| window5->SetProperty(aura::client::kZOrderingKey, |
| ui::ZOrderLevel::kFloatingWindow); |
| |
| // Control z order and MRU order. |
| wm::ActivateWindow(window8.get()); |
| wm::ActivateWindow(window7.get()); // Will be fullscreen. |
| wm::ActivateWindow(window6.get()); // Will be maximized. |
| wm::ActivateWindow(window5.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window4.get()); |
| wm::ActivateWindow(window3.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window2.get()); // Will be fullscreen. |
| wm::ActivateWindow(window1.get()); |
| |
| const WMEvent toggle_maximize_event(WM_EVENT_TOGGLE_MAXIMIZE); |
| WindowState::Get(window6.get())->OnWMEvent(&toggle_maximize_event); |
| const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| ASSERT_TRUE(WindowState::Get(window2.get())->IsFullscreen()); |
| ASSERT_TRUE(WindowState::Get(window7.get())->IsFullscreen()); |
| ASSERT_TRUE(WindowState::Get(window6.get())->IsMaximized()); |
| |
| // Helper to check if `window` is visibly animating. In some overview |
| // animations, we use tween zero, so there is no visible animation though it |
| // technically is animating according to the ui::LayerAnimator API. |
| auto is_visibly_animating = [](aura::Window* window) -> bool { |
| ui::LayerAnimatorTestController controller(window->layer()->GetAnimator()); |
| ui::LayerAnimationSequence* sequence = |
| controller.GetRunningSequence(ui::LayerAnimationElement::TRANSFORM); |
| if (!sequence) |
| return false; |
| // There's only one element per sequence in the overview animation so this |
| // is fine. |
| ui::LayerAnimationElement* element = sequence->FirstElement(); |
| if (!element) |
| return false; |
| return element->tween_type() != gfx::Tween::ZERO; |
| }; |
| |
| // Case 1: Click on `window1` to activate it and exit overview. |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| ToggleOverview(); |
| // For entering animation, only animate `window1`, `window2`, `window3` and |
| // `window5`. `window2` is fullscreen so all windows except `window1`, |
| // `window3` and `window5` are occluded. |
| EXPECT_TRUE(is_visibly_animating(window1.get())); |
| EXPECT_TRUE(is_visibly_animating(window2.get())); |
| EXPECT_TRUE(is_visibly_animating(window3.get())); |
| EXPECT_FALSE(is_visibly_animating(window4.get())); |
| EXPECT_TRUE(is_visibly_animating(window5.get())); |
| EXPECT_FALSE(is_visibly_animating(window6.get())); |
| EXPECT_FALSE(is_visibly_animating(window7.get())); |
| EXPECT_FALSE(is_visibly_animating(window8.get())); |
| WaitForOverviewEnterAnimation(); |
| |
| // Click on `window1` to activate it and exit overview. `window2` occludes |
| // everything but `window1`, `window3` and `window5`, and `window1` is |
| // occluded by `window3`. So `window2`, `window3` and `window5` should be |
| // animated. |
| ClickWindow(window1.get()); |
| EXPECT_FALSE(is_visibly_animating(window1.get())); |
| EXPECT_TRUE(is_visibly_animating(window2.get())); |
| EXPECT_TRUE(is_visibly_animating(window3.get())); |
| EXPECT_FALSE(is_visibly_animating(window4.get())); |
| EXPECT_TRUE(is_visibly_animating(window5.get())); |
| EXPECT_FALSE(is_visibly_animating(window6.get())); |
| EXPECT_FALSE(is_visibly_animating(window7.get())); |
| EXPECT_FALSE(is_visibly_animating(window8.get())); |
| WaitForOverviewExitAnimation(); |
| |
| // Case 2: Click on `window3` to activate it and exit overview. Since |
| // `window2` is fullscreen, all windows after it are occluded, except |
| // `window3` and `window5`, which are always on top. `window1` is not occluded |
| // by `window2` but has the same bounds as `window3` so is occluded. |
| // Reset window z-order. Need to toggle fullscreen first to workaround |
| // https://crbug.com/816224. |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| wm::ActivateWindow(window8.get()); |
| wm::ActivateWindow(window7.get()); // Will be fullscreen. |
| wm::ActivateWindow(window6.get()); // Maximized. |
| wm::ActivateWindow(window5.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window4.get()); |
| wm::ActivateWindow(window3.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window2.get()); // Will be fullscreen. |
| wm::ActivateWindow(window1.get()); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| |
| // Enter overview. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| ClickWindow(window3.get()); |
| EXPECT_FALSE(is_visibly_animating(window1.get())); |
| EXPECT_TRUE(is_visibly_animating(window2.get())); |
| EXPECT_TRUE(is_visibly_animating(window3.get())); |
| EXPECT_FALSE(is_visibly_animating(window4.get())); |
| EXPECT_TRUE(is_visibly_animating(window5.get())); |
| EXPECT_FALSE(is_visibly_animating(window6.get())); |
| EXPECT_FALSE(is_visibly_animating(window7.get())); |
| EXPECT_FALSE(is_visibly_animating(window8.get())); |
| WaitForOverviewExitAnimation(); |
| |
| // Case 3: Click on maximized `window6` to activate it and exit overview. |
| // `window6` will become the topmost regular z-order window and will occlude |
| // everything except `window2` as it is fullscreen and `window3` and `window5` |
| // as they are always on top. Reset window z-order. Need to toggle fullscreen |
| // first to workaround https://crbug.com/816224. |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| wm::ActivateWindow(window8.get()); |
| wm::ActivateWindow(window7.get()); // Will be fullscreen. |
| wm::ActivateWindow(window6.get()); // Maximized. |
| wm::ActivateWindow(window5.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window4.get()); |
| wm::ActivateWindow(window3.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window2.get()); // Will be fullscreen. |
| wm::ActivateWindow(window1.get()); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| // Enter overview. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| ClickWindow(window6.get()); |
| EXPECT_FALSE(is_visibly_animating(window1.get())); |
| EXPECT_FALSE(is_visibly_animating(window2.get())); |
| EXPECT_TRUE(is_visibly_animating(window3.get())); |
| EXPECT_FALSE(is_visibly_animating(window4.get())); |
| EXPECT_TRUE(is_visibly_animating(window5.get())); |
| EXPECT_TRUE(is_visibly_animating(window6.get())); |
| EXPECT_FALSE(is_visibly_animating(window7.get())); |
| EXPECT_FALSE(is_visibly_animating(window8.get())); |
| WaitForOverviewExitAnimation(); |
| |
| // Case 4: Click on `window8` to activate it and exit overview. |
| // Should animate `window8`, `window1`, `window2`, `window3` and `window5` |
| // because `window3` and `window5` are AlwaysOnTop windows and `window2` is |
| // fullscreen. |
| // Reset window z-order. Need to toggle fullscreen first to workaround |
| // https://crbug.com/816224. |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| wm::ActivateWindow(window8.get()); |
| wm::ActivateWindow(window7.get()); // Will be fullscreen. |
| wm::ActivateWindow(window6.get()); // Maximized. |
| wm::ActivateWindow(window5.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window4.get()); |
| wm::ActivateWindow(window3.get()); // AlwaysOnTop window. |
| wm::ActivateWindow(window2.get()); // Will be fullscreen. |
| wm::ActivateWindow(window1.get()); |
| WindowState::Get(window2.get())->OnWMEvent(&toggle_fullscreen_event); |
| WindowState::Get(window7.get())->OnWMEvent(&toggle_fullscreen_event); |
| |
| // Enter overview. |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| ClickWindow(window8.get()); |
| EXPECT_FALSE(is_visibly_animating(window1.get())); |
| EXPECT_TRUE(is_visibly_animating(window2.get())); |
| EXPECT_TRUE(is_visibly_animating(window3.get())); |
| EXPECT_FALSE(is_visibly_animating(window4.get())); |
| EXPECT_TRUE(is_visibly_animating(window5.get())); |
| EXPECT_FALSE(is_visibly_animating(window6.get())); |
| EXPECT_FALSE(is_visibly_animating(window7.get())); |
| EXPECT_TRUE(is_visibly_animating(window8.get())); |
| WaitForOverviewExitAnimation(); |
| } |
| |
| // Verify that the selector item can animate after the item is dragged and |
| // released. |
| TEST_P(OverviewSessionTest, WindowItemCanAnimateOnDragRelease) { |
| base::HistogramTester histogram_tester; |
| UpdateDisplay("500x400"); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| // Drag |item2| in a way so that |window2| does not get activated. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo( |
| gfx::ToRoundedPoint(item2->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| |
| generator->MoveMouseTo(gfx::Point(250, 200)); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| generator->ReleaseLeftButton(); |
| EXPECT_TRUE(window2->layer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::AnimatableProperty::TRANSFORM)); |
| base::RunLoop().RunUntilIdle(); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1); |
| } |
| |
| // Verify that the overview items titlebar and close button change visibility |
| // when a item is being dragged. |
| TEST_P(OverviewSessionTest, OverviewItemTitleCloseVisibilityOnDrag) { |
| base::HistogramTester histogram_tester; |
| UpdateDisplay("500x400"); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| // Start the drag on |item1|. Verify the dragged item, |item1| has both the |
| // close button and titlebar hidden. The close button opacity however is |
| // opaque as its a child of the header which handles fading away the whole |
| // header. All other items, |item2| should only have the close button hidden. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo( |
| gfx::ToRoundedPoint(item1->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(0.f, GetTitlebarOpacity(item1)); |
| EXPECT_EQ(1.f, GetCloseButtonOpacity(item1)); |
| EXPECT_EQ(1.f, GetTitlebarOpacity(item2)); |
| EXPECT_EQ(0.f, GetCloseButtonOpacity(item2)); |
| |
| // Drag |item1| in a way so that |window1| does not get activated (drags |
| // within a certain threshold count as clicks). Verify the close button and |
| // titlebar is visible for all items. |
| generator->MoveMouseTo(gfx::Point(250, 200)); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0); |
| |
| generator->ReleaseLeftButton(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1.f, GetTitlebarOpacity(item1)); |
| EXPECT_EQ(1.f, GetCloseButtonOpacity(item1)); |
| EXPECT_EQ(1.f, GetTitlebarOpacity(item2)); |
| EXPECT_EQ(1.f, GetCloseButtonOpacity(item2)); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1); |
| } |
| |
| // Tests that overview widgets are stacked in the correct order. |
| TEST_P(OverviewSessionTest, OverviewWidgetStackingOrder) { |
| base::HistogramTester histogram_tester; |
| // Create three windows, including one minimized. |
| std::unique_ptr<aura::Window> minimized(CreateTestWindow()); |
| WindowState::Get(minimized.get())->Minimize(); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| |
| aura::Window* parent = window->parent(); |
| EXPECT_EQ(parent, minimized->parent()); |
| |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(minimized.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| |
| views::Widget* widget1 = item1->item_widget(); |
| views::Widget* widget2 = item2->item_widget(); |
| views::Widget* widget3 = item3->item_widget(); |
| |
| // The original order of stacking is determined by the order the associated |
| // window was activated. |
| EXPECT_GT(IndexOf(widget3->GetNativeWindow(), parent), |
| IndexOf(widget2->GetNativeWindow(), parent)); |
| EXPECT_GT(IndexOf(widget2->GetNativeWindow(), parent), |
| IndexOf(widget1->GetNativeWindow(), parent)); |
| |
| // Verify that the item widget is stacked below the window. |
| EXPECT_LT(IndexOf(widget1->GetNativeWindow(), parent), |
| IndexOf(minimized.get(), parent)); |
| EXPECT_LT(IndexOf(widget2->GetNativeWindow(), parent), |
| IndexOf(window.get(), parent)); |
| EXPECT_LT(IndexOf(widget3->GetNativeWindow(), parent), |
| IndexOf(window3.get(), parent)); |
| |
| // Drag the first window. Verify that it's item widget is not stacked above |
| // the other two. |
| const gfx::Point start_drag = |
| gfx::ToRoundedPoint(item1->target_bounds().CenterPoint()); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(start_drag); |
| generator->PressLeftButton(); |
| EXPECT_GT(IndexOf(widget1->GetNativeWindow(), parent), |
| IndexOf(widget2->GetNativeWindow(), parent)); |
| EXPECT_GT(IndexOf(widget1->GetNativeWindow(), parent), |
| IndexOf(widget3->GetNativeWindow(), parent)); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 0); |
| |
| // Drag to origin and then back to the start to avoid activating the window or |
| // entering splitview. |
| generator->MoveMouseTo(gfx::Point()); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 1); |
| |
| generator->MoveMouseTo(start_drag); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 2); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 0); |
| |
| generator->ReleaseLeftButton(); |
| |
| // Verify the stacking order is same as before dragging started. |
| EXPECT_GT(IndexOf(widget3->GetNativeWindow(), parent), |
| IndexOf(widget2->GetNativeWindow(), parent)); |
| EXPECT_GT(IndexOf(widget2->GetNativeWindow(), parent), |
| IndexOf(widget1->GetNativeWindow(), parent)); |
| |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.TabletMode", 2); |
| histogram_tester.ExpectTotalCount( |
| "Ash.Overview.WindowDrag.PresentationTime.MaxLatency.TabletMode", 1); |
| } |
| |
| // Test that dragging a window from the top creates a drop target stacked at the |
| // bottom. Test that dropping into overview removes the drop target. |
| TEST_P(OverviewSessionTest, DropTargetStackedAtBottomForWindowDraggedFromTop) { |
| UpdateDisplay("800x600"); |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| window1->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| aura::Window* parent = window1->parent(); |
| ASSERT_EQ(parent, window2->parent()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| std::unique_ptr<WindowResizer> resizer = |
| CreateWindowResizer(window1.get(), gfx::PointF(400, 0), HTCAPTION, |
| ::wm::WINDOW_MOVE_SOURCE_TOUCH); |
| ASSERT_TRUE(GetDropTarget(0)); |
| EXPECT_LT(IndexOf(GetDropTarget(0)->GetWindow(), parent), |
| IndexOf(window2.get(), parent)); |
| resizer->Drag(gfx::PointF(400, 500), ui::EF_NONE); |
| resizer->CompleteDrag(); |
| EXPECT_FALSE(GetDropTarget(0)); |
| } |
| |
| // Test that dragging an overview item to snap creates a drop target stacked at |
| // the bottom. Test that ending the drag removes the drop target. |
| TEST_P(OverviewSessionTest, DropTargetStackedAtBottomForOverviewItem) { |
| EnterTabletMode(); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| aura::Window* parent = window1->parent(); |
| ASSERT_EQ(parent, window2->parent()); |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| ToggleOverview(); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(gfx::ToRoundedPoint( |
| GetOverviewItemForWindow(window1.get())->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| generator->MoveMouseBy(5, 0); |
| ASSERT_TRUE(GetDropTarget(0)); |
| EXPECT_LT(IndexOf(GetDropTarget(0)->GetWindow(), parent), |
| IndexOf(window2.get(), parent)); |
| generator->ReleaseLeftButton(); |
| EXPECT_FALSE(GetDropTarget(0)); |
| } |
| |
| // Verify that a windows which enter overview mode have a visible backdrop, if |
| // the window is to be letter or pillar fitted. |
| TEST_P(OverviewSessionTest, Backdrop) { |
| // Add three windows which in overview mode will be considered wide, tall and |
| // normal. Window |wide|, with size (400, 160) will be resized to (300, 160) |
| // when the 400x300 is rotated to 300x400, and should be considered a normal |
| // overview window after display change. |
| UpdateDisplay("400x300"); |
| std::unique_ptr<aura::Window> wide(CreateTestWindow(gfx::Rect(400, 160))); |
| std::unique_ptr<aura::Window> tall(CreateTestWindow(gfx::Rect(100, 300))); |
| std::unique_ptr<aura::Window> normal(CreateTestWindow(gfx::Rect(300, 300))); |
| |
| ToggleOverview(); |
| base::RunLoop().RunUntilIdle(); |
| OverviewItem* wide_item = GetOverviewItemForWindow(wide.get()); |
| OverviewItem* tall_item = GetOverviewItemForWindow(tall.get()); |
| OverviewItem* normal_item = GetOverviewItemForWindow(normal.get()); |
| |
| // Only very tall and very wide windows will have a backdrop. The backdrop |
| // only gets created if we need it once during the overview session. |
| ASSERT_TRUE(GetBackdropView(wide_item)); |
| EXPECT_TRUE(GetBackdropView(wide_item)->GetVisible()); |
| EXPECT_TRUE(GetBackdropView(tall_item)); |
| ASSERT_TRUE(GetBackdropView(tall_item)->GetVisible()); |
| EXPECT_FALSE(GetBackdropView(normal_item)); |
| |
| display::Screen* screen = display::Screen::GetScreen(); |
| const display::Display& display = screen->GetPrimaryDisplay(); |
| display_manager()->SetDisplayRotation( |
| display.id(), display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| |
| // After rotation the former wide window will be a normal window and its |
| // backdrop will still be there but invisible. |
| ASSERT_TRUE(GetBackdropView(wide_item)); |
| EXPECT_FALSE(GetBackdropView(wide_item)->GetVisible()); |
| EXPECT_TRUE(GetBackdropView(tall_item)); |
| ASSERT_TRUE(GetBackdropView(tall_item)->GetVisible()); |
| EXPECT_FALSE(GetBackdropView(normal_item)); |
| |
| // Test that leaving overview mode cleans up properly. |
| ToggleOverview(); |
| } |
| |
| // Test that the rounded corners are removed during animations. |
| TEST_P(OverviewSessionTest, RoundedCornersVisibility) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Test that entering overview mode normally will disable all the rounded |
| // corners until the animation is complete. |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| EXPECT_FALSE(HasRoundedCorner(item1)); |
| EXPECT_FALSE(HasRoundedCorner(item2)); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kEnterAnimationComplete); |
| EXPECT_TRUE(HasRoundedCorner(item1)); |
| EXPECT_TRUE(HasRoundedCorner(item2)); |
| |
| // Tests that entering overview mode with all windows minimized (launcher |
| // button pressed) will still disable all the rounded corners until the |
| // animation is complete. |
| ToggleOverview(); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kExitAnimationComplete); |
| WindowState::Get(window1.get())->Minimize(); |
| WindowState::Get(window2.get())->Minimize(); |
| |
| ToggleOverview(); |
| item1 = GetOverviewItemForWindow(window1.get()); |
| item2 = GetOverviewItemForWindow(window2.get()); |
| EXPECT_FALSE(HasRoundedCorner(item1)); |
| EXPECT_FALSE(HasRoundedCorner(item2)); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kEnterAnimationComplete); |
| EXPECT_TRUE(HasRoundedCorner(item1)); |
| EXPECT_TRUE(HasRoundedCorner(item2)); |
| |
| // Test that leaving overview mode cleans up properly. |
| ToggleOverview(); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kExitAnimationComplete); |
| } |
| |
| // Test that the shadow disappears while dragging an overview item. |
| TEST_P(OverviewSessionTest, ShadowVisibilityDragging) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| wm::ActivateWindow(window2.get()); |
| wm::ActivateWindow(window1.get()); |
| |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Drag the first window. Verify that the shadow was removed for the first |
| // window but still exists for the second window as we do not make shadow |
| // for a dragged window. |
| const gfx::Point start_drag = |
| gfx::ToRoundedPoint(item1->target_bounds().CenterPoint()); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(start_drag); |
| generator->PressLeftButton(); |
| EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating()); |
| EXPECT_FALSE(window2->layer()->GetAnimator()->is_animating()); |
| |
| EXPECT_TRUE(item1->GetShadowBoundsForTesting().IsEmpty()); |
| EXPECT_FALSE(item2->GetShadowBoundsForTesting().IsEmpty()); |
| |
| // Drag to horizontally and then back to the start to avoid activating the |
| // window, drag to close or entering splitview. Verify that the shadow is |
| // invisible on both items during animation. |
| generator->MoveMouseTo(gfx::Point(0, start_drag.y())); |
| |
| // The drop target window should be created with no shadow. |
| OverviewItem* drop_target_item = GetDropTarget(0); |
| ASSERT_TRUE(drop_target_item); |
| EXPECT_TRUE(drop_target_item->GetShadowBoundsForTesting().IsEmpty()); |
| |
| generator->MoveMouseTo(start_drag); |
| generator->ReleaseLeftButton(); |
| EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating()); |
| EXPECT_TRUE(item1->GetShadowBoundsForTesting().IsEmpty()); |
| EXPECT_TRUE(item2->GetShadowBoundsForTesting().IsEmpty()); |
| |
| // Verify that the shadow is visble again after animation is finished. |
| window1->layer()->GetAnimator()->StopAnimating(); |
| window2->layer()->GetAnimator()->StopAnimating(); |
| EXPECT_FALSE(item1->GetShadowBoundsForTesting().IsEmpty()); |
| EXPECT_FALSE(item2->GetShadowBoundsForTesting().IsEmpty()); |
| } |
| |
| // Tests that the shadows in overview mode are placed correctly. |
| TEST_P(OverviewSessionTest, ShadowBounds) { |
| // Helper function to check if the bounds of a shadow owned by |shadow_parent| |
| // is contained within the bounds of |widget|. |
| auto contains = [](views::Widget* widget, OverviewItem* shadow_parent) { |
| return gfx::Rect(widget->GetNativeWindow()->bounds().size()) |
| .Contains(shadow_parent->GetShadowBoundsForTesting()); |
| }; |
| |
| // Helper function which returns the ratio of the shadow owned by |
| // |shadow_parent| width and height. |
| auto shadow_ratio = [](OverviewItem* shadow_parent) { |
| gfx::RectF boundsf = gfx::RectF(shadow_parent->GetShadowBoundsForTesting()); |
| return boundsf.width() / boundsf.height(); |
| }; |
| |
| // Add three windows which in overview mode will be considered wide, tall and |
| // normal. Set top view insets to 0 so it is easy to check the ratios of the |
| // shadows match the ratios of the untransformed windows. |
| UpdateDisplay("900x800"); |
| std::unique_ptr<aura::Window> wide( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(400, 100))); |
| std::unique_ptr<aura::Window> tall( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(100, 400))); |
| std::unique_ptr<aura::Window> normal( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect(200, 200))); |
| wide->SetProperty(aura::client::kTopViewInset, 0); |
| tall->SetProperty(aura::client::kTopViewInset, 0); |
| normal->SetProperty(aura::client::kTopViewInset, 0); |
| |
| ToggleOverview(); |
| base::RunLoop().RunUntilIdle(); |
| OverviewItem* wide_item = GetOverviewItemForWindow(wide.get()); |
| OverviewItem* tall_item = GetOverviewItemForWindow(tall.get()); |
| OverviewItem* normal_item = GetOverviewItemForWindow(normal.get()); |
| |
| views::Widget* wide_widget = wide_item->item_widget(); |
| views::Widget* tall_widget = tall_item->item_widget(); |
| views::Widget* normal_widget = normal_item->item_widget(); |
| |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| |
| // Verify all the shadows are within the bounds of their respective item |
| // widgets when the overview windows are positioned without animations. |
| SetGridBounds(grid, gfx::Rect(400, 800)); |
| grid->PositionWindows(false); |
| EXPECT_TRUE(contains(wide_widget, wide_item)); |
| EXPECT_TRUE(contains(tall_widget, tall_item)); |
| EXPECT_TRUE(contains(normal_widget, normal_item)); |
| |
| // Verify the shadows preserve the ratios of the original windows. |
| EXPECT_NEAR(shadow_ratio(wide_item), 4.f, 0.01f); |
| EXPECT_NEAR(shadow_ratio(tall_item), 0.25f, 0.01f); |
| EXPECT_NEAR(shadow_ratio(normal_item), 1.f, 0.01f); |
| |
| // Verify all the shadows are within the bounds of their respective item |
| // widgets when the overview windows are positioned with animations. |
| SetGridBounds(grid, gfx::Rect(400, 800)); |
| grid->PositionWindows(true); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(contains(wide_widget, wide_item)); |
| EXPECT_TRUE(contains(tall_widget, tall_item)); |
| EXPECT_TRUE(contains(normal_widget, normal_item)); |
| |
| EXPECT_NEAR(shadow_ratio(wide_item), 4.f, 0.01f); |
| EXPECT_NEAR(shadow_ratio(tall_item), 0.25f, 0.01f); |
| EXPECT_NEAR(shadow_ratio(normal_item), 1.f, 0.01f); |
| |
| // Test that leaving overview mode cleans up properly. |
| ToggleOverview(); |
| } |
| |
| // Verify that attempting to drag with a secondary finger works as expected. |
| TEST_P(OverviewSessionTest, DraggingWithTwoFingers) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| EnterTabletMode(); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| |
| const gfx::RectF original_bounds1 = item1->target_bounds(); |
| const gfx::RectF original_bounds2 = item2->target_bounds(); |
| |
| constexpr int kTouchId1 = 1; |
| constexpr int kTouchId2 = 2; |
| |
| // Dispatches a long press event at the event generators current location. |
| // Long press is one way to start dragging in splitview. |
| auto dispatch_long_press = [this]() { |
| ui::GestureEventDetails event_details(ui::ET_GESTURE_LONG_PRESS); |
| const gfx::Point location = GetEventGenerator()->current_screen_location(); |
| ui::GestureEvent long_press(location.x(), location.y(), 0, |
| ui::EventTimeForNow(), event_details); |
| GetEventGenerator()->Dispatch(&long_press); |
| }; |
| |
| // Verify that the bounds of the tapped window expand when touched. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(original_bounds1.CenterPoint())); |
| generator->PressTouchId(kTouchId1); |
| dispatch_long_press(); |
| EXPECT_GT(item1->target_bounds().width(), original_bounds1.width()); |
| EXPECT_GT(item1->target_bounds().height(), original_bounds1.height()); |
| |
| // Verify that attempting to touch the second window with a second finger does |
| // nothing to the second window. The first window remains the window to be |
| // dragged. |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(original_bounds2.CenterPoint())); |
| generator->PressTouchId(kTouchId2); |
| dispatch_long_press(); |
| EXPECT_GT(item1->target_bounds().width(), original_bounds1.width()); |
| EXPECT_GT(item1->target_bounds().height(), original_bounds1.height()); |
| EXPECT_EQ(item2->target_bounds(), original_bounds2); |
| |
| // Verify the first window moves on drag. |
| gfx::PointF last_center_point = item1->target_bounds().CenterPoint(); |
| generator->MoveTouchIdBy(kTouchId1, 40, 40); |
| EXPECT_NE(last_center_point, item1->target_bounds().CenterPoint()); |
| EXPECT_EQ(original_bounds2.CenterPoint(), |
| item2->target_bounds().CenterPoint()); |
| |
| // Verify the first window moves on drag, even if we switch to a second |
| // finger. |
| last_center_point = item1->target_bounds().CenterPoint(); |
| generator->ReleaseTouchId(kTouchId2); |
| generator->PressTouchId(kTouchId2); |
| generator->MoveTouchIdBy(kTouchId2, 40, 40); |
| EXPECT_NE(last_center_point, item1->target_bounds().CenterPoint()); |
| EXPECT_EQ(original_bounds2.CenterPoint(), |
| item2->target_bounds().CenterPoint()); |
| } |
| |
| // Verify that shadows on windows disappear for the duration of overview mode. |
| TEST_P(OverviewSessionTest, ShadowDisappearsInOverview) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| |
| // Verify that the shadow is initially visible. |
| ::wm::ShadowController* shadow_controller = Shell::Get()->shadow_controller(); |
| EXPECT_TRUE(shadow_controller->IsShadowVisibleForWindow(window.get())); |
| |
| // Verify that the shadow is invisible after entering overview mode. |
| ToggleOverview(); |
| EXPECT_FALSE(shadow_controller->IsShadowVisibleForWindow(window.get())); |
| |
| // Verify that the shadow is visible again after exiting overview mode. |
| ToggleOverview(); |
| EXPECT_TRUE(shadow_controller->IsShadowVisibleForWindow(window.get())); |
| } |
| |
| // Verify that PIP windows will be excluded from the overview, but not hidden. |
| TEST_P(OverviewSessionTest, PipWindowShownButExcludedFromOverview) { |
| std::unique_ptr<aura::Window> pip_window( |
| CreateTestWindow(gfx::Rect(200, 200))); |
| WindowState* window_state = WindowState::Get(pip_window.get()); |
| const WMEvent enter_pip(WM_EVENT_PIP); |
| window_state->OnWMEvent(&enter_pip); |
| |
| // Enter overview. |
| ToggleOverview(); |
| |
| // PIP window should be visible but not in the overview. |
| EXPECT_TRUE(pip_window->IsVisible()); |
| EXPECT_FALSE(HighlightOverviewWindow(pip_window.get())); |
| } |
| |
| // Tests the PositionWindows function works as expected. |
| TEST_P(OverviewSessionTest, PositionWindows) { |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| const gfx::RectF bounds1 = item1->target_bounds(); |
| const gfx::RectF bounds2 = item2->target_bounds(); |
| const gfx::RectF bounds3 = item3->target_bounds(); |
| |
| // Verify that the bounds remain the same when calling PositionWindows again. |
| GetOverviewSession()->PositionWindows(/*animate=*/false); |
| EXPECT_EQ(bounds1, item1->target_bounds()); |
| EXPECT_EQ(bounds2, item2->target_bounds()); |
| EXPECT_EQ(bounds3, item3->target_bounds()); |
| |
| // Verify that |item2| and |item3| change bounds when calling PositionWindows |
| // while ignoring |item1|. |
| GetOverviewSession()->PositionWindows(/*animate=*/false, {item1}); |
| EXPECT_EQ(bounds1, item1->target_bounds()); |
| EXPECT_NE(bounds2, item2->target_bounds()); |
| EXPECT_NE(bounds3, item3->target_bounds()); |
| |
| // Return the windows to their original bounds. |
| GetOverviewSession()->PositionWindows(/*animate=*/false); |
| |
| // Verify that items that are animating before closing are ignored by |
| // PositionWindows. |
| item1->set_animating_to_close(true); |
| item2->set_animating_to_close(true); |
| GetOverviewSession()->PositionWindows(/*animate=*/false); |
| EXPECT_EQ(bounds1, item1->target_bounds()); |
| EXPECT_EQ(bounds2, item2->target_bounds()); |
| EXPECT_NE(bounds3, item3->target_bounds()); |
| } |
| |
| // Tests that overview mode is entered with kWindowDragged mode when a window is |
| // dragged from the top of the screen. For the purposes of this test, we use a |
| // browser window. |
| TEST_P(OverviewSessionTest, DraggingFromTopAnimation) { |
| EnterTabletMode(); |
| std::unique_ptr<views::Widget> widget(CreateTestWidget( |
| nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(200, 200))); |
| widget->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, 20); |
| |
| // Drag from the the top of the app to enter overview. |
| ui::GestureEvent event(0, 0, 0, base::TimeTicks(), |
| ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN)); |
| WindowState* window_state = WindowState::Get(widget->GetNativeWindow()); |
| window_state->CreateDragDetails(event.location_f(), HTCAPTION, |
| ::wm::WINDOW_MOVE_SOURCE_TOUCH); |
| auto drag_controller = std::make_unique<TabletModeWindowResizer>( |
| window_state, std::make_unique<TabletModeBrowserWindowDragDelegate>()); |
| ui::Event::DispatcherApi dispatch_helper(&event); |
| dispatch_helper.set_target(widget->GetNativeWindow()); |
| drag_controller->Drag(event.location_f(), event.flags()); |
| |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_EQ(OverviewEnterExitType::kImmediateEnter, |
| GetOverviewSession()->enter_exit_overview_type()); |
| } |
| |
| // Tests the grid bounds are as expected with different shelf auto hide |
| // behaviors and alignments. |
| TEST_P(OverviewSessionTest, GridBounds) { |
| UpdateDisplay("700x600"); |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(200, 200))); |
| |
| Shelf* shelf = GetPrimaryShelf(); |
| shelf->SetAlignment(ShelfAlignment::kBottom); |
| shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); |
| |
| // Test that with the bottom shelf, the grid should take up the entire display |
| // minus the shelf area on the bottom regardless of auto hide behavior. |
| const int shelf_size = ShelfConfig::Get()->shelf_size(); |
| ToggleOverview(); |
| EXPECT_EQ(gfx::Rect(0, 0, 700, 600 - shelf_size), GetGridBounds()); |
| ToggleOverview(); |
| |
| shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); |
| ToggleOverview(); |
| EXPECT_EQ(gfx::Rect(0, 0, 700, 600 - shelf_size), GetGridBounds()); |
| ToggleOverview(); |
| |
| // Test that with the right shelf, the grid should take up the entire display |
| // minus the shelf area on the right regardless of auto hide behavior. |
| shelf->SetAlignment(ShelfAlignment::kRight); |
| shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kNever); |
| ToggleOverview(); |
| EXPECT_EQ(gfx::Rect(0, 0, 700 - shelf_size, 600), GetGridBounds()); |
| ToggleOverview(); |
| |
| shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways); |
| ToggleOverview(); |
| EXPECT_EQ(gfx::Rect(0, 0, 700 - shelf_size, 600), GetGridBounds()); |
| ToggleOverview(); |
| } |
| |
| // Tests that windows that have a backdrop can still be tapped normally. |
| // Regression test for crbug.com/938645. |
| TEST_P(OverviewSessionTest, SelectingWindowWithBackdrop) { |
| std::unique_ptr<aura::Window> window(CreateTestWindow(gfx::Rect(500, 200))); |
| |
| ToggleOverview(); |
| OverviewItem* item = GetOverviewItemForWindow(window.get()); |
| ASSERT_EQ(OverviewGridWindowFillMode::kLetterBoxed, |
| item->GetWindowDimensionsType()); |
| |
| // Tap the target. |
| GetEventGenerator()->set_current_screen_location( |
| gfx::ToRoundedPoint(item->target_bounds().CenterPoint())); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| TEST_P(OverviewSessionTest, ShelfAlignmentChangeWhileInOverview) { |
| Shelf* shelf = GetPrimaryShelf(); |
| shelf->SetAlignment(ShelfAlignment::kBottom); |
| ToggleOverview(); |
| shelf->SetAlignment(ShelfAlignment::kRight); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| namespace { |
| class TestEventHandler : public ui::EventHandler { |
| public: |
| TestEventHandler() = default; |
| ~TestEventHandler() override = default; |
| // ui::EventHandler: |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| if (event->type() != ui::ET_KEY_PRESSED) |
| return; |
| |
| has_seen_event_ = true; |
| event->SetHandled(); |
| event->StopPropagation(); |
| } |
| bool HasSeenEvent() { return has_seen_event_; } |
| void Reset() { has_seen_event_ = false; } |
| |
| private: |
| bool has_seen_event_ = false; |
| }; |
| } // namespace |
| |
| // Test that keys are eaten when entering overview mode. |
| TEST_P(OverviewSessionTest, EatKeysDuringStartAnimation) { |
| std::unique_ptr<aura::Window> test_window(CreateTestWindow()); |
| TestEventHandler test_event_handler; |
| test_window->SetTargetHandler(&test_event_handler); |
| test_window->Focus(); |
| |
| ui::ScopedAnimationDurationScaleMode animation_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Keys shouldn't be eaten by overview session normally. |
| SendKey(ui::VKEY_A); |
| ASSERT_TRUE(test_window->HasFocus()); |
| EXPECT_TRUE(test_event_handler.HasSeenEvent()); |
| test_event_handler.Reset(); |
| |
| // Keys should be eaten by overview session when entering overview mode. |
| ToggleOverview(); |
| ASSERT_TRUE(Shell::Get()->overview_controller()->IsInStartAnimation()); |
| ASSERT_TRUE(test_window->HasFocus()); |
| SendKey(ui::VKEY_B); |
| EXPECT_FALSE(test_event_handler.HasSeenEvent()); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| WaitForOverviewEnterAnimation(); |
| ASSERT_FALSE(Shell::Get()->overview_controller()->IsInStartAnimation()); |
| EXPECT_FALSE(test_window->HasFocus()); |
| |
| ToggleOverview(); |
| SendKey(ui::VKEY_C); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_TRUE(test_event_handler.HasSeenEvent()); |
| } |
| |
| // Tests that in tablet mode, tapping on the background will go to home screen. |
| TEST_P(OverviewSessionTest, TapOnBackgroundGoToHome) { |
| EnterTabletMode(); |
| UpdateDisplay("800x600"); |
| std::unique_ptr<aura::Window> window(CreateTestWindow()); |
| WindowState* window_state = WindowState::Get(window.get()); |
| |
| EXPECT_FALSE(window_state->IsMinimized()); |
| EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible()); |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // Tap on the background. The tap location should be out of the tapping area |
| // for back gesture. Otherwise, the touch event will be consumed and no |
| // gesture event will be generated. |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| GetEventGenerator()->GestureTapAt( |
| gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset, 10)); |
| ShellTestApi().WaitForOverviewAnimationState( |
| OverviewAnimationState::kExitAnimationComplete); |
| |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_TRUE(window_state->IsMinimized()); |
| EXPECT_TRUE(Shell::Get()->app_list_controller()->IsHomeScreenVisible()); |
| } |
| |
| // Tests that in tablet mode, tapping on the background in split view mode will |
| // be no-op. |
| TEST_P(OverviewSessionTest, TapOnBackgroundInSplitView) { |
| EnterTabletMode(); |
| UpdateDisplay("800x600"); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| |
| EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible()); |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| GetSplitViewController()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| EXPECT_TRUE(GetSplitViewController()->InSplitViewMode()); |
| |
| // Tap on the background. |
| GetEventGenerator()->GestureTapAt(gfx::Point(10, 10)); |
| |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_FALSE(Shell::Get()->app_list_controller()->IsHomeScreenVisible()); |
| EXPECT_TRUE(GetSplitViewController()->InSplitViewMode()); |
| } |
| |
| // Tests starting the overview session using kFadeInEnter type. |
| TEST_P(OverviewSessionTest, FadeIn) { |
| EnterTabletMode(); |
| // Create a minimized window. |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| WindowState::Get(window.get())->Minimize(); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| ToggleOverview(OverviewEnterExitType::kFadeInEnter); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item = GetOverviewItemForWindow(window.get()); |
| |
| // Verify that the item widget's transform is not animated as part of the |
| // animation. |
| views::Widget* widget = item->item_widget(); |
| EXPECT_FALSE(widget->GetLayer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::TRANSFORM)); |
| |
| // Opacity should be animated to full opacity. |
| EXPECT_EQ(1.0f, widget->GetLayer()->GetTargetOpacity()); |
| EXPECT_TRUE(widget->GetLayer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::OPACITY)); |
| |
| // Validate item bounds are within the grid. |
| const gfx::Rect bounds = gfx::ToEnclosedRect(item->target_bounds()); |
| EXPECT_TRUE(GetGridBounds().Contains(bounds)); |
| |
| // Header is expected to be shown immediately. |
| EXPECT_EQ( |
| 1.0f, |
| item->overview_item_view()->header_view()->layer()->GetTargetOpacity()); |
| |
| EXPECT_EQ(OverviewEnterExitType::kFadeInEnter, |
| GetOverviewSession()->enter_exit_overview_type()); |
| } |
| |
| // Tests exiting the overview session using kFadeOutExit type. |
| TEST_P(OverviewSessionTest, FadeOutExit) { |
| EnterTabletMode(); |
| // Create a test window. |
| std::unique_ptr<views::Widget> test_widget(CreateTestWidget()); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_FALSE(test_widget->IsMinimized()); |
| |
| ui::ScopedAnimationDurationScaleMode test_duration_mode( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Grab the item widget before the session starts shutting down. The widget |
| // should outlive the session, at least until the animations are done - given |
| // that NON_ZERO_DURATION animation duration scale, it should be safe to |
| // dereference the widget pointer immediately (synchronously) after the |
| // session ends. |
| OverviewItem* item = GetOverviewItemForWindow(test_widget->GetNativeWindow()); |
| views::Widget* grid_item_widget = item->item_widget(); |
| gfx::Rect item_bounds = grid_item_widget->GetWindowBoundsInScreen(); |
| |
| ToggleOverview(OverviewEnterExitType::kFadeOutExit); |
| ASSERT_FALSE(InOverviewSession()); |
| |
| // The test window should be minimized as overview fade out exit starts. |
| EXPECT_TRUE(test_widget->IsMinimized()); |
| |
| // Verify that the item widget's transform is not animated as part of the |
| // animation, and that item widget bounds are not changed after minimizing the |
| // window. |
| EXPECT_FALSE(grid_item_widget->GetLayer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::TRANSFORM)); |
| EXPECT_EQ(item_bounds, grid_item_widget->GetWindowBoundsInScreen()); |
| |
| // Opacity should be animated to zero opacity. |
| EXPECT_EQ(0.0f, grid_item_widget->GetLayer()->GetTargetOpacity()); |
| EXPECT_TRUE(grid_item_widget->GetLayer()->GetAnimator()->IsAnimatingProperty( |
| ui::LayerAnimationElement::OPACITY)); |
| } |
| |
| // Tests that accessibility overrides are set as expected on overview related |
| // widgets. |
| TEST_P(OverviewSessionTest, AccessibilityFocusAnnotator) { |
| // TODO(crbug.com/1360638): The body of this test is only run when Desk |
| // Templates is turned OFF *and* Save & Recall is turned ON. Once the flag |
| // flip for Save & Recall has truly landed, remove the `NoSavedDesks` variant |
| // of this test below and remove the Save & Recall feature check at the start |
| // of this test. |
| if (GetParam() || !saved_desk_util::IsDeskSaveAndRecallEnabled()) |
| return; |
| |
| auto window3 = CreateTestWindow(gfx::Rect(100, 100)); |
| auto window2 = CreateTestWindow(gfx::Rect(100, 100)); |
| auto window1 = CreateTestWindow(gfx::Rect(100, 100)); |
| |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| auto* focus_widget = views::Widget::GetWidgetForNativeWindow( |
| GetOverviewSession()->GetOverviewFocusWindow()); |
| DCHECK(focus_widget); |
| |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget()); |
| DCHECK(desk_widget); |
| |
| SavedDeskSaveDeskButton* save_button = grid->GetSaveDeskForLaterButton(); |
| DCHECK(save_button); |
| auto* save_widget = save_button->GetWidget(); |
| |
| // Overview items are in MRU order, so the expected order in the grid list is |
| // the reverse creation order. |
| auto* item_widget1 = GetOverviewItemForWindow(window1.get())->item_widget(); |
| auto* item_widget2 = GetOverviewItemForWindow(window2.get())->item_widget(); |
| auto* item_widget3 = GetOverviewItemForWindow(window3.get())->item_widget(); |
| |
| // Order should be [focus_widget, item_widget1, item_widget2, item_widget3, |
| // desk_widget, save_widget]. |
| CheckA11yOverrides("focus", focus_widget, save_widget, item_widget1); |
| CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget2); |
| CheckA11yOverrides("item2", item_widget2, item_widget1, item_widget3); |
| CheckA11yOverrides("item3", item_widget3, item_widget2, desk_widget); |
| CheckA11yOverrides("desk", desk_widget, item_widget3, save_widget); |
| CheckA11yOverrides("save", save_widget, desk_widget, focus_widget); |
| |
| // Remove |window2|. The new order should be [focus_widget, item_widget1, |
| // item_widget3, desk_widget, save_widget]. |
| window2.reset(); |
| CheckA11yOverrides("focus", focus_widget, save_widget, item_widget1); |
| CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget3); |
| CheckA11yOverrides("item3", item_widget3, item_widget1, desk_widget); |
| CheckA11yOverrides("desk", desk_widget, item_widget3, save_widget); |
| CheckA11yOverrides("save", save_widget, desk_widget, focus_widget); |
| } |
| |
| // Tests that accessibility overrides are set as expected on overview related |
| // widgets. |
| TEST_P(OverviewSessionTest, AccessibilityFocusAnnotatorNoSavedDesks) { |
| // If desks templates is enabled, the a11y order changes. This is tested in |
| // the desks templates test suite. |
| if (GetParam() || saved_desk_util::IsDeskSaveAndRecallEnabled()) |
| return; |
| |
| auto window3 = CreateTestWindow(gfx::Rect(100, 100)); |
| auto window2 = CreateTestWindow(gfx::Rect(100, 100)); |
| auto window1 = CreateTestWindow(gfx::Rect(100, 100)); |
| |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| |
| auto* focus_widget = views::Widget::GetWidgetForNativeWindow( |
| GetOverviewSession()->GetOverviewFocusWindow()); |
| DCHECK(focus_widget); |
| |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| auto* desk_widget = const_cast<views::Widget*>(grid->desks_widget()); |
| DCHECK(desk_widget); |
| |
| // Overview items are in MRU order, so the expected order in the grid list is |
| // the reverse creation order. |
| auto* item_widget1 = GetOverviewItemForWindow(window1.get())->item_widget(); |
| auto* item_widget2 = GetOverviewItemForWindow(window2.get())->item_widget(); |
| auto* item_widget3 = GetOverviewItemForWindow(window3.get())->item_widget(); |
| |
| // Order should be [focus_widget, item_widget1, item_widget2, item_widget3, |
| // desk_widget]. |
| CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1); |
| CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget2); |
| CheckA11yOverrides("item2", item_widget2, item_widget1, item_widget3); |
| CheckA11yOverrides("item3", item_widget3, item_widget2, desk_widget); |
| CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget); |
| |
| // Remove |window2|. The new order should be [focus_widget, item_widget1, |
| // item_widget3, desk_widget]. |
| window2.reset(); |
| CheckA11yOverrides("focus", focus_widget, desk_widget, item_widget1); |
| CheckA11yOverrides("item1", item_widget1, focus_widget, item_widget3); |
| CheckA11yOverrides("item3", item_widget3, item_widget1, desk_widget); |
| CheckA11yOverrides("desk", desk_widget, item_widget3, focus_widget); |
| } |
| |
| // Tests that removing a transient child during overview does not result in a |
| // crash when exiting overview. |
| TEST_P(OverviewSessionTest, RemoveTransientNoCrash) { |
| auto child = CreateTestWindow(); |
| auto parent = CreateTestWindow(); |
| wm::AddTransientChild(parent.get(), child.get()); |
| |
| ToggleOverview(); |
| wm::RemoveTransientChild(parent.get(), child.get()); |
| ToggleOverview(); |
| } |
| |
| // Tests that closing the overview item destroys the entire transient tree. Note |
| // that closing does not destroy transient children which are ShellSurfaceBase, |
| // but this test covers the regular case. |
| TEST_P(OverviewSessionTest, ClosingTransientTree) { |
| // Release ownership as it will get deleted by the transient window manager, |
| // when the associated overview item is closed later. |
| auto* window = CreateAppWindow().release(); |
| |
| auto* child_window1 = CreateAppWindow().release(); |
| wm::AddTransientChild(window, child_window1); |
| |
| // Add a second child that is not backed by a widget. |
| auto* child_window2 = CreateTestWindow().release(); |
| wm::AddTransientChild(window, child_window2); |
| |
| TestDestroyedWidgetObserver widget_observer( |
| views::Widget::GetWidgetForNativeWindow(window)); |
| TestDestroyedWidgetObserver child_widget_observer( |
| views::Widget::GetWidgetForNativeWindow(child_window1)); |
| |
| ToggleOverview(); |
| |
| // There is a uaf that happens after adding a new desk and removing a desk, |
| // which transfers all windows to the new desk, removes the OverviewItem for |
| // the window and then adds a new `OverviewItem` for the window. We replicate |
| // that over here. See crbug.com/1317875. |
| auto* controller = DesksController::Get(); |
| controller->NewDesk(DesksCreationRemovalSource::kKeyboard); |
| RemoveDesk(controller->active_desk(), DeskCloseType::kCombineDesks); |
| |
| OverviewItem* item = GetOverviewItemForWindow(window); |
| ASSERT_TRUE(item); |
| item->CloseWindow(); |
| |
| // `NativeWidgetAura::Close()` fires a post task. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(widget_observer.widget_destroyed()); |
| EXPECT_TRUE(child_widget_observer.widget_destroyed()); |
| } |
| |
| TEST_P(OverviewSessionTest, FrameThrottlingBrowser) { |
| FrameThrottlingController* frame_throttling_controller = |
| Shell::Get()->frame_throttling_controller(); |
| const int window_count = 5; |
| std::vector<viz::FrameSinkId> ids{ |
| {1u, 1u}, {2u, 2u}, {3u, 3u}, {4u, 4u}, {5u, 5u}}; |
| std::vector<std::unique_ptr<aura::Window>> windows; |
| windows.reserve(window_count + 1); |
| for (int i = 0; i < window_count; ++i) { |
| windows.emplace_back( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| windows[i]->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| windows[i]->SetEmbedFrameSinkId(ids[i]); |
| } |
| |
| ToggleOverview(); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| |
| // Add a new window to overview. |
| std::unique_ptr<aura::Window> new_window( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| constexpr viz::FrameSinkId new_window_id{6u, 6u}; |
| new_window->SetEmbedFrameSinkId(new_window_id); |
| new_window->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::BROWSER)); |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false, |
| /*use_spawn_animation=*/false); |
| windows.push_back(std::move(new_window)); |
| ids.push_back(new_window_id); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| |
| // Remove windows one by one. |
| for (int i = 0; i < window_count + 1; ++i) { |
| aura::Window* window = windows[i].get(); |
| ids.erase(ids.begin()); |
| OverviewItem* item = grid->GetOverviewItemContaining(window); |
| grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| } |
| } |
| |
| TEST_P(OverviewSessionTest, FrameThrottlingLacros) { |
| FrameThrottlingController* frame_throttling_controller = |
| Shell::Get()->frame_throttling_controller(); |
| const int window_count = 5; |
| std::vector<viz::FrameSinkId> ids{ |
| {1u, 1u}, {2u, 2u}, {3u, 3u}, {4u, 4u}, {5u, 5u}}; |
| std::vector<std::unique_ptr<aura::Window>> windows; |
| windows.reserve(window_count + 1); |
| for (int i = 0; i < window_count; ++i) { |
| windows.emplace_back( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| windows[i]->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::LACROS)); |
| windows[i]->SetEmbedFrameSinkId(ids[i]); |
| } |
| for (auto& w : windows) |
| EXPECT_FALSE(w->GetProperty(ash::kFrameRateThrottleKey)); |
| |
| ToggleOverview(); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| for (auto& w : windows) |
| EXPECT_TRUE(w->GetProperty(ash::kFrameRateThrottleKey)); |
| |
| // Add a new window to overview. |
| std::unique_ptr<aura::Window> new_window( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| constexpr viz::FrameSinkId new_window_id{6u, 6u}; |
| new_window->SetEmbedFrameSinkId(new_window_id); |
| new_window->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::LACROS)); |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false, |
| /*use_spawn_animation=*/false); |
| windows.push_back(std::move(new_window)); |
| ids.push_back(new_window_id); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| for (auto& w : windows) |
| EXPECT_TRUE(w->GetProperty(ash::kFrameRateThrottleKey)); |
| |
| // Remove windows one by one. |
| for (int i = 0; i < window_count + 1; ++i) { |
| aura::Window* window = windows[i].get(); |
| ids.erase(ids.begin()); |
| OverviewItem* item = grid->GetOverviewItemContaining(window); |
| grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false); |
| EXPECT_THAT(frame_throttling_controller->GetFrameSinkIdsToThrottle(), |
| testing::UnorderedElementsAreArray(ids)); |
| EXPECT_FALSE(window->GetProperty(ash::kFrameRateThrottleKey)); |
| } |
| } |
| |
| TEST_P(OverviewSessionTest, FrameThrottlingArc) { |
| testing::NiceMock<MockFrameThrottlingObserver> observer; |
| FrameThrottlingController* frame_throttling_controller = |
| Shell::Get()->frame_throttling_controller(); |
| uint8_t throttled_fps = frame_throttling_controller->throttled_fps(); |
| frame_throttling_controller->AddArcObserver(&observer); |
| |
| const int window_count = 5; |
| std::vector<std::unique_ptr<aura::Window>> windows; |
| windows.reserve(window_count + 1); |
| for (int i = 0; i < window_count; ++i) { |
| windows.emplace_back( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| windows[i]->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::ARC_APP)); |
| } |
| |
| std::vector<aura::Window*> windows_to_throttle(window_count, nullptr); |
| std::transform(windows.begin(), windows.end(), windows_to_throttle.begin(), |
| [](std::unique_ptr<aura::Window>& w) { return w.get(); }); |
| EXPECT_CALL(observer, OnThrottlingStarted(testing::UnorderedElementsAreArray( |
| windows_to_throttle), |
| throttled_fps)); |
| ToggleOverview(); |
| |
| // Add a new window to overview. |
| std::unique_ptr<aura::Window> new_window( |
| CreateTestWindowInShellWithDelegate(nullptr, -1, gfx::Rect())); |
| new_window->SetProperty(aura::client::kAppType, |
| static_cast<int>(AppType::ARC_APP)); |
| windows_to_throttle.push_back(new_window.get()); |
| EXPECT_CALL(observer, OnThrottlingEnded()); |
| EXPECT_CALL(observer, OnThrottlingStarted(testing::UnorderedElementsAreArray( |
| windows_to_throttle), |
| throttled_fps)); |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| grid->AppendItem(new_window.get(), /*reposition=*/false, /*animate=*/false, |
| /*use_spawn_animation=*/false); |
| windows.push_back(std::move(new_window)); |
| |
| // Remove windows one by one. Once one window is out of the overview grid, no |
| // more windows will be throttled. |
| for (int i = 0; i < window_count + 1; ++i) { |
| aura::Window* window = windows[i].get(); |
| if (i == 0) |
| EXPECT_CALL(observer, OnThrottlingEnded()); |
| EXPECT_CALL(observer, OnThrottlingStarted(testing::_, testing::_)).Times(0); |
| OverviewItem* item = grid->GetOverviewItemContaining(window); |
| grid->RemoveItem(item, /*item_destroying=*/false, /*reposition=*/false); |
| } |
| frame_throttling_controller->RemoveArcObserver(&observer); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, OverviewSessionTest, testing::Bool()); |
| |
| class TabletModeOverviewSessionTest : public OverviewTestBase { |
| public: |
| TabletModeOverviewSessionTest() = default; |
| ~TabletModeOverviewSessionTest() override = default; |
| |
| TabletModeOverviewSessionTest(const TabletModeOverviewSessionTest&) = delete; |
| TabletModeOverviewSessionTest& operator=( |
| const TabletModeOverviewSessionTest&) = delete; |
| |
| // OverviewTestBase: |
| void SetUp() override { |
| OverviewTestBase::SetUp(); |
| EnterTabletMode(); |
| } |
| |
| SplitViewController* split_view_controller() { |
| return SplitViewController::Get(Shell::GetPrimaryRootWindow()); |
| } |
| |
| protected: |
| void GenerateScrollSequence(const gfx::Point& start, const gfx::Point& end) { |
| GetEventGenerator()->GestureScrollSequence(start, end, |
| base::Milliseconds(100), 1000); |
| } |
| |
| void DispatchLongPress(OverviewItem* item) { |
| ui::TouchEvent long_press( |
| ui::ET_GESTURE_LONG_PRESS, |
| gfx::ToRoundedPoint(item->target_bounds().CenterPoint()), |
| base::TimeTicks::Now(), |
| ui::PointerDetails(ui::EventPointerType::kTouch)); |
| GetEventGenerator()->Dispatch(&long_press); |
| } |
| |
| // Creates |n| test windows. They are created in reverse order, so that the |
| // first window in the vector is the MRU window. |
| std::vector<std::unique_ptr<aura::Window>> CreateTestWindows(int n) { |
| std::vector<std::unique_ptr<aura::Window>> windows(n); |
| for (int i = n - 1; i >= 0; --i) |
| windows[i] = CreateTestWindow(); |
| return windows; |
| } |
| }; |
| |
| // Tests that windows are in proper positions in the new overview layout. |
| TEST_F(TabletModeOverviewSessionTest, CheckNewLayoutWindowPositions) { |
| auto windows = CreateTestWindows(6); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(windows[0].get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(windows[1].get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(windows[2].get()); |
| OverviewItem* item4 = GetOverviewItemForWindow(windows[3].get()); |
| |
| const gfx::RectF item1_bounds = item1->target_bounds(); |
| const gfx::RectF item2_bounds = item2->target_bounds(); |
| const gfx::RectF item3_bounds = item3->target_bounds(); |
| const gfx::RectF item4_bounds = item4->target_bounds(); |
| |
| // |window1| should be in the top left position. |window2| should be directly |
| // below |window1|, thus sharing the same x-value but not the same y-value. |
| EXPECT_EQ(item1_bounds.x(), item2_bounds.x()); |
| EXPECT_LT(item1_bounds.y(), item2_bounds.y()); |
| // |window3| should be directly right of |window1|, thus sharing the same |
| // y-value, but not the same x-value. |
| EXPECT_LT(item1_bounds.x(), item3_bounds.x()); |
| EXPECT_EQ(item1_bounds.y(), item3_bounds.y()); |
| // |window4| should be directly right of |window2| and directly below |
| // |window3|. |
| EXPECT_LT(item2_bounds.x(), item4_bounds.x()); |
| EXPECT_EQ(item2_bounds.y(), item4_bounds.y()); |
| EXPECT_EQ(item3_bounds.x(), item4_bounds.x()); |
| EXPECT_LT(item3_bounds.y(), item4_bounds.y()); |
| } |
| |
| TEST_F(TabletModeOverviewSessionTest, CheckOffscreenWindows) { |
| auto windows = CreateTestWindows(8); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item0 = GetOverviewItemForWindow(windows[0].get()); |
| OverviewItem* item1 = GetOverviewItemForWindow(windows[1].get()); |
| OverviewItem* item6 = GetOverviewItemForWindow(windows[6].get()); |
| OverviewItem* item7 = GetOverviewItemForWindow(windows[7].get()); |
| |
| const gfx::RectF screen_bounds(GetGridBounds()); |
| const gfx::RectF item0_bounds = item0->target_bounds(); |
| const gfx::RectF item1_bounds = item1->target_bounds(); |
| const gfx::RectF item6_bounds = item6->target_bounds(); |
| const gfx::RectF item7_bounds = item7->target_bounds(); |
| |
| // |item6| should be in the same row of windows as |item0|, but offscreen |
| // (one screen length away). |
| EXPECT_FALSE(screen_bounds.Contains(item6_bounds)); |
| EXPECT_EQ(item0_bounds.y(), item6_bounds.y()); |
| // |item7| should be in the same row of windows as |item1|, but offscreen |
| // and below |item6|. |
| EXPECT_FALSE(screen_bounds.Contains(item7_bounds)); |
| EXPECT_EQ(item1_bounds.y(), item7_bounds.y()); |
| EXPECT_LT(item6_bounds.y(), item7_bounds.y()); |
| } |
| |
| // Tests to see if windows are not shifted if all already available windows |
| // fit on screen. |
| TEST_F(TabletModeOverviewSessionTest, CheckNoOverviewItemShift) { |
| auto windows = CreateTestWindows(4); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item0 = GetOverviewItemForWindow(windows[0].get()); |
| const gfx::RectF before_shift_bounds = item0->target_bounds(); |
| |
| GenerateScrollSequence(gfx::Point(100, 60), gfx::Point(0, 50)); |
| EXPECT_EQ(before_shift_bounds, item0->target_bounds()); |
| } |
| |
| // Tests to see if windows are shifted if at least one window is |
| // partially/completely positioned offscreen. |
| TEST_F(TabletModeOverviewSessionTest, CheckOverviewItemShift) { |
| auto windows = CreateTestWindows(7); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item0 = GetOverviewItemForWindow(windows[0].get()); |
| const gfx::RectF before_shift_bounds = item0->target_bounds(); |
| |
| GenerateScrollSequence(gfx::Point(100, 60), gfx::Point(0, 50)); |
| EXPECT_LT(item0->target_bounds(), before_shift_bounds); |
| } |
| |
| // Tests to see if windows remain in bounds after scrolling extremely far. |
| TEST_F(TabletModeOverviewSessionTest, CheckOverviewItemScrollingBounds) { |
| auto windows = CreateTestWindows(8); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // Scroll an extreme amount to see if windows on the far left are still in |
| // bounds. First, align the left-most window (|windows[0]|) to the left-hand |
| // bound and store the item's location. Then, scroll a far amount and check to |
| // see if the item moved at all. |
| OverviewItem* leftmost_window = GetOverviewItemForWindow(windows[0].get()); |
| |
| GenerateScrollSequence( |
| gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset + 5, 50), |
| gfx::Point(5000, 50)); |
| const gfx::RectF left_bounds = leftmost_window->target_bounds(); |
| GenerateScrollSequence( |
| gfx::Point(BackGestureEventHandler::kStartGoingBackLeftEdgeInset + 5, 50), |
| gfx::Point(5000, 50)); |
| EXPECT_EQ(left_bounds, leftmost_window->target_bounds()); |
| |
| // Scroll an extreme amount to see if windows on the far right are still in |
| // bounds. First, align the right-most window (|windows[7]|) to the right-hand |
| // bound and store the item's location. Then, scroll a far amount and check to |
| // see if the item moved at all. |
| OverviewItem* rightmost_window = GetOverviewItemForWindow(windows[7].get()); |
| GenerateScrollSequence(gfx::Point(5000, 50), gfx::Point(0, 50)); |
| const gfx::RectF right_bounds = rightmost_window->target_bounds(); |
| GenerateScrollSequence(gfx::Point(5000, 50), gfx::Point(0, 50)); |
| EXPECT_EQ(right_bounds, rightmost_window->target_bounds()); |
| } |
| |
| // Tests that destroying a window does not cause a crash while scrolling the |
| // overview grid. Regression test for https://crbug.com/1200605. |
| TEST_F(TabletModeOverviewSessionTest, WindowDestroyWhileScrolling) { |
| auto windows = CreateTestWindows(8); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // Start a scroll sequence. |
| int x = 500; |
| const int y = 200; |
| base::TimeTicks timestamp = ui::EventTimeForNow(); |
| auto* event_generator = GetEventGenerator(); |
| ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(x, y), timestamp, |
| ui::PointerDetails()); |
| event_generator->Dispatch(&press); |
| |
| // Scroll a bit to the left, so the overview items that are offscreen on the |
| // right start to become visible. |
| const base::TimeDelta step_delay = base::Milliseconds(5); |
| for (int i = 0; i < 10; ++i) { |
| timestamp += step_delay; |
| ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(x, y), timestamp, |
| ui::PointerDetails()); |
| event_generator->Dispatch(&move); |
| x -= 5; |
| } |
| |
| // Delete one of the windows. |
| base::Erase(windows, windows[2]); |
| |
| // Continue scrolling and then end the scroll. There should be no crash. |
| for (int i = 0; i < 10; ++i) { |
| timestamp += step_delay; |
| ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(x, y), timestamp, |
| ui::PointerDetails()); |
| event_generator->Dispatch(&move); |
| x -= 5; |
| } |
| |
| ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(x, y), timestamp, |
| ui::PointerDetails()); |
| event_generator->Dispatch(&release); |
| } |
| |
| // Tests the windows are stacked correctly when entering or exiting splitview |
| // while the new overivew layout is enabled. |
| TEST_F(TabletModeOverviewSessionTest, StackingOrderSplitviewWindow) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateUnsnappableWindow(); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // Snap |window1| to the left and exit overview. |window3| should have higher |
| // z-order now, since it is the MRU window. |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| ToggleOverview(); |
| ASSERT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| ASSERT_GT(IndexOf(window3.get(), window3->parent()), |
| IndexOf(window1.get(), window1->parent())); |
| |
| // Test that on entering overview, |window3| is of a lower z-order, so that |
| // when we scroll the grid, it will be seen under |window1|. |
| ToggleOverview(); |
| EXPECT_LT(IndexOf(window3.get(), window3->parent()), |
| IndexOf(window1.get(), window1->parent())); |
| |
| // Test that |window2| has a cannot snap widget indicating that it cannot be |
| // snapped, and that both |window2| and the widget are lower z-order than |
| // |window1|. |
| views::Widget* cannot_snap_widget = |
| static_cast<views::Widget*>(GetOverviewItemForWindow(window2.get()) |
| ->cannot_snap_widget_for_testing()); |
| ASSERT_TRUE(cannot_snap_widget); |
| aura::Window* cannot_snap_window = cannot_snap_widget->GetNativeWindow(); |
| ASSERT_EQ(window1->parent(), cannot_snap_window->parent()); |
| EXPECT_LT(IndexOf(window2.get(), window2->parent()), |
| IndexOf(window1.get(), window1->parent())); |
| EXPECT_LT(IndexOf(cannot_snap_window, cannot_snap_window->parent()), |
| IndexOf(window1.get(), window1->parent())); |
| |
| // Test that on exiting overview, |window3| becomes activated, so it returns |
| // to being higher on the z-order than |window1|. |
| ToggleOverview(); |
| EXPECT_GT(IndexOf(window3.get(), window3->parent()), |
| IndexOf(window1.get(), window1->parent())); |
| } |
| |
| // Tests the windows are remain stacked underneath the split view window after |
| // dragging or long pressing. |
| TEST_F(TabletModeOverviewSessionTest, StackingOrderAfterGestureEvent) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| |
| // Tests that if we long press, but cancel the event, the window stays stacked |
| // under the snapped window. |
| OverviewItem* item = GetOverviewItemForWindow(window2.get()); |
| const gfx::PointF item_center = item->target_bounds().CenterPoint(); |
| DispatchLongPress(item); |
| ui::GestureEvent gesture_end(item_center.x(), item_center.y(), 0, |
| ui::EventTimeForNow(), |
| ui::GestureEventDetails(ui::ET_GESTURE_END)); |
| item->HandleGestureEvent(&gesture_end); |
| EXPECT_GT(IndexOf(window1.get(), window1->parent()), |
| IndexOf(window2.get(), window2->parent())); |
| |
| // Tests that if we drag the window around, then release, the window also |
| // stays stacked under the snapped window. |
| ASSERT_TRUE(InOverviewSession()); |
| const gfx::Vector2dF delta(15.f, 15.f); |
| DispatchLongPress(item); |
| GetOverviewSession()->Drag(item, item_center + delta); |
| GetOverviewSession()->CompleteDrag(item, item_center + delta); |
| EXPECT_GT(IndexOf(window1.get(), window1->parent()), |
| IndexOf(window2.get(), window2->parent())); |
| } |
| |
| // Test that scrolling occurs if started on top of a window using the window's |
| // center-point as a start. |
| TEST_F(TabletModeOverviewSessionTest, HorizontalScrollingOnOverviewItem) { |
| auto windows = CreateTestWindows(8); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* leftmost_window = GetOverviewItemForWindow(windows[0].get()); |
| const gfx::Point topleft_window_center = |
| gfx::ToRoundedPoint(leftmost_window->target_bounds().CenterPoint()); |
| const gfx::RectF left_bounds = leftmost_window->target_bounds(); |
| |
| GenerateScrollSequence(topleft_window_center, gfx::Point(-500, 50)); |
| EXPECT_LT(leftmost_window->target_bounds(), left_bounds); |
| } |
| |
| // Tests that dragging a fullscreened window to snap in overview does not result |
| // in a u-a-f. Regression test for crbug.com/1330042. |
| TEST_F(TabletModeOverviewSessionTest, SnappingFullscreenWindow) { |
| UpdateDisplay("800x600"); |
| |
| auto window = CreateAppWindow(gfx::Rect(300, 300)); |
| |
| const WMEvent fullscreen_event(WM_EVENT_FULLSCREEN); |
| WindowState::Get(window.get())->OnWMEvent(&fullscreen_event); |
| EXPECT_TRUE(WindowState::Get(window.get())->IsFullscreen()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* item = GetOverviewItemForWindow(window.get()); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(item->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| generator->MoveMouseTo(gfx::Point(10, 300)); |
| generator->ReleaseLeftButton(); |
| |
| EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped()); |
| } |
| |
| // A unique test class for testing flings in overview as those rely on observing |
| // compositor animations which require a mock time task environment. |
| class OverviewSessionFlingTest : public AshTestBase { |
| public: |
| OverviewSessionFlingTest() |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| ~OverviewSessionFlingTest() override = default; |
| |
| OverviewSessionFlingTest(const OverviewSessionFlingTest&) = delete; |
| OverviewSessionFlingTest& operator=(const OverviewSessionFlingTest&) = delete; |
| |
| // AshTestBase: |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| |
| // Overview flinging is only available in tablet mode. |
| base::RunLoop().RunUntilIdle(); |
| TabletModeControllerTestApi().EnterTabletMode(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| }; |
| |
| TEST_F(OverviewSessionFlingTest, BasicFling) { |
| std::vector<std::unique_ptr<aura::Window>> windows(16); |
| for (int i = 15; i >= 0; --i) |
| windows[i] = CreateTestWindow(); |
| |
| ToggleOverview(); |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| OverviewGridEventHandler* grid_event_handler = grid->grid_event_handler(); |
| |
| OverviewItem* item = GetOverviewItemForWindow(windows[2].get()); |
| const gfx::Point item_center = |
| gfx::ToRoundedPoint(item->target_bounds().CenterPoint()); |
| |
| // Create a scroll sequence which results in a fling. |
| const gfx::Vector2d shift(-200, 0); |
| GetEventGenerator()->GestureScrollSequence(item_center, item_center + shift, |
| base::Milliseconds(10), 10); |
| |
| ui::Compositor* const compositor = |
| windows[0]->GetRootWindow()->layer()->GetCompositor(); |
| ui::DrawWaiterForTest::WaitForCompositingStarted(compositor); |
| ASSERT_TRUE(grid_event_handler->IsFlingInProgressForTesting()); |
| |
| // Test that the scroll offset decreases as we advance the clock. Check the |
| // scroll offset instead of the item bounds as there is an optimization which |
| // does not update the item bounds of invisible elements. On some iterations, |
| // there may not be enough time passed to decay the velocity so the scroll |
| // offset will not change, but the overall change should be substantial. |
| constexpr int kMaxLoops = 10; |
| const float initial_scroll_offset = grid->scroll_offset(); |
| float previous_scroll_offset = initial_scroll_offset; |
| for (int i = 0; |
| i < kMaxLoops && grid_event_handler->IsFlingInProgressForTesting(); |
| ++i) { |
| task_environment()->FastForwardBy(base::Milliseconds(50)); |
| ui::DrawWaiterForTest::WaitForCompositingStarted(compositor); |
| |
| float scroll_offset = grid->scroll_offset(); |
| EXPECT_LE(scroll_offset, previous_scroll_offset); |
| previous_scroll_offset = scroll_offset; |
| } |
| |
| EXPECT_LT(grid->scroll_offset(), initial_scroll_offset - 100.f); |
| } |
| |
| // Tests that a vertical scroll sequence will close the window it is scrolled |
| // on. |
| TEST_F(TabletModeOverviewSessionTest, VerticalScrollingOnOverviewItem) { |
| constexpr int kNumWidgets = 8; |
| std::vector<std::unique_ptr<views::Widget>> widgets(kNumWidgets); |
| for (int i = kNumWidgets - 1; i >= 0; --i) |
| widgets[i] = CreateTestWidget(); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* leftmost_window = |
| GetOverviewItemForWindow(widgets[0]->GetNativeWindow()); |
| const gfx::Point topleft_window_center = |
| gfx::ToRoundedPoint(leftmost_window->target_bounds().CenterPoint()); |
| const gfx::Point end_point = topleft_window_center - gfx::Vector2d(0, 300); |
| |
| GenerateScrollSequence(topleft_window_center, end_point); |
| EXPECT_TRUE(widgets[0]->IsClosed()); |
| } |
| |
| // Test that scrolling occurs if we hit the associated keyboard shortcut. |
| TEST_F(TabletModeOverviewSessionTest, CheckScrollingWithKeyboardShortcut) { |
| auto windows = CreateTestWindows(8); |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| OverviewItem* leftmost_window = GetOverviewItemForWindow(windows[0].get()); |
| const gfx::RectF left_bounds = leftmost_window->target_bounds(); |
| |
| SendKey(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN); |
| EXPECT_LT(leftmost_window->target_bounds(), left_bounds); |
| } |
| |
| // Test that tapping a window in overview closes overview mode. |
| TEST_F(TabletModeOverviewSessionTest, CheckWindowActivateOnTap) { |
| base::UserActionTester user_action_tester; |
| auto windows = CreateTestWindows(8); |
| wm::ActivateWindow(windows[1].get()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| |
| // Tap on |windows[1]| to exit overview. |
| GetEventGenerator()->GestureTapAt( |
| GetTransformedTargetBounds(windows[1].get()).CenterPoint()); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| |
| // |windows[1]| remains active. Click on it to exit overview. |
| ASSERT_EQ(windows[1].get(), window_util::GetFocusedWindow()); |
| ToggleOverview(); |
| ClickWindow(windows[1].get()); |
| EXPECT_EQ( |
| 0, user_action_tester.GetActionCount(kActiveWindowChangedFromOverview)); |
| } |
| |
| TEST_F(TabletModeOverviewSessionTest, LayoutValidAfterRotation) { |
| UpdateDisplay("1366x768"); |
| display::test::ScopedSetInternalDisplayId set_internal( |
| Shell::Get()->display_manager(), |
| display::Screen::GetScreen()->GetPrimaryDisplay().id()); |
| auto windows = CreateTestWindows(7); |
| |
| // Helper to determine whether a grid layout is valid. It is considered valid |
| // if the left edge of the first item is close enough to the left edge of the |
| // grid bounds and if the right edge of the last item is close enough to the |
| // right edge of the grid bounds. Either of these being false would mean there |
| // is a large padding which shouldn't be there. |
| auto layout_valid = [&windows, this](int expected_padding) { |
| OverviewItem* first_item = GetOverviewItemForWindow(windows.front().get()); |
| OverviewItem* last_item = GetOverviewItemForWindow(windows.back().get()); |
| |
| const gfx::Rect first_bounds = |
| gfx::ToEnclosedRect(first_item->target_bounds()); |
| const gfx::Rect last_bounds = |
| gfx::ToEnclosedRect(last_item->target_bounds()); |
| |
| const gfx::Rect grid_bounds = GetGridBounds(); |
| const bool first_bounds_valid = |
| first_bounds.x() <= (grid_bounds.x() + expected_padding); |
| const bool last_bounds_valid = |
| last_bounds.right() >= (grid_bounds.right() - expected_padding); |
| return first_bounds_valid && last_bounds_valid; |
| }; |
| |
| // Enter overview and scroll to the edge of the grid. The layout should remain |
| // valid. |
| ToggleOverview(); |
| ASSERT_TRUE(InOverviewSession()); |
| // The expected padding should be the x position of the first item, before the |
| // grid gets shifted. |
| const int expected_padding = |
| GetOverviewItemForWindow(windows.front().get())->target_bounds().x(); |
| GenerateScrollSequence(gfx::Point(1300, 10), gfx::Point(100, 10)); |
| EXPECT_TRUE(layout_valid(expected_padding)); |
| |
| // Tests that the layout is still valid after a couple rotations. |
| ScreenOrientationControllerTestApi test_api( |
| Shell::Get()->screen_orientation_controller()); |
| test_api.SetDisplayRotation(display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_TRUE(layout_valid(expected_padding)); |
| |
| test_api.SetDisplayRotation(display::Display::ROTATE_180, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_TRUE(layout_valid(expected_padding)); |
| } |
| |
| // Tests that windows snap through long press and drag to left or right side of |
| // the screen. |
| TEST_F(TabletModeOverviewSessionTest, DragOverviewWindowToSnap) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow(bounds)); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| ASSERT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // Dispatches a long press event at the |overview_item1|'s current location to |
| // start dragging in SplitView. Drags |overview_item1| to the left border of |
| // the screen. SplitView should trigger and upon completing drag, |
| // |overview_item1| should snap to the left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| const gfx::PointF snap_left_location = |
| gfx::PointF(GetGridBounds().left_center()); |
| |
| DispatchLongPress(overview_item1); |
| GetOverviewSession()->Drag( |
| overview_item1, |
| gfx::PointF(overview_item1->target_bounds().left_center())); |
| GetOverviewSession()->CompleteDrag(overview_item1, snap_left_location); |
| |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| |
| // Dispatches a long press event at the |overview_item2|'s current location to |
| // start dragging in SplitView. Drags |overview_item2| to the right border of |
| // the screen. Upon completing drag, |overview_item2| should snap to the |
| // right. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| const gfx::PointF snap_right_location = |
| gfx::PointF(GetGridBounds().right_center()); |
| |
| DispatchLongPress(overview_item2); |
| GetOverviewSession()->Drag( |
| overview_item2, |
| gfx::PointF(overview_item2->target_bounds().right_center())); |
| GetOverviewSession()->CompleteDrag(overview_item2, snap_right_location); |
| |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window2.get()); |
| } |
| |
| // Verify that if the window item has been dragged enough vertically, the window |
| // will be closed. |
| TEST_F(TabletModeOverviewSessionTest, DragToClose) { |
| // This test requires a widget. |
| std::unique_ptr<views::Widget> widget(CreateTestWidget()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* item = GetOverviewItemForWindow(widget->GetNativeWindow()); |
| const gfx::PointF start = item->target_bounds().CenterPoint(); |
| ASSERT_TRUE(item); |
| |
| // This drag has not covered enough distance, so the widget is not closed and |
| // we remain in overview mode. |
| GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 80)); |
| GetOverviewSession()->CompleteDrag(item, start + gfx::Vector2dF(0, 80)); |
| ASSERT_TRUE(GetOverviewSession()); |
| |
| // Verify that the second drag has enough vertical distance, so the widget |
| // will be closed and overview mode will be exited. |
| GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 180)); |
| GetOverviewSession()->CompleteDrag(item, start + gfx::Vector2dF(0, 180)); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetOverviewSession()); |
| EXPECT_TRUE(widget->IsClosed()); |
| } |
| |
| // Verify that if the window item has been flung enough vertically, the window |
| // will be closed. |
| TEST_F(TabletModeOverviewSessionTest, FlingToClose) { |
| // This test requires a widget. |
| std::unique_ptr<views::Widget> widget(CreateTestWidget()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(1u, GetOverviewSession()->grid_list()[0]->size()); |
| |
| OverviewItem* item = GetOverviewItemForWindow(widget->GetNativeWindow()); |
| const gfx::PointF start = item->target_bounds().CenterPoint(); |
| ASSERT_TRUE(item); |
| |
| // Verify that items flung horizontally do not close the item. |
| GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50)); |
| GetOverviewSession()->Fling(item, start, 2500, 0); |
| ASSERT_TRUE(GetOverviewSession()); |
| |
| // Verify that items flung vertically but without enough velocity do not |
| // close the item. |
| GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50)); |
| GetOverviewSession()->Fling(item, start, 0, 1500); |
| ASSERT_TRUE(GetOverviewSession()); |
| |
| // Verify that flinging the item closes it, and since it is the last item in |
| // overview mode, overview mode is exited. |
| GetOverviewSession()->InitiateDrag(item, start, /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, start + gfx::Vector2dF(0, 50)); |
| GetOverviewSession()->Fling(item, start, 0, 2500); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetOverviewSession()); |
| EXPECT_TRUE(widget->IsClosed()); |
| } |
| |
| // Tests that nudging occurs in the most basic case, which is we have one row |
| // and one item which is about to be deleted by dragging. If the item is deleted |
| // we still only have one row, so the other items should nudge while the item is |
| // being dragged. |
| TEST_F(TabletModeOverviewSessionTest, BasicNudging) { |
| // Set up three equal windows, which take up one row on the overview grid. |
| // When one of them is deleted we are still left with all the windows on one |
| // row. |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| |
| const gfx::RectF item1_bounds = item1->target_bounds(); |
| const gfx::RectF item2_bounds = item2->target_bounds(); |
| const gfx::RectF item3_bounds = item3->target_bounds(); |
| |
| // Drag |item1| vertically. |item2| and |item3| bounds should change as they |
| // should be nudging towards their final bounds. |
| GetOverviewSession()->InitiateDrag(item1, item1_bounds.CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag( |
| item1, item1_bounds.CenterPoint() + gfx::Vector2dF(0, 160)); |
| EXPECT_NE(item2_bounds, item2->target_bounds()); |
| EXPECT_NE(item3_bounds, item3->target_bounds()); |
| |
| // Drag |item1| back to its start drag location and release, so that it does |
| // not get deleted. |
| GetOverviewSession()->Drag(item1, item1_bounds.CenterPoint()); |
| GetOverviewSession()->CompleteDrag(item1, item1_bounds.CenterPoint()); |
| |
| // Drag |item3| vertically. |item1| and |item2| bounds should change as they |
| // should be nudging towards their final bounds. |
| GetOverviewSession()->InitiateDrag(item3, item3_bounds.CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag( |
| item3, item3_bounds.CenterPoint() + gfx::Vector2dF(0, 160)); |
| EXPECT_NE(item1_bounds, item1->target_bounds()); |
| EXPECT_NE(item2_bounds, item2->target_bounds()); |
| } |
| |
| // Tests that no nudging occurs when the number of rows in overview mode change |
| // if the item to be deleted results in the overview grid to change number of |
| // rows. |
| TEST_F(TabletModeOverviewSessionTest, NoNudgingWhenNumRowsChange) { |
| // Set up four equal windows, which would split into two rows in overview |
| // mode. Removing one window would leave us with three windows, which only |
| // takes a single row in overview. |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window4 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| OverviewItem* item4 = GetOverviewItemForWindow(window4.get()); |
| |
| const gfx::RectF item1_bounds = item1->target_bounds(); |
| const gfx::RectF item2_bounds = item2->target_bounds(); |
| const gfx::RectF item3_bounds = item3->target_bounds(); |
| const gfx::RectF item4_bounds = item4->target_bounds(); |
| |
| // Drag |item1| past the drag to swipe threshold. None of the other window |
| // bounds should change, as none of them should be nudged. |
| GetOverviewSession()->InitiateDrag(item1, item1_bounds.CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag( |
| item1, item1_bounds.CenterPoint() + gfx::Vector2dF(0, 160)); |
| EXPECT_EQ(item2_bounds, item2->target_bounds()); |
| EXPECT_EQ(item3_bounds, item3->target_bounds()); |
| EXPECT_EQ(item4_bounds, item4->target_bounds()); |
| } |
| |
| // Tests that no nudging occurs when the item to be deleted results in an item |
| // from the previous row to drop down to the current row, thus causing the items |
| // to the right of the item to be shifted right, which is visually unacceptable. |
| TEST_F(TabletModeOverviewSessionTest, NoNudgingWhenLastItemOnPreviousRowDrops) { |
| // Set up five equal windows, which would split into two rows in overview |
| // mode. Removing one window would cause the rows to rearrange, with the third |
| // item dropping down from the first row to the second row. Create the windows |
| // backward so the the window indexs match the order seen in overview, as |
| // overview windows are ordered by MRU. |
| const int kWindows = 5; |
| std::unique_ptr<aura::Window> windows[kWindows]; |
| for (int i = kWindows - 1; i >= 0; --i) |
| windows[i] = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* items[kWindows]; |
| gfx::RectF item_bounds[kWindows]; |
| for (int i = 0; i < kWindows; ++i) { |
| items[i] = GetOverviewItemForWindow(windows[i].get()); |
| item_bounds[i] = items[i]->target_bounds(); |
| } |
| |
| // Drag the forth item past the drag to swipe threshold. None of the other |
| // window bounds should change, as none of them should be nudged, because |
| // deleting the fourth item will cause the third item to drop down from the |
| // first row to the second. |
| GetOverviewSession()->InitiateDrag(items[3], item_bounds[3].CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag( |
| items[3], item_bounds[3].CenterPoint() + gfx::Vector2dF(0, 160)); |
| EXPECT_EQ(item_bounds[0], items[0]->target_bounds()); |
| EXPECT_EQ(item_bounds[1], items[1]->target_bounds()); |
| EXPECT_EQ(item_bounds[2], items[2]->target_bounds()); |
| EXPECT_EQ(item_bounds[4], items[4]->target_bounds()); |
| |
| // Drag the fourth item back to its start drag location and release, so that |
| // it does not get deleted. |
| GetOverviewSession()->Drag(items[3], item_bounds[3].CenterPoint()); |
| GetOverviewSession()->CompleteDrag(items[3], item_bounds[3].CenterPoint()); |
| |
| // Drag the first item past the drag to swipe threshold. The second and third |
| // items should nudge as expected as there is no item dropping down to their |
| // row. The fourth and fifth items should not nudge as they are in a different |
| // row than the first item. |
| GetOverviewSession()->InitiateDrag(items[0], item_bounds[0].CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag( |
| items[0], item_bounds[0].CenterPoint() + gfx::Vector2dF(0, 160)); |
| EXPECT_NE(item_bounds[1], items[1]->target_bounds()); |
| EXPECT_NE(item_bounds[2], items[2]->target_bounds()); |
| EXPECT_EQ(item_bounds[3], items[3]->target_bounds()); |
| EXPECT_EQ(item_bounds[4], items[4]->target_bounds()); |
| } |
| |
| // Tests that there is no crash when destroying a window during a nudge drag. |
| // Regression test for https://crbug.com/997335. |
| TEST_F(TabletModeOverviewSessionTest, DestroyWindowDuringNudge) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* item = GetOverviewItemForWindow(window1.get()); |
| const gfx::PointF item_center = item->target_bounds().CenterPoint(); |
| |
| // Drag |item1| vertically to start nudging. |
| GetOverviewSession()->InitiateDrag(item, item_center, |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(item, item_center + gfx::Vector2dF(0, 160)); |
| |
| // Destroy |window2| and |window3|,then keep dragging. There should be no |
| // crash. |
| window2.reset(); |
| window3.reset(); |
| GetOverviewSession()->Drag(item, item_center + gfx::Vector2dF(0, 260)); |
| } |
| |
| TEST_F(TabletModeOverviewSessionTest, MultiTouch) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow(bounds)); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Dispatches a long press event to start drag mode. |
| OverviewItem* item = GetOverviewItemForWindow(window1.get()); |
| DispatchLongPress(item); |
| GetOverviewSession()->Drag(item, gfx::PointF(10.f, 500.f)); |
| const gfx::Rect item_bounds = item->GetWindow()->GetBoundsInScreen(); |
| |
| // Tap on a point on the wallpaper. Normally this would exit overview, but not |
| // while a drag is underway. |
| GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10)); |
| GetEventGenerator()->PressTouch(); |
| GetEventGenerator()->ReleaseTouch(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(item_bounds, item->GetWindow()->GetBoundsInScreen()); |
| |
| // Long press on another item, the bounds of both items should be unchanged. |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| const gfx::Rect item2_bounds = item2->GetWindow()->GetBoundsInScreen(); |
| DispatchLongPress(item2); |
| EXPECT_EQ(item_bounds, item->GetWindow()->GetBoundsInScreen()); |
| EXPECT_EQ(item2_bounds, item2->GetWindow()->GetBoundsInScreen()); |
| |
| // Clicking on a point on the wallpaper should still exit overview. |
| GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10)); |
| GetEventGenerator()->ClickLeftButton(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| } |
| |
| // Tests that when exiting overview in a way that causes windows to minimize, |
| // rounded corners are removed, otherwise they will be visible after |
| // unminimizing. Regression test for https://crbug.com/1146240. |
| TEST_F(TabletModeOverviewSessionTest, MinimizedRoundedCorners) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window(CreateTestWindow(bounds)); |
| |
| // Enter overview. Spin the run loop since rounded corners are applied on a |
| // post task. |
| ToggleOverview(); |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Tap on a point on the wallpaper to minimize the window and exit overview. |
| GetEventGenerator()->set_current_screen_location(gfx::Point(10, 10)); |
| GetEventGenerator()->ClickLeftButton(); |
| |
| // Tests that the window layer has rounded corners removed after exiting |
| // overview. |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized()); |
| EXPECT_EQ(gfx::RoundedCornersF(), window->layer()->rounded_corner_radii()); |
| } |
| |
| // Test the split view and overview functionalities in tablet mode. |
| class SplitViewOverviewSessionTest : public OverviewTestBase { |
| public: |
| SplitViewOverviewSessionTest() = default; |
| |
| SplitViewOverviewSessionTest(const SplitViewOverviewSessionTest&) = delete; |
| SplitViewOverviewSessionTest& operator=(const SplitViewOverviewSessionTest&) = |
| delete; |
| |
| ~SplitViewOverviewSessionTest() override = default; |
| |
| enum class SelectorItemLocation { |
| CENTER, |
| ORIGIN, |
| TOP_RIGHT, |
| BOTTOM_RIGHT, |
| BOTTOM_LEFT, |
| }; |
| |
| void SetUp() override { |
| OverviewTestBase::SetUp(); |
| EnterTabletMode(); |
| } |
| |
| SplitViewController* split_view_controller() { |
| return SplitViewController::Get(Shell::GetPrimaryRootWindow()); |
| } |
| |
| bool IsDividerAnimating() { |
| return split_view_controller()->IsDividerAnimating(); |
| } |
| |
| void SkipDividerSnapAnimation() { |
| if (!IsDividerAnimating()) |
| return; |
| split_view_controller()->StopAndShoveAnimatedDivider(); |
| split_view_controller()->EndResizeImpl(); |
| split_view_controller()->EndTabletSplitViewAfterResizingIfAppropriate(); |
| } |
| |
| void EndSplitView() { split_view_controller()->EndSplitView(); } |
| |
| void CheckWindowResizingPerformanceHistograms( |
| const char* trace, |
| int with_empty_overview_grid, |
| int max_latency_with_empty_overview_grid, |
| int with_nonempty_overview_grid, |
| int max_latency_with_nonempty_overview_grid) { |
| CheckForDuplicateTraceName(trace); |
| SCOPED_TRACE(trace); |
| |
| histograms_.ExpectTotalCount( |
| "Ash.SplitViewResize.PresentationTime.ClamshellMode.SingleWindow", |
| with_empty_overview_grid); |
| histograms_.ExpectTotalCount( |
| "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode." |
| "SingleWindow", |
| max_latency_with_empty_overview_grid); |
| histograms_.ExpectTotalCount( |
| "Ash.SplitViewResize.PresentationTime.ClamshellMode.WithOverview", |
| with_nonempty_overview_grid); |
| histograms_.ExpectTotalCount( |
| "Ash.SplitViewResize.PresentationTime.MaxLatency.ClamshellMode." |
| "WithOverview", |
| max_latency_with_nonempty_overview_grid); |
| } |
| |
| protected: |
| aura::Window* CreateWindow(const gfx::Rect& bounds) { |
| aura::Window* window = CreateTestWindowInShellWithDelegate( |
| new SplitViewTestWindowDelegate, -1, bounds); |
| return window; |
| } |
| |
| aura::Window* CreateWindowWithMinimumSize(const gfx::Rect& bounds, |
| const gfx::Size& size) { |
| SplitViewTestWindowDelegate* delegate = new SplitViewTestWindowDelegate(); |
| aura::Window* window = |
| CreateTestWindowInShellWithDelegate(delegate, -1, bounds); |
| delegate->set_minimum_size(size); |
| return window; |
| } |
| |
| gfx::Rect GetSplitViewLeftWindowBounds() { |
| return split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, split_view_controller()->left_window()); |
| } |
| |
| gfx::Rect GetSplitViewRightWindowBounds() { |
| return split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, split_view_controller()->right_window()); |
| } |
| |
| gfx::Rect GetSplitViewDividerBounds(bool is_dragging) { |
| if (!split_view_controller()->InSplitViewMode()) |
| return gfx::Rect(); |
| return split_view_controller() |
| ->split_view_divider_->GetDividerBoundsInScreen(is_dragging); |
| } |
| |
| gfx::Rect GetWorkAreaInScreen(aura::Window* window) { |
| return screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| window); |
| } |
| |
| // Drags a overview item |item| from its center or one of its corners |
| // to |end_location|. This should be used over |
| // DragWindowTo(OverviewItem*, gfx::Point) when testing snapping a |
| // window, but the windows centerpoint may be inside a snap region, thus the |
| // window will not snapped. This function is mostly used to test splitview so |
| // |long_press| is default to true. Set |long_press| to false if we do not |
| // want to long press after every press, which enables dragging vertically to |
| // close an item. |
| void DragWindowTo(OverviewItem* item, |
| const gfx::PointF& end_location, |
| SelectorItemLocation location, |
| bool long_press = true) { |
| gfx::PointF start_location; |
| switch (location) { |
| case SelectorItemLocation::CENTER: |
| start_location = item->target_bounds().CenterPoint(); |
| break; |
| case SelectorItemLocation::ORIGIN: |
| start_location = item->target_bounds().origin(); |
| break; |
| case SelectorItemLocation::TOP_RIGHT: |
| start_location = item->target_bounds().top_right(); |
| break; |
| case SelectorItemLocation::BOTTOM_RIGHT: |
| start_location = item->target_bounds().bottom_right(); |
| break; |
| case SelectorItemLocation::BOTTOM_LEFT: |
| start_location = item->target_bounds().bottom_left(); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| GetOverviewSession()->InitiateDrag(item, start_location, |
| /*is_touch_dragging=*/true); |
| if (long_press) |
| GetOverviewSession()->StartNormalDragMode(start_location); |
| GetOverviewSession()->Drag(item, end_location); |
| GetOverviewSession()->CompleteDrag(item, end_location); |
| } |
| |
| // Drags a overview item |item| from its center point to |end_location|. |
| void DragWindowTo(OverviewItem* item, const gfx::PointF& end_location) { |
| DragWindowTo(item, end_location, SelectorItemLocation::CENTER, true); |
| } |
| |
| private: |
| class SplitViewTestWindowDelegate : public aura::test::TestWindowDelegate { |
| public: |
| SplitViewTestWindowDelegate() = default; |
| ~SplitViewTestWindowDelegate() override = default; |
| |
| // aura::test::TestWindowDelegate: |
| void OnWindowDestroying(aura::Window* window) override { window->Hide(); } |
| void OnWindowDestroyed(aura::Window* window) override { delete this; } |
| }; |
| }; |
| |
| // Tests that dragging an overview item to the edge of the screen snaps the |
| // window. If two windows are snapped to left and right side of the screen, exit |
| // the overview mode. |
| TEST_F(SplitViewOverviewSessionTest, DragOverviewWindowToSnap) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| |
| // Drag |window2| selector item to attempt to snap to left. Since there is |
| // already one left snapped window |window1|, |window1| will be put in |
| // overview mode. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| DragWindowTo(overview_item2, gfx::PointF(0, 0)); |
| |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window2.get()); |
| EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview( |
| window1.get())); |
| |
| // Drag |window3| selector item to snap to right. |
| OverviewItem* overview_item3 = GetOverviewItemForWindow(window3.get()); |
| const gfx::PointF end_location3(GetWorkAreaInScreen(window3.get()).width(), |
| 0.f); |
| DragWindowTo(overview_item3, end_location3); |
| |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window3.get()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| } |
| |
| // Verify the correct behavior when dragging windows in overview mode. |
| TEST_F(SplitViewOverviewSessionTest, OverviewDragControllerBehavior) { |
| ui::GestureConfiguration* gesture_config = |
| ui::GestureConfiguration::GetInstance(); |
| gesture_config->set_long_press_time_in_ms(1); |
| gesture_config->set_short_press_time(base::Milliseconds(1)); |
| gesture_config->set_show_press_delay_in_ms(1); |
| |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* window_item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* window_item2 = GetOverviewItemForWindow(window2.get()); |
| |
| // Verify that if a drag is orginally horizontal, the drag behavior is drag to |
| // snap. |
| using DragBehavior = OverviewWindowDragController::DragBehavior; |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(window_item1->target_bounds().CenterPoint())); |
| generator->PressTouch(); |
| |
| // Simulate a long press, which is required to snap windows. |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2)); |
| run_loop.Run(); |
| |
| OverviewWindowDragController* drag_controller = |
| GetOverviewSession()->window_drag_controller(); |
| ASSERT_TRUE(drag_controller); |
| EXPECT_EQ(DragBehavior::kNormalDrag, |
| drag_controller->current_drag_behavior()); |
| generator->MoveTouchBy(20, 0); |
| EXPECT_EQ(DragBehavior::kNormalDrag, |
| drag_controller->current_drag_behavior()); |
| generator->ReleaseTouch(); |
| EXPECT_EQ(DragBehavior::kNoDrag, drag_controller->current_drag_behavior()); |
| |
| // Verify that if a drag is orginally vertical, the drag behavior is drag to |
| // close. |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(window_item2->target_bounds().CenterPoint())); |
| generator->PressTouch(); |
| |
| // Use small increments otherwise a fling event will be fired. |
| for (int j = 0; j < 20; ++j) |
| generator->MoveTouchBy(0, 1); |
| |
| // A new instance of drag controller gets created each time a drag starts. |
| drag_controller = GetOverviewSession()->window_drag_controller(); |
| EXPECT_EQ(DragBehavior::kDragToClose, |
| drag_controller->current_drag_behavior()); |
| } |
| |
| // Verify the window grid size changes as expected when dragging items around in |
| // overview mode when split view is enabled. |
| TEST_F(SplitViewOverviewSessionTest, |
| OverviewGridSizeWhileDraggingWithSplitView) { |
| // Add three windows and enter overview mode. |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window3(CreateTestWindow()); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Select window one and start the drag. |
| const int window_width = |
| Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen().width(); |
| OverviewItem* overview_item = GetOverviewItemForWindow(window1.get()); |
| gfx::RectF overview_item_bounds = overview_item->target_bounds(); |
| gfx::PointF start_location(overview_item_bounds.CenterPoint()); |
| GetOverviewSession()->InitiateDrag(overview_item, start_location, |
| /*is_touch_dragging=*/false); |
| |
| // Verify that when dragged to the left, the window grid is located where the |
| // right window of split view mode should be. |
| const gfx::PointF left(0, 0); |
| GetOverviewSession()->Drag(overview_item, left); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_TRUE(split_view_controller()->left_window() == nullptr); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetSplitViewRightWindowBounds()), |
| GetGridBounds()); |
| |
| // Verify that when dragged to the right, the window grid is located where the |
| // left window of split view mode should be. |
| const gfx::PointF right(window_width, 0); |
| GetOverviewSession()->Drag(overview_item, right); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_TRUE(split_view_controller()->right_window() == nullptr); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetSplitViewLeftWindowBounds()), |
| GetGridBounds()); |
| |
| // Verify that when dragged to the center, the window grid is has the |
| // dimensions of the work area. |
| const gfx::PointF center(window_width / 2, 0); |
| GetOverviewSession()->Drag(overview_item, center); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetWorkAreaInScreen(window1.get())), |
| GetGridBounds()); |
| |
| // Snap window1 to the left and initialize dragging for window2. |
| GetOverviewSession()->Drag(overview_item, left); |
| GetOverviewSession()->CompleteDrag(overview_item, left); |
| ASSERT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| ASSERT_EQ(window1.get(), split_view_controller()->left_window()); |
| overview_item = GetOverviewItemForWindow(window2.get()); |
| overview_item_bounds = overview_item->target_bounds(); |
| start_location = overview_item_bounds.CenterPoint(); |
| GetOverviewSession()->InitiateDrag(overview_item, start_location, |
| /*is_touch_dragging=*/false); |
| |
| // Verify that when there is a snapped window, the window grid bounds remain |
| // constant despite overview items being dragged left and right. |
| GetOverviewSession()->Drag(overview_item, left); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetSplitViewRightWindowBounds()), |
| GetGridBounds()); |
| GetOverviewSession()->Drag(overview_item, right); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetSplitViewRightWindowBounds()), |
| GetGridBounds()); |
| GetOverviewSession()->Drag(overview_item, center); |
| EXPECT_EQ(ShrinkBoundsByHotseatInset(GetSplitViewRightWindowBounds()), |
| GetGridBounds()); |
| } |
| |
| // Tests dragging a unsnappable window. |
| TEST_F(SplitViewOverviewSessionTest, DraggingUnsnappableAppWithSplitView) { |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| |
| // The grid bounds should be the size of the root window minus the shelf. |
| const gfx::Rect root_window_bounds = |
| Shell::Get()->GetPrimaryRootWindow()->GetBoundsInScreen(); |
| const gfx::Rect shelf_bounds = |
| Shelf::ForWindow(Shell::Get()->GetPrimaryRootWindow())->GetIdealBounds(); |
| const gfx::Rect expected_grid_bounds = ShrinkBoundsByHotseatInset( |
| SubtractRects(root_window_bounds, shelf_bounds)); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Verify that after dragging the unsnappable window to the left and right, |
| // the window grid bounds do not change. |
| OverviewItem* overview_item = |
| GetOverviewItemForWindow(unsnappable_window.get()); |
| GetOverviewSession()->InitiateDrag( |
| overview_item, overview_item->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| GetOverviewSession()->Drag(overview_item, gfx::PointF()); |
| EXPECT_EQ(expected_grid_bounds, GetGridBounds()); |
| GetOverviewSession()->Drag(overview_item, |
| gfx::PointF(root_window_bounds.right(), 0.f)); |
| EXPECT_EQ(expected_grid_bounds, GetGridBounds()); |
| GetOverviewSession()->Drag( |
| overview_item, gfx::PointF(root_window_bounds.right() / 2.f, 0.f)); |
| EXPECT_EQ(expected_grid_bounds, GetGridBounds()); |
| } |
| |
| // Test that if an unsnappable window is dragged from overview to where another |
| // window is already snapped, then there is no snap preview, and if the drag |
| // ends there, then there is no DCHECK failure (or crash). |
| TEST_F(SplitViewOverviewSessionTest, |
| DragUnsnappableWindowFromOverviewToSnappedWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| ASSERT_EQ(1u, GetOverviewSession()->grid_list().size()); |
| OverviewGrid* overview_grid = GetOverviewSession()->grid_list()[0].get(); |
| OverviewItem* overview_item = |
| overview_grid->GetOverviewItemContaining(unsnappable_window.get()); |
| GetOverviewSession()->InitiateDrag( |
| overview_item, overview_item->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| GetOverviewSession()->Drag(overview_item, gfx::PointF()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| overview_grid->split_view_drag_indicators() |
| ->current_window_dragging_state()); |
| GetOverviewSession()->CompleteDrag(overview_item, gfx::PointF()); |
| } |
| |
| TEST_F(SplitViewOverviewSessionTest, Clipping) { |
| // Helper to check if two rectangles have roughly the same aspect ratio. They |
| // may be off by a bit due to insets but should have roughly the same shape. |
| auto aspect_ratio_near = [](const gfx::Rect& rect1, const gfx::Rect& rect2) { |
| DCHECK_GT(rect1.height(), 0); |
| DCHECK_GT(rect2.height(), 0); |
| constexpr float kEpsilon = 0.05f; |
| const float rect1_aspect_ratio = |
| static_cast<float>(rect1.width()) / rect1.height(); |
| const float rect2_aspect_ratio = |
| static_cast<float>(rect2.width()) / rect2.height(); |
| return std::abs(rect2_aspect_ratio - rect1_aspect_ratio) < kEpsilon; |
| }; |
| |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(); // Minimized. |
| std::unique_ptr<aura::Window> window4 = CreateTestWindow(); // Has top inset. |
| WindowState::Get(window3.get())->Minimize(); |
| window4->SetProperty(aura::client::kTopViewInset, 32); |
| |
| for (bool portrait : {false, true}) { |
| SCOPED_TRACE(portrait ? "Portrait" : "Landscape"); |
| if (portrait) { |
| ScreenOrientationControllerTestApi test_api( |
| Shell::Get()->screen_orientation_controller()); |
| test_api.SetDisplayRotation(display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| } |
| |
| const gfx::Rect clipping1 = window1->layer()->clip_rect(); |
| const gfx::Rect clipping2 = window2->layer()->clip_rect(); |
| const gfx::Rect clipping3 = window3->layer()->clip_rect(); |
| const gfx::Rect clipping4 = window4->layer()->clip_rect(); |
| const gfx::Rect maximized_bounds = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| window1.get()); |
| const gfx::Rect split_view_bounds_right = |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::SnapPosition::RIGHT, |
| /*window_for_minimum_size=*/nullptr); |
| |
| ToggleOverview(); |
| |
| // Tests that after entering overview, windows with no top inset and |
| // minimized windows still have no clip. |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(clipping1, window1->layer()->clip_rect()); |
| EXPECT_EQ(clipping2, window2->layer()->clip_rect()); |
| EXPECT_EQ(clipping3, window3->layer()->clip_rect()); |
| EXPECT_NE(clipping4, window4->layer()->clip_rect()); |
| const gfx::Rect overview_clipping4 = window4->layer()->clip_rect(); |
| |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| OverviewItem* item4 = GetOverviewItemForWindow(window4.get()); |
| GetOverviewSession()->InitiateDrag(item1, |
| item1->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| |
| // Tests that after we drag to a preview area, the items target bounds have |
| // a matching aspect ratio to what the window would have if it were to be |
| // snapped in splitview. The window clipping should match this, but the |
| // windows regular bounds remain unchanged (maximized). |
| GetOverviewSession()->Drag(item1, gfx::PointF()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapLeft, |
| GetOverviewSession() |
| ->grid_list()[0] |
| ->split_view_drag_indicators() |
| ->current_window_dragging_state()); |
| EXPECT_FALSE(window2->layer()->clip_rect().IsEmpty()); |
| EXPECT_TRUE(aspect_ratio_near(window2->layer()->clip_rect(), |
| split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item2->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window2->GetBoundsInScreen(), maximized_bounds)); |
| |
| // The actual window of a minimized window should not be clipped. The |
| // clipped layer will be the WindowPreviewView of the associated |
| // OverviewItemView. |
| EXPECT_TRUE(window3->layer()->clip_rect().IsEmpty()); |
| ui::Layer* preview_layer = |
| item3->overview_item_view()->preview_view()->layer(); |
| EXPECT_FALSE(preview_layer->clip_rect().IsEmpty()); |
| EXPECT_FALSE(preview_layer->transform().IsIdentity()); |
| // The clip rect is affected by |preview_layer|'s transform so apply it. |
| gfx::RectF clip_rect3_f(preview_layer->clip_rect()); |
| preview_layer->transform().TransformRect(&clip_rect3_f); |
| const gfx::Rect clip_rects3 = gfx::ToEnclosedRect(clip_rect3_f); |
| EXPECT_TRUE(aspect_ratio_near(clip_rects3, split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item3->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window3->GetBoundsInScreen(), maximized_bounds)); |
| |
| // A window with top view inset should be clipped, but with a new clipping |
| // than the original overview clipping. |
| EXPECT_FALSE(window4->layer()->clip_rect().IsEmpty()); |
| EXPECT_NE(overview_clipping4, window4->layer()->clip_rect()); |
| EXPECT_TRUE(aspect_ratio_near(window4->layer()->clip_rect(), |
| split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item4->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window4->GetBoundsInScreen(), maximized_bounds)); |
| |
| // Tests that after snapping, the aspect ratios should be the same as being |
| // in the preview area. |
| GetOverviewSession()->CompleteDrag(item1, gfx::PointF()); |
| ASSERT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(window2->layer()->clip_rect().IsEmpty()); |
| EXPECT_TRUE(aspect_ratio_near(window2->layer()->clip_rect(), |
| split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item2->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window2->GetBoundsInScreen(), maximized_bounds)); |
| |
| EXPECT_TRUE(window3->layer()->clip_rect().IsEmpty()); |
| EXPECT_TRUE(aspect_ratio_near(clip_rects3, split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item3->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window3->GetBoundsInScreen(), maximized_bounds)); |
| |
| EXPECT_FALSE(window4->layer()->clip_rect().IsEmpty()); |
| EXPECT_NE(overview_clipping4, window4->layer()->clip_rect()); |
| EXPECT_TRUE(aspect_ratio_near(window4->layer()->clip_rect(), |
| split_view_bounds_right)); |
| EXPECT_TRUE(aspect_ratio_near( |
| gfx::ToEnclosedRect(item4->GetWindowTargetBoundsWithInsets()), |
| split_view_bounds_right)); |
| EXPECT_TRUE( |
| aspect_ratio_near(window4->GetBoundsInScreen(), maximized_bounds)); |
| |
| // Tests that the clipping is reset after exiting overview. |
| EndSplitView(); |
| ToggleOverview(); |
| EXPECT_EQ(clipping1, window1->layer()->clip_rect()); |
| EXPECT_EQ(clipping2, window2->layer()->clip_rect()); |
| EXPECT_EQ(clipping3, window3->layer()->clip_rect()); |
| EXPECT_EQ(clipping4, window4->layer()->clip_rect()); |
| } |
| } |
| |
| // Tests that when splitview is inactive, there is no need for aspect ratio |
| // changes, so there is no clipping on the overview windows. Regression test for |
| // crbug.com/1020440. |
| TEST_F(SplitViewOverviewSessionTest, NoClippingWhenSplitviewDisabled) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| // Splitview is disabled when ChromeVox is enabled. |
| Shell::Get()->accessibility_controller()->SetSpokenFeedbackEnabled( |
| true, A11Y_NOTIFICATION_NONE); |
| ASSERT_FALSE(ShouldAllowSplitView()); |
| const gfx::Rect clipping1 = window1->layer()->clip_rect(); |
| const gfx::Rect clipping2 = window2->layer()->clip_rect(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(clipping1, window1->layer()->clip_rect()); |
| EXPECT_EQ(clipping2, window2->layer()->clip_rect()); |
| |
| // Drag to the edge of the screen. There should be no clipping and no crash. |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| GetOverviewSession()->InitiateDrag(item1, |
| item1->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| GetOverviewSession()->Drag(item1, gfx::PointF()); |
| EXPECT_EQ(clipping1, window1->layer()->clip_rect()); |
| EXPECT_EQ(clipping2, window2->layer()->clip_rect()); |
| } |
| |
| // Tests that if there is only one window in the MRU window list in the overview |
| // mode, snapping the window to one side of the screen will not end the overview |
| // mode even if there is no more window left in the overview window grid. |
| TEST_F(SplitViewOverviewSessionTest, EmptyWindowsListNotExitOverview) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| |
| // Test that overview mode is active in this single window case. |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Create a new window should exit the overview mode. |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| // If there are only 2 snapped windows, close one of them should enter |
| // overview mode. |
| window2.reset(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // If there are more than 2 windows in overview |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window4(CreateWindow(bounds)); |
| wm::ActivateWindow(window3.get()); |
| wm::ActivateWindow(window4.get()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| window3.reset(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| window4.reset(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Test that if there is only 1 snapped window, and no window in the overview |
| // grid, ToggleOverview() can't end overview. |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| EndSplitView(); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Test that ToggleOverview() can end overview if we're not in split view |
| // mode. |
| ToggleOverview(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| |
| // Now enter overview and split view again. Test that exiting tablet mode can |
| // end split view and overview correctly. |
| ToggleOverview(); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| |
| // Test that closing all windows in overview can end overview if we're not in |
| // split view mode. |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| window1.reset(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| } |
| |
| // Tests using Alt+[ on a maximized window. |
| TEST_F(SplitViewOverviewSessionTest, AltLeftSquareBracketOnMaximizedWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| wm::ActivateWindow(snapped_window.get()); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| EXPECT_EQ(WindowStateType::kMaximized, snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_FALSE(InOverviewSession()); |
| const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| snapped_window_state->OnWMEvent(&alt_left_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get())); |
| EXPECT_EQ(WindowStateType::kPrimarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(snapped_window.get(), split_view_controller()->left_window()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests using Alt+] on a maximized window. |
| TEST_F(SplitViewOverviewSessionTest, AltRightSquareBracketOnMaximizedWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| wm::ActivateWindow(snapped_window.get()); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| EXPECT_EQ(WindowStateType::kMaximized, snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller()->state()); |
| EXPECT_FALSE(InOverviewSession()); |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| snapped_window_state->OnWMEvent(&alt_right_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get())); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(snapped_window.get(), split_view_controller()->right_window()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests using Alt+[ and Alt+] on an unsnappable window. |
| TEST_F(SplitViewOverviewSessionTest, AltSquareBracketOnUnsnappableWindow) { |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| std::unique_ptr<aura::Window> other_window = CreateTestWindow(); |
| wm::ActivateWindow(unsnappable_window.get()); |
| WindowState* unsnappable_window_state = |
| WindowState::Get(unsnappable_window.get()); |
| const auto expect_unsnappable_window_is_active_and_maximized = |
| [this, &unsnappable_window, unsnappable_window_state]() { |
| EXPECT_TRUE(wm::IsActiveWindow(unsnappable_window.get())); |
| EXPECT_EQ(WindowStateType::kMaximized, |
| unsnappable_window_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| }; |
| expect_unsnappable_window_is_active_and_maximized(); |
| const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| unsnappable_window_state->OnWMEvent(&alt_left_square_bracket); |
| expect_unsnappable_window_is_active_and_maximized(); |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| unsnappable_window_state->OnWMEvent(&alt_right_square_bracket); |
| expect_unsnappable_window_is_active_and_maximized(); |
| } |
| |
| // Tests using Alt+[ on a left snapped window, and Alt+] on a right snapped |
| // window. |
| TEST_F(SplitViewOverviewSessionTest, AltSquareBracketOnSameSideSnappedWindow) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| const auto test_unsnapping_window1 = [this, |
| &window1](WMEventType event_type) { |
| wm::ActivateWindow(window1.get()); |
| WindowState* window1_state = WindowState::Get(window1.get()); |
| const WindowSnapWMEvent event(event_type); |
| window1_state->OnWMEvent(&event); |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| EXPECT_EQ(WindowStateType::kMaximized, window1_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| }; |
| // Test Alt+[ with active window snapped on left and overview on right. |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| // Test Alt+] with active window snapped on right and overview on left. |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY); |
| // Test Alt+[ with active window snapped on left and other window snapped on |
| // right, if the left window is the default snapped window. |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| // Test Alt+[ with active window snapped on left and other window snapped on |
| // right, if the right window is the default snapped window. |
| split_view_controller()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| // Test Alt+] with active window snapped on right and other window snapped on |
| // left, if the left window is the default snapped window. |
| split_view_controller()->SnapWindow(window2.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY); |
| // Test Alt+] with active window snapped on right and other window snapped on |
| // left, if the right window is the default snapped window. |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| split_view_controller()->SnapWindow(window2.get(), SplitViewController::LEFT); |
| test_unsnapping_window1(WM_EVENT_CYCLE_SNAP_SECONDARY); |
| } |
| |
| // Tests using Alt+[ on a right snapped window, and Alt+] on a left snapped |
| // window. |
| TEST_F(SplitViewOverviewSessionTest, |
| AltSquareBracketOnOppositeSideSnappedWindow) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| const auto test_left_snapping_window1 = [this, &window1, &window2]() { |
| wm::ActivateWindow(window1.get()); |
| WindowState* window1_state = WindowState::Get(window1.get()); |
| const WindowSnapWMEvent alt_left_square_bracket( |
| WM_EVENT_CYCLE_SNAP_PRIMARY); |
| window1_state->OnWMEvent(&alt_left_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| EXPECT_EQ(WindowStateType::kPrimarySnapped, window1_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(window1.get(), split_view_controller()->left_window()); |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetOverviewItemForWindow(window2.get())); |
| }; |
| const auto test_right_snapping_window1 = [this, &window1, &window2]() { |
| wm::ActivateWindow(window1.get()); |
| WindowState* window1_state = WindowState::Get(window1.get()); |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| window1_state->OnWMEvent(&alt_right_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, |
| window1_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(window1.get(), split_view_controller()->right_window()); |
| ASSERT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetOverviewItemForWindow(window2.get())); |
| }; |
| // Test Alt+[ with active window snapped on right and overview on left. |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| test_left_snapping_window1(); |
| // Test Alt+] with active window snapped on left and overview on right. |
| test_right_snapping_window1(); |
| // Test Alt+[ with active window snapped on right and other window snapped on |
| // left, if the right window is the default snapped window. |
| split_view_controller()->SnapWindow(window2.get(), SplitViewController::LEFT); |
| test_left_snapping_window1(); |
| // Test Alt+] with active window snapped on left and other window snapped on |
| // right, if the left window is the default snapped window. |
| split_view_controller()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| test_right_snapping_window1(); |
| // Test Alt+[ with active window snapped on right and other window snapped on |
| // left, if the left window is the default snapped window. |
| EndSplitView(); |
| split_view_controller()->SnapWindow(window2.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| test_left_snapping_window1(); |
| // Test Alt+] with active window snapped on left and other window snapped on |
| // right, if the right window is the default snapped window. |
| EndSplitView(); |
| split_view_controller()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| test_right_snapping_window1(); |
| } |
| |
| // Test the overview window drag functionalities when screen rotates. |
| TEST_F(SplitViewOverviewSessionTest, SplitViewRotationTest) { |
| UpdateDisplay("807x407"); |
| int64_t display_id = display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| display::DisplayManager* display_manager = Shell::Get()->display_manager(); |
| display::test::ScopedSetInternalDisplayId set_internal(display_manager, |
| display_id); |
| ScreenOrientationControllerTestApi test_api( |
| Shell::Get()->screen_orientation_controller()); |
| |
| // Set the screen orientation to LANDSCAPE_PRIMARY. |
| test_api.SetDisplayRotation(display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| chromeos::OrientationType::kLandscapePrimary); |
| |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| // Test that dragging |window1| to the left of the screen snaps it to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| |
| // Test that dragging |window2| to the right of the screen snaps it to right. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| gfx::Rect work_area_rect = GetWorkAreaInScreen(window2.get()); |
| gfx::PointF end_location2(work_area_rect.width(), work_area_rect.height()); |
| DragWindowTo(overview_item2, end_location2); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window2.get()); |
| |
| // Test that |left_window_| was snapped to left after rotated 0 degree. |
| gfx::Rect left_window_bounds = |
| split_view_controller()->left_window()->GetBoundsInScreen(); |
| EXPECT_EQ(left_window_bounds.x(), work_area_rect.x()); |
| EXPECT_EQ(left_window_bounds.y(), work_area_rect.y()); |
| EndSplitView(); |
| |
| // Rotate the screen by 270 degree. |
| test_api.SetDisplayRotation(display::Display::ROTATE_270, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| chromeos::OrientationType::kPortraitPrimary); |
| ToggleOverview(); |
| |
| // Test that dragging |window1| to the top of the screen snaps it to left. |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| |
| // Test that dragging |window2| to the bottom of the screen snaps it to right. |
| overview_item2 = GetOverviewItemForWindow(window2.get()); |
| work_area_rect = GetWorkAreaInScreen(window2.get()); |
| end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height()); |
| DragWindowTo(overview_item2, end_location2, SelectorItemLocation::ORIGIN); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window2.get()); |
| |
| // Test that |left_window_| was snapped to top after rotated 270 degree. |
| left_window_bounds = |
| split_view_controller()->left_window()->GetBoundsInScreen(); |
| EXPECT_EQ(left_window_bounds.x(), work_area_rect.x()); |
| EXPECT_EQ(left_window_bounds.y(), work_area_rect.y()); |
| EndSplitView(); |
| |
| // Rotate the screen by 180 degree. |
| test_api.SetDisplayRotation(display::Display::ROTATE_180, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| chromeos::OrientationType::kLandscapeSecondary); |
| ToggleOverview(); |
| |
| // Test that dragging |window1| to the left of the screen snaps it to right. |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kRightSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window1.get()); |
| |
| // Test that dragging |window2| to the right of the screen snaps it to left. |
| overview_item2 = GetOverviewItemForWindow(window2.get()); |
| work_area_rect = GetWorkAreaInScreen(window2.get()); |
| end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height()); |
| DragWindowTo(overview_item2, end_location2, SelectorItemLocation::ORIGIN); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window2.get()); |
| |
| // Test that |right_window_| was snapped to left after rotated 180 degree. |
| gfx::Rect right_window_bounds = |
| split_view_controller()->right_window()->GetBoundsInScreen(); |
| EXPECT_EQ(right_window_bounds.x(), work_area_rect.x()); |
| EXPECT_EQ(right_window_bounds.y(), work_area_rect.y()); |
| EndSplitView(); |
| |
| // Rotate the screen by 90 degree. |
| test_api.SetDisplayRotation(display::Display::ROTATE_90, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(test_api.GetCurrentOrientation(), |
| chromeos::OrientationType::kPortraitSecondary); |
| ToggleOverview(); |
| |
| // Test that dragging |window1| to the top of the screen snaps it to right. |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kRightSnapped); |
| EXPECT_EQ(split_view_controller()->right_window(), window1.get()); |
| |
| // Test that dragging |window2| to the bottom of the screen snaps it to left. |
| overview_item2 = GetOverviewItemForWindow(window2.get()); |
| work_area_rect = GetWorkAreaInScreen(window2.get()); |
| end_location2 = gfx::PointF(work_area_rect.width(), work_area_rect.height()); |
| DragWindowTo(overview_item2, end_location2); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window2.get()); |
| |
| // Test that |right_window_| was snapped to top after rotated 90 degree. |
| right_window_bounds = |
| split_view_controller()->right_window()->GetBoundsInScreen(); |
| EXPECT_EQ(right_window_bounds.x(), work_area_rect.x()); |
| EXPECT_EQ(right_window_bounds.y(), work_area_rect.y()); |
| EndSplitView(); |
| } |
| |
| // Test that when split view mode and overview mode are both active at the same |
| // time, dragging the split view divider resizes the bounds of snapped window |
| // and the bounds of overview window grids at the same time. |
| TEST_F(SplitViewOverviewSessionTest, SplitViewOverviewBothActiveTest) { |
| UpdateDisplay("907x407"); |
| |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| const gfx::Rect window1_bounds = window1->GetBoundsInScreen(); |
| const gfx::Rect overview_grid_bounds = GetGridBounds(); |
| const gfx::Rect divider_bounds = |
| GetSplitViewDividerBounds(false /* is_dragging */); |
| |
| // Test that window1, divider, overview grid are aligned horizontally. |
| EXPECT_EQ(window1_bounds.right(), divider_bounds.x()); |
| EXPECT_EQ(divider_bounds.right(), overview_grid_bounds.x()); |
| |
| const gfx::Point resize_start_location(divider_bounds.CenterPoint()); |
| split_view_controller()->StartResize(resize_start_location); |
| const gfx::Point resize_end_location(300, 0); |
| split_view_controller()->EndResize(resize_end_location); |
| SkipDividerSnapAnimation(); |
| |
| const gfx::Rect window1_bounds_after_resize = window1->GetBoundsInScreen(); |
| const gfx::Rect overview_grid_bounds_after_resize = GetGridBounds(); |
| const gfx::Rect divider_bounds_after_resize = |
| GetSplitViewDividerBounds(false /* is_dragging */); |
| |
| // Test that window1, divider, overview grid are still aligned horizontally |
| // after resizing. |
| EXPECT_EQ(window1_bounds.right(), divider_bounds.x()); |
| EXPECT_EQ(divider_bounds.right(), overview_grid_bounds.x()); |
| |
| // Test that window1, divider, overview grid's bounds are changed after |
| // resizing. |
| EXPECT_NE(window1_bounds, window1_bounds_after_resize); |
| EXPECT_NE(overview_grid_bounds, overview_grid_bounds_after_resize); |
| EXPECT_NE(divider_bounds, divider_bounds_after_resize); |
| } |
| |
| // Verify that selecting an unsnappable window while in split view works as |
| // intended. |
| TEST_F(SplitViewOverviewSessionTest, SelectUnsnappableWindowInSplitView) { |
| // Create one snappable and one unsnappable window. |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Snap the snappable window to enter split view mode. |
| split_view_controller()->SnapWindow(window.get(), SplitViewController::LEFT); |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| // Select the unsnappable window. |
| OverviewItem* overview_item = |
| GetOverviewItemForWindow(unsnappable_window.get()); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint())); |
| generator->ClickLeftButton(); |
| |
| // Verify that we are out of split view and overview mode, and that the active |
| // window is the unsnappable window. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(unsnappable_window.get(), window_util::GetActiveWindow()); |
| |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window.get(), SplitViewController::LEFT); |
| split_view_controller()->SnapWindow(window2.get(), |
| SplitViewController::RIGHT); |
| |
| // Split view mode should be active. Overview mode should be ended. |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| |
| ToggleOverview(); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Now select the unsnappable window. |
| overview_item = GetOverviewItemForWindow(unsnappable_window.get()); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint())); |
| generator->ClickLeftButton(); |
| |
| // Split view mode should be ended. And the unsnappable window should be the |
| // active window now. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(unsnappable_window.get(), window_util::GetActiveWindow()); |
| } |
| |
| // Verify that when in overview mode, the selector items unsnappable indicator |
| // shows up when expected. |
| TEST_F(SplitViewOverviewSessionTest, OverviewUnsnappableIndicatorVisibility) { |
| // Create three windows; two normal and one unsnappable, so that when after |
| // snapping |window1| to enter split view we can test the state of each normal |
| // and unsnappable windows. |
| std::unique_ptr<aura::Window> window1(CreateTestWindow()); |
| std::unique_ptr<aura::Window> window2(CreateTestWindow()); |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* snappable_overview_item = |
| GetOverviewItemForWindow(window2.get()); |
| OverviewItem* unsnappable_overview_item = |
| GetOverviewItemForWindow(unsnappable_window.get()); |
| |
| // Note: |cannot_snap_label_view_| and its parent will be created on demand. |
| EXPECT_FALSE(snappable_overview_item->cannot_snap_widget_for_testing()); |
| ASSERT_FALSE(unsnappable_overview_item->cannot_snap_widget_for_testing()); |
| |
| // Snap the extra snappable window to enter split view mode. |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(snappable_overview_item->cannot_snap_widget_for_testing()); |
| ASSERT_TRUE(unsnappable_overview_item->cannot_snap_widget_for_testing()); |
| ui::Layer* unsnappable_layer = |
| unsnappable_overview_item->cannot_snap_widget_for_testing() |
| ->GetNativeWindow() |
| ->layer(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Exiting the splitview will hide the unsnappable label. |
| const gfx::Rect divider_bounds = |
| GetSplitViewDividerBounds(/*is_dragging=*/false); |
| GetEventGenerator()->set_current_screen_location( |
| divider_bounds.CenterPoint()); |
| GetEventGenerator()->DragMouseTo(0, 0); |
| SkipDividerSnapAnimation(); |
| |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| } |
| |
| // Verify that during "normal" dragging from overview (not drag-to-close), the |
| // dragged item's unsnappable indicator is temporarily suppressed. |
| TEST_F(SplitViewOverviewSessionTest, |
| OverviewUnsnappableIndicatorVisibilityWhileDragging) { |
| ui::GestureConfiguration* gesture_config = |
| ui::GestureConfiguration::GetInstance(); |
| gesture_config->set_long_press_time_in_ms(1); |
| gesture_config->set_short_press_time(base::Milliseconds(1)); |
| gesture_config->set_show_press_delay_in_ms(1); |
| |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow(); |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| OverviewItem* unsnappable_overview_item = |
| GetOverviewItemForWindow(unsnappable_window.get()); |
| ASSERT_TRUE(unsnappable_overview_item->cannot_snap_widget_for_testing()); |
| ui::Layer* unsnappable_layer = |
| unsnappable_overview_item->cannot_snap_widget_for_testing() |
| ->GetNativeWindow() |
| ->layer(); |
| ASSERT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Test that the unsnappable label is temporarily suppressed during mouse |
| // dragging. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| const gfx::Point drag_starting_point = gfx::ToRoundedPoint( |
| unsnappable_overview_item->target_bounds().CenterPoint()); |
| generator->set_current_screen_location(drag_starting_point); |
| generator->PressLeftButton(); |
| using DragBehavior = OverviewWindowDragController::DragBehavior; |
| EXPECT_EQ( |
| DragBehavior::kUndefined, |
| GetOverviewSession()->window_drag_controller()->current_drag_behavior()); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| generator->MoveMouseBy(0, 20); |
| EXPECT_EQ( |
| DragBehavior::kNormalDrag, |
| GetOverviewSession()->window_drag_controller()->current_drag_behavior()); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| generator->ReleaseLeftButton(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Test that the unsnappable label is temporarily suppressed during "normal" |
| // touch dragging (not drag-to-close). |
| generator->set_current_screen_location(drag_starting_point); |
| generator->PressTouch(); |
| { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2)); |
| run_loop.Run(); |
| } |
| EXPECT_EQ( |
| DragBehavior::kNormalDrag, |
| GetOverviewSession()->window_drag_controller()->current_drag_behavior()); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| generator->MoveTouchBy(20, 0); |
| generator->ReleaseTouch(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Test that the unsnappable label reappears if "normal" touch dragging (not |
| // drag-to-close) ends when the item has not been actually dragged anywhere. |
| // This case improves test coverage because it is handled in |
| // |OverviewWindowDragController::ResetGesture| instead of |
| // |OverviewWindowDragController::CompleteNormalDrag|. |
| generator->set_current_screen_location(drag_starting_point); |
| generator->PressTouch(); |
| { |
| base::RunLoop run_loop; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(2)); |
| run_loop.Run(); |
| } |
| EXPECT_EQ( |
| DragBehavior::kNormalDrag, |
| GetOverviewSession()->window_drag_controller()->current_drag_behavior()); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| generator->ReleaseTouch(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Test that the unsnappable label persists in drag-to-close mode. |
| generator->set_current_screen_location(drag_starting_point); |
| generator->PressTouch(); |
| // Use small increments otherwise a fling event will be fired. |
| for (int j = 0; j < 20; ++j) |
| generator->MoveTouchBy(0, 1); |
| EXPECT_EQ( |
| DragBehavior::kDragToClose, |
| GetOverviewSession()->window_drag_controller()->current_drag_behavior()); |
| // Drag-to-close mode affects the opacity of the whole overview item, |
| // including the unsnappable label. |
| EXPECT_EQ(unsnappable_overview_item->GetWindow()->layer()->opacity(), |
| unsnappable_layer->opacity()); |
| generator->ReleaseTouch(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| } |
| |
| // Verify that an item's unsnappable indicator is updated for display rotation. |
| TEST_F(SplitViewOverviewSessionTest, |
| OverviewUnsnappableIndicatorVisibilityAfterDisplayRotation) { |
| UpdateDisplay("900x800"); |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| // Because of its minimum size, |overview_window| is snappable in horizontal |
| // split view but not in vertical split view. |
| std::unique_ptr<aura::Window> overview_window( |
| CreateWindowWithMinimumSize(gfx::Rect(400, 600), gfx::Size(300, 500))); |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| OverviewItem* overview_item = GetOverviewItemForWindow(overview_window.get()); |
| // Note: |cannot_snap_label_view_| and its parent will be created on demand. |
| EXPECT_FALSE(overview_item->cannot_snap_widget_for_testing()); |
| |
| // Rotate to primary portrait orientation. The unsnappable indicator appears. |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .SetFirstDisplayAsInternalDisplay(); |
| ScreenOrientationControllerTestApi test_api( |
| Shell::Get()->screen_orientation_controller()); |
| test_api.SetDisplayRotation(display::Display::ROTATE_270, |
| display::Display::RotationSource::ACTIVE); |
| ASSERT_TRUE(overview_item->cannot_snap_widget_for_testing()); |
| ui::Layer* unsnappable_layer = |
| overview_item->cannot_snap_widget_for_testing()->GetLayer(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Rotate to primary landscape orientation. The unsnappable indicator hides. |
| test_api.SetDisplayRotation(display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| } |
| |
| // Test that when splitview mode and overview mode are both active at the same |
| // time, dragging divider behaviors are correct. |
| TEST_F(SplitViewOverviewSessionTest, DragDividerToExitTest) { |
| UpdateDisplay("907x407"); |
| |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| // Test that overview mode and split view mode are both active. |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Drag the divider toward closing the snapped window. |
| gfx::Rect divider_bounds = GetSplitViewDividerBounds(false /* is_dragging */); |
| split_view_controller()->StartResize(divider_bounds.CenterPoint()); |
| split_view_controller()->EndResize(gfx::Point(0, 0)); |
| SkipDividerSnapAnimation(); |
| |
| // Test that split view mode is ended. Overview mode is still active. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Now drag |window2| selector item to snap to left. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| DragWindowTo(overview_item2, gfx::PointF()); |
| // Test that overview mode and split view mode are both active. |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Drag the divider toward closing the overview window grid. |
| divider_bounds = GetSplitViewDividerBounds(false /*is_dragging=*/); |
| const gfx::Rect display_bounds = GetWorkAreaInScreen(window2.get()); |
| split_view_controller()->StartResize(divider_bounds.CenterPoint()); |
| split_view_controller()->EndResize(display_bounds.bottom_right()); |
| SkipDividerSnapAnimation(); |
| |
| // Test that split view mode is ended. Overview mode is also ended. |window2| |
| // should be activated. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(Shell::Get()->overview_controller()->InOverviewSession()); |
| EXPECT_EQ(window2.get(), window_util::GetActiveWindow()); |
| } |
| |
| TEST_F(SplitViewOverviewSessionTest, OverviewItemLongPressed) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| OverviewItem* overview_item = GetOverviewItemForWindow(window1.get()); |
| gfx::PointF start_location(overview_item->target_bounds().CenterPoint()); |
| const gfx::RectF original_bounds(overview_item->target_bounds()); |
| |
| // Verify that when a overview item receives a resetting gesture, we |
| // stay in overview mode and the bounds of the item are the same as they were |
| // before the press sequence started. |
| GetOverviewSession()->InitiateDrag(overview_item, start_location, |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->ResetDraggedWindowGesture(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(original_bounds, overview_item->target_bounds()); |
| |
| // Verify that when a overview item is tapped, we exit overview mode, |
| // and the current active window is the item. |
| GetOverviewSession()->InitiateDrag(overview_item, start_location, |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->ActivateDraggedWindow(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(window1.get(), window_util::GetActiveWindow()); |
| } |
| |
| TEST_F(SplitViewOverviewSessionTest, SnappedWindowBoundsTest) { |
| const gfx::Rect bounds(400, 400); |
| const int kMinimumBoundSize = 100; |
| const gfx::Size size(kMinimumBoundSize, kMinimumBoundSize); |
| |
| std::unique_ptr<aura::Window> window1( |
| CreateWindowWithMinimumSize(bounds, size)); |
| std::unique_ptr<aura::Window> window2( |
| CreateWindowWithMinimumSize(bounds, size)); |
| std::unique_ptr<aura::Window> window3( |
| CreateWindowWithMinimumSize(bounds, size)); |
| const int screen_width = |
| screen_util::GetDisplayWorkAreaBoundsInParent(window1.get()).width(); |
| ToggleOverview(); |
| |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Then drag the divider to left toward closing the snapped window. |
| gfx::Rect divider_bounds = GetSplitViewDividerBounds(false /*is_dragging=*/); |
| split_view_controller()->StartResize(divider_bounds.CenterPoint()); |
| // Drag the divider to a point that is close enough but still have a short |
| // distance to the edge of the screen. |
| split_view_controller()->EndResize(gfx::Point(20, 20)); |
| SkipDividerSnapAnimation(); |
| |
| // Test that split view mode is ended. Overview mode is still active. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| // Test that |window1| has the dimensions of a tablet mode maxed window, so |
| // that when it is placed back on the grid it will not look skinny. |
| EXPECT_LE(window1->bounds().x(), 0); |
| EXPECT_EQ(window1->bounds().width(), screen_width); |
| |
| // Drag |window2| selector item to snap to right. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| const gfx::Rect work_area_rect = GetWorkAreaInScreen(window2.get()); |
| gfx::Point end_location2 = |
| gfx::Point(work_area_rect.width(), work_area_rect.height()); |
| DragWindowTo(overview_item2, gfx::PointF(end_location2)); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller()->state()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| |
| // Then drag the divider to right toward closing the snapped window. |
| divider_bounds = GetSplitViewDividerBounds(false /* is_dragging */); |
| split_view_controller()->StartResize(divider_bounds.CenterPoint()); |
| // Drag the divider to a point that is close enough but still have a short |
| // distance to the edge of the screen. |
| end_location2.Offset(-20, -20); |
| split_view_controller()->EndResize(end_location2); |
| SkipDividerSnapAnimation(); |
| |
| // Test that split view mode is ended. Overview mode is still active. |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(Shell::Get()->overview_controller()->InOverviewSession()); |
| // Test that |window2| has the dimensions of a tablet mode maxed window, so |
| // that when it is placed back on the grid it will not look skinny. |
| EXPECT_GE(window2->bounds().x(), 0); |
| EXPECT_EQ(window2->bounds().width(), screen_width); |
| } |
| |
| // Test snapped window bounds with adjustment for the minimum size of a window. |
| TEST_F(SplitViewOverviewSessionTest, SnappedWindowBoundsWithMinimumSizeTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateTestWindow(bounds)); |
| const int work_area_length = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| Shell::GetPrimaryRootWindow()) |
| .width(); |
| std::unique_ptr<aura::Window> window2(CreateWindowWithMinimumSize( |
| bounds, gfx::Size(work_area_length / 3 + 20, 0))); |
| |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| split_view_controller()->StartResize( |
| GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint()); |
| split_view_controller()->EndResize(gfx::Point(work_area_length / 3, 10)); |
| SkipDividerSnapAnimation(); |
| // Use |EXPECT_NEAR| for reasons related to rounding and divider thickness. |
| EXPECT_NEAR( |
| work_area_length / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr) |
| .width(), |
| 8); |
| EXPECT_NEAR(work_area_length / 2, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| window2.get()) |
| .width(), |
| 8); |
| EXPECT_NEAR( |
| work_area_length * 2 / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr) |
| .width(), |
| 8); |
| EXPECT_NEAR(work_area_length * 2 / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| window2.get()) |
| .width(), |
| 8); |
| split_view_controller()->StartResize( |
| GetSplitViewDividerBounds(/*is_dragging=*/false).CenterPoint()); |
| split_view_controller()->EndResize(gfx::Point(work_area_length * 2 / 3, 10)); |
| EXPECT_NEAR( |
| work_area_length * 2 / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr) |
| .width(), |
| 8); |
| EXPECT_NEAR(work_area_length * 2 / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| window2.get()) |
| .width(), |
| 8); |
| EXPECT_NEAR( |
| work_area_length / 3, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr) |
| .width(), |
| 8); |
| EXPECT_NEAR(work_area_length / 2, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| window2.get()) |
| .width(), |
| 8); |
| } |
| |
| // Verify that if the split view divider is dragged all the way to the edge, the |
| // window being dragged gets returned to the overview list, if overview mode is |
| // still active. |
| TEST_F(SplitViewOverviewSessionTest, |
| DividerDraggedToEdgeReturnsWindowToOverviewList) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| // Drag |window1| selector item to snap to left. There should be two items on |
| // the overview grid afterwards, |window2| and |window3|. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| ASSERT_TRUE(split_view_controller()->split_view_divider()); |
| std::vector<aura::Window*> window_list = |
| GetOverviewController()->GetWindowsListInOverviewGridsForTest(); |
| EXPECT_EQ(2u, window_list.size()); |
| EXPECT_FALSE(base::Contains(window_list, window1.get())); |
| EXPECT_TRUE(wm::IsActiveWindow(window1.get())); |
| |
| // Drag the divider to the left edge. |
| const gfx::Rect divider_bounds = |
| GetSplitViewDividerBounds(/*is_dragging=*/false); |
| GetEventGenerator()->set_current_screen_location( |
| divider_bounds.CenterPoint()); |
| GetEventGenerator()->DragMouseTo(0, 0); |
| SkipDividerSnapAnimation(); |
| |
| // Verify that it is still in overview mode and that |window1| is returned to |
| // the overview list. |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| window_list = GetOverviewController()->GetWindowsListInOverviewGridsForTest(); |
| EXPECT_EQ(3u, window_list.size()); |
| EXPECT_TRUE(base::Contains(window_list, window1.get())); |
| EXPECT_FALSE(wm::IsActiveWindow(window1.get())); |
| } |
| |
| // Verify that if overview mode is active and the split view divider is dragged |
| // all the way to the opposite edge, then the split view window is reinserted |
| // into the overview grid at the correct position according to MRU order, and |
| // the stacking order is also correct. |
| TEST_F( |
| SplitViewOverviewSessionTest, |
| SplitViewWindowReinsertedToOverviewAtCorrectPositionWhenSplitViewIsEnded) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| ToggleOverview(); |
| DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF()); |
| DragWindowTo(GetOverviewItemForWindow(window2.get()), |
| gfx::PointF(799.f, 0.f)); |
| EXPECT_EQ(window1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(window2.get(), split_view_controller()->right_window()); |
| ToggleOverview(); |
| // Drag the divider to the left edge. |
| const gfx::Rect divider_bounds = |
| GetSplitViewDividerBounds(/*is_dragging=*/false); |
| GetEventGenerator()->set_current_screen_location( |
| divider_bounds.CenterPoint()); |
| GetEventGenerator()->DragMouseTo(0, 0); |
| SkipDividerSnapAnimation(); |
| |
| // Verify the grid arrangement. |
| ASSERT_TRUE(InOverviewSession()); |
| const std::vector<aura::Window*> expected_mru_list = { |
| window2.get(), window1.get(), window3.get()}; |
| const std::vector<aura::Window*> expected_overview_list = { |
| window2.get(), window1.get(), window3.get()}; |
| EXPECT_EQ( |
| expected_mru_list, |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)); |
| EXPECT_EQ(expected_overview_list, |
| GetOverviewController()->GetWindowsListInOverviewGridsForTest()); |
| |
| // Verify the stacking order. |
| aura::Window* parent = window1->parent(); |
| ASSERT_EQ(parent, window2->parent()); |
| ASSERT_EQ(parent, window3->parent()); |
| EXPECT_GT(IndexOf(GetOverviewItemForWindow(window2.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent), |
| IndexOf(GetOverviewItemForWindow(window1.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent)); |
| EXPECT_GT(IndexOf(GetOverviewItemForWindow(window1.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent), |
| IndexOf(GetOverviewItemForWindow(window3.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent)); |
| } |
| |
| // Verify that if a window is dragged from overview and snapped in place of |
| // another split view window, then the old split view window is reinserted into |
| // the overview grid at the correct position according to MRU order, and the |
| // stacking order is also correct. |
| TEST_F( |
| SplitViewOverviewSessionTest, |
| SplitViewWindowReinsertedToOverviewAtCorrectPositionWhenAnotherWindowTakesItsPlace) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window4(CreateWindow(bounds)); |
| ToggleOverview(); |
| DragWindowTo(GetOverviewItemForWindow(window1.get()), gfx::PointF()); |
| DragWindowTo(GetOverviewItemForWindow(window2.get()), |
| gfx::PointF(799.f, 0.f)); |
| EXPECT_EQ(window1.get(), split_view_controller()->left_window()); |
| EXPECT_EQ(window2.get(), split_view_controller()->right_window()); |
| ToggleOverview(); |
| DragWindowTo(GetOverviewItemForWindow(window3.get()), gfx::PointF()); |
| EXPECT_EQ(window3.get(), split_view_controller()->left_window()); |
| |
| // Verify the grid arrangement. |
| ASSERT_TRUE(InOverviewSession()); |
| const std::vector<aura::Window*> expected_mru_list = { |
| window3.get(), window2.get(), window1.get(), window4.get()}; |
| const std::vector<aura::Window*> expected_overview_list = { |
| window2.get(), window1.get(), window4.get()}; |
| EXPECT_EQ( |
| expected_mru_list, |
| Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk)); |
| EXPECT_EQ(expected_overview_list, |
| GetOverviewController()->GetWindowsListInOverviewGridsForTest()); |
| |
| // Verify the stacking order. |
| aura::Window* parent = window1->parent(); |
| ASSERT_EQ(parent, window2->parent()); |
| ASSERT_EQ(parent, window4->parent()); |
| EXPECT_GT(IndexOf(GetOverviewItemForWindow(window2.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent), |
| IndexOf(GetOverviewItemForWindow(window1.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent)); |
| EXPECT_GT(IndexOf(GetOverviewItemForWindow(window1.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent), |
| IndexOf(GetOverviewItemForWindow(window4.get()) |
| ->item_widget() |
| ->GetNativeWindow(), |
| parent)); |
| } |
| |
| // Verify that if the split view divider is dragged close to the edge, the grid |
| // bounds will be fixed to a third of the work area width and start sliding off |
| // the screen instead of continuing to shrink. |
| TEST_F(SplitViewOverviewSessionTest, |
| OverviewHasMinimumBoundsWhenDividerDragged) { |
| UpdateDisplay("600x400"); |
| |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| // Snap a window to the left and test dragging the divider towards the right |
| // edge of the screen. |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| OverviewGrid* grid = GetOverviewSession()->grid_list()[0].get(); |
| ASSERT_TRUE(grid); |
| |
| // Drag the divider to the right edge. |
| gfx::Rect divider_bounds = GetSplitViewDividerBounds(/*is_dragging=*/false); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location(divider_bounds.CenterPoint()); |
| generator->PressLeftButton(); |
| |
| // Tests that near the right edge, the grid bounds are fixed at 200 and are |
| // partially off screen to the right. |
| generator->MoveMouseTo(580, 0); |
| EXPECT_EQ(200, grid->bounds().width()); |
| EXPECT_GT(grid->bounds().right(), 600); |
| generator->ReleaseLeftButton(); |
| SkipDividerSnapAnimation(); |
| |
| // Releasing close to the edge will activate the left window and exit |
| // overview. |
| ASSERT_FALSE(InOverviewSession()); |
| ToggleOverview(); |
| // Snap a window to the right and test dragging the divider towards the left |
| // edge of the screen. |
| split_view_controller()->SnapWindow(window1.get(), |
| SplitViewController::RIGHT); |
| grid = GetOverviewSession()->grid_list()[0].get(); |
| ASSERT_TRUE(grid); |
| |
| // Drag the divider to the left edge. |
| divider_bounds = GetSplitViewDividerBounds(/*is_dragging=*/false); |
| generator->set_current_screen_location(divider_bounds.CenterPoint()); |
| generator->PressLeftButton(); |
| |
| generator->MoveMouseTo(20, 0); |
| // Tests that near the left edge, the grid bounds are fixed at 200 and are |
| // partially off screen to the left. |
| EXPECT_EQ(200, grid->bounds().width()); |
| EXPECT_LT(grid->bounds().x(), 0); |
| generator->ReleaseLeftButton(); |
| SkipDividerSnapAnimation(); |
| } |
| |
| // Test that when splitview mode is active, minimizing one of the snapped window |
| // will insert the minimized window back to overview mode if overview mode is |
| // active at the moment. |
| TEST_F(SplitViewOverviewSessionTest, InsertMinimizedWindowBackToOverview) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // Minimize |window1| will put |window1| back to overview grid. |
| WindowState::Get(window1.get())->Minimize(); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetOverviewItemForWindow(window1.get())); |
| |
| // Now snap both |window1| and |window2|. |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kBothSnapped); |
| EXPECT_EQ(split_view_controller()->left_window(), window1.get()); |
| EXPECT_EQ(split_view_controller()->right_window(), window2.get()); |
| |
| // Minimize |window1| will open overview and put |window1| to overview grid. |
| WindowState::Get(window1.get())->Minimize(); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kRightSnapped); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetOverviewItemForWindow(window1.get())); |
| |
| // Minimize |window2| also put |window2| to overview grid. |
| WindowState::Get(window2.get())->Minimize(); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(GetOverviewItemForWindow(window1.get())); |
| EXPECT_TRUE(GetOverviewItemForWindow(window2.get())); |
| } |
| |
| // Test that when splitview and overview are both active at the same time, if |
| // overview is ended due to snapping a window in splitview, the tranform of each |
| // window in the overview grid is restored. |
| TEST_F(SplitViewOverviewSessionTest, SnappedWindowAnimationObserverTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| |
| // There are four ways to exit overview mode. Verify in each case the |
| // tranform of each window in the overview window grid has been restored. |
| |
| // 1. Overview is ended by dragging a item in overview to snap to splitview. |
| // Drag |window1| selector item to snap to left. There should be two items on |
| // the overview grid afterwards, |window2| and |window3|. |
| ToggleOverview(); |
| EXPECT_FALSE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity()); |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| // Drag |window2| to snap to right. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| const gfx::Rect work_area_rect = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| window2.get()); |
| const gfx::PointF end_location2(work_area_rect.width(), 0); |
| DragWindowTo(overview_item2, end_location2); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity()); |
| |
| // 2. Overview is ended by ToggleOverview() directly. |
| // ToggleOverview() will open overview grid in the non-default side of the |
| // split screen. |
| ToggleOverview(); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| // ToggleOverview() directly. |
| ToggleOverview(); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity()); |
| |
| // 3. Overview is ended by actviating an existing window. |
| ToggleOverview(); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| wm::ActivateWindow(window2.get()); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity()); |
| |
| // 4. Overview is ended by activating a new window. |
| ToggleOverview(); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_FALSE(window3->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| std::unique_ptr<aura::Window> window4(CreateWindow(bounds)); |
| wm::ActivateWindow(window4.get()); |
| EXPECT_EQ(SplitViewController::State::kBothSnapped, |
| split_view_controller()->state()); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(window1->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window2->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window3->layer()->GetTargetTransform().IsIdentity()); |
| EXPECT_TRUE(window4->layer()->GetTargetTransform().IsIdentity()); |
| } |
| |
| // Test that when split view and overview are both active at the same time, |
| // double tapping on the divider can swap the window's position with the |
| // overview window grid's postion. |
| TEST_F(SplitViewOverviewSessionTest, SwapWindowAndOverviewGrid) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF()); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kLeftSnapped); |
| EXPECT_EQ(split_view_controller()->default_snap_position(), |
| SplitViewController::LEFT); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(GetGridBounds(), |
| ShrinkBoundsByHotseatInset( |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr))); |
| |
| split_view_controller()->SwapWindows(); |
| EXPECT_EQ(split_view_controller()->state(), |
| SplitViewController::State::kRightSnapped); |
| EXPECT_EQ(split_view_controller()->default_snap_position(), |
| SplitViewController::RIGHT); |
| EXPECT_EQ( |
| GetGridBounds(), |
| ShrinkBoundsByHotseatInset( |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr))); |
| } |
| |
| // Test that in tablet mode, pressing tab key in overview should not crash. |
| TEST_F(SplitViewOverviewSessionTest, NoCrashWhenPressTabKey) { |
| std::unique_ptr<aura::Window> window(CreateWindow(gfx::Rect(400, 400))); |
| std::unique_ptr<aura::Window> window2(CreateWindow(gfx::Rect(400, 400))); |
| |
| // In overview, there should be no crash when pressing tab key. |
| ToggleOverview(); |
| EXPECT_TRUE(InOverviewSession()); |
| SendKey(ui::VKEY_TAB); |
| EXPECT_TRUE(InOverviewSession()); |
| |
| // When splitview and overview are both active, there should be no crash when |
| // pressing tab key. |
| split_view_controller()->SnapWindow(window.get(), SplitViewController::LEFT); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| SendKey(ui::VKEY_TAB); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Tests closing a snapped window while in overview mode. |
| TEST_F(SplitViewOverviewSessionTest, ClosingSplitViewWindow) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| // Now close the snapped |window1|. We should remain in overview mode and the |
| // overview focus window should regain focus. |
| window1.reset(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_EQ(GetOverviewSession()->GetOverviewFocusWindow(), |
| window_util::GetFocusedWindow()); |
| } |
| |
| // Test that you cannot drag from overview during the split view divider |
| // animation. |
| TEST_F(SplitViewOverviewSessionTest, |
| CannotDragFromOverviewDuringSplitViewDividerAnimation) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| |
| gfx::Point divider_drag_point = |
| split_view_controller() |
| ->split_view_divider() |
| ->GetDividerBoundsInScreen(/*is_dragging=*/false) |
| .CenterPoint(); |
| split_view_controller()->StartResize(divider_drag_point); |
| divider_drag_point.Offset(20, 0); |
| split_view_controller()->Resize(divider_drag_point); |
| split_view_controller()->EndResize(divider_drag_point); |
| ASSERT_TRUE(IsDividerAnimating()); |
| |
| OverviewItem* overview_item = GetOverviewItemForWindow(overview_window.get()); |
| GetOverviewSession()->InitiateDrag( |
| overview_item, overview_item->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/true); |
| EXPECT_FALSE(overview_item->IsDragItem()); |
| } |
| |
| // Tests that a window which is dragged to a splitview zone is destroyed, the |
| // grid bounds return to a non-splitview bounds. |
| TEST_F(SplitViewOverviewSessionTest, GridBoundsAfterWindowDestroyed) { |
| // Create two windows otherwise we exit overview after one window is |
| // destroyed. |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| const gfx::Rect grid_bounds = GetGridBounds(); |
| // Drag the item such that the splitview preview area shows up and the grid |
| // bounds shrink. |
| OverviewItem* overview_item = GetOverviewItemForWindow(window1.get()); |
| GetOverviewSession()->InitiateDrag( |
| overview_item, overview_item->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/true); |
| GetOverviewSession()->Drag(overview_item, gfx::PointF(1.f, 1.f)); |
| EXPECT_NE(grid_bounds, GetGridBounds()); |
| |
| // Tests that when the dragged window is destroyed, the grid bounds return to |
| // their normal size. |
| window1.reset(); |
| EXPECT_EQ(grid_bounds, GetGridBounds()); |
| } |
| |
| // Tests that overview stays active if we have a snapped window. |
| TEST_F(SplitViewOverviewSessionTest, OnScreenLock) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| // Overview should exit if no snapped window after locking/unlocking. |
| ToggleOverview(); |
| GetSessionControllerClient()->LockScreen(); |
| GetSessionControllerClient()->UnlockScreen(); |
| ASSERT_FALSE(InOverviewSession()); |
| |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window2.get(), SplitViewController::LEFT); |
| |
| // Lock and unlock the machine. Test that we are still in overview and |
| // splitview. |
| GetSessionControllerClient()->LockScreen(); |
| GetSessionControllerClient()->UnlockScreen(); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| } |
| |
| // Verify that selecting an minimized snappable window while in split view |
| // triggers auto snapping. |
| TEST_F(SplitViewOverviewSessionTest, |
| SelectMinimizedSnappableWindowInSplitView) { |
| // Create two snappable windows. |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> minimized_window = CreateTestWindow(); |
| WindowState::Get(minimized_window.get())->Minimize(); |
| |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| |
| // Snap a window to enter split view mode. |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| |
| // Select the minimized window. |
| OverviewItem* overview_item = |
| GetOverviewItemForWindow(minimized_window.get()); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| gfx::ToRoundedPoint(overview_item->target_bounds().CenterPoint())); |
| generator->ClickLeftButton(); |
| |
| // Verify that both windows are in a snapped state and overview mode is ended. |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE( |
| split_view_controller()->IsWindowInSplitView(snapped_window.get())); |
| EXPECT_EQ( |
| split_view_controller()->GetPositionOfSnappedWindow(snapped_window.get()), |
| SplitViewController::LEFT); |
| EXPECT_TRUE( |
| split_view_controller()->IsWindowInSplitView(minimized_window.get())); |
| EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow( |
| minimized_window.get()), |
| SplitViewController::RIGHT); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_EQ(minimized_window.get(), window_util::GetActiveWindow()); |
| } |
| |
| // Verify no crash (or DCHECK failure) if you exit and re-enter mirror mode |
| // while in tablet split view with empty overview. |
| TEST_F(SplitViewOverviewSessionTest, |
| ExitAndReenterMirrorModeWithEmptyOverview) { |
| UpdateDisplay("800x600,800x600"); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window.get(), SplitViewController::LEFT); |
| display_manager()->SetMirrorMode(display::MirrorMode::kOff, absl::nullopt); |
| display_manager()->SetMirrorMode(display::MirrorMode::kNormal, absl::nullopt); |
| } |
| |
| // Tests that there is no crash when dragging the divider in portrait mode. |
| // Regression test for https://crbug.com/1267486. |
| TEST_F(SplitViewOverviewSessionTest, NoCrashWhenDraggingDividerInPortrait) { |
| // The crash only occured in portrait mode. |
| UpdateDisplay("600x800"); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| |
| ToggleOverview(); |
| // Note that this snaps `window1` to the top. |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| |
| // Drag the divider all the way to the bottom. There should be no crash. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| split_view_controller() |
| ->split_view_divider() |
| ->GetDividerBoundsInScreen(/*is_dragging=*/false) |
| .CenterPoint()); |
| generator->PressTouch(); |
| generator->MoveTouchBy(0, 600); |
| generator->ReleaseTouch(); |
| } |
| |
| // Test the split view and overview functionalities in clamshell mode. Split |
| // view is only active when overview is active in clamshell mode. |
| class SplitViewOverviewSessionInClamshellTest |
| : public SplitViewOverviewSessionTest { |
| public: |
| SplitViewOverviewSessionInClamshellTest() = default; |
| |
| SplitViewOverviewSessionInClamshellTest( |
| const SplitViewOverviewSessionInClamshellTest&) = delete; |
| SplitViewOverviewSessionInClamshellTest& operator=( |
| const SplitViewOverviewSessionInClamshellTest&) = delete; |
| |
| ~SplitViewOverviewSessionInClamshellTest() override = default; |
| |
| // AshTestBase: |
| void SetUp() override { |
| SplitViewOverviewSessionTest::SetUp(); |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); |
| DCHECK(ShouldAllowSplitView()); |
| } |
| |
| aura::Window* CreateWindowWithHitTestComponent(int hit_test_component, |
| const gfx::Rect& bounds) { |
| return CreateTestWindowInShellWithDelegate( |
| new TestWindowHitTestDelegate(hit_test_component), 0, bounds); |
| } |
| |
| private: |
| class TestWindowHitTestDelegate : public aura::test::TestWindowDelegate { |
| public: |
| explicit TestWindowHitTestDelegate(int hit_test_component) { |
| set_window_component(hit_test_component); |
| } |
| |
| TestWindowHitTestDelegate(const TestWindowHitTestDelegate&) = delete; |
| TestWindowHitTestDelegate& operator=(const TestWindowHitTestDelegate&) = |
| delete; |
| |
| ~TestWindowHitTestDelegate() override = default; |
| |
| private: |
| // aura::Test::TestWindowDelegate: |
| void OnWindowDestroyed(aura::Window* window) override { delete this; } |
| }; |
| }; |
| |
| // Test some basic functionalities in clamshell splitview mode. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, BasicFunctionalitiesTest) { |
| UpdateDisplay("600x400"); |
| EXPECT_FALSE(Shell::Get()->tablet_mode_controller()->InTabletMode()); |
| |
| // 1. Test the 1 window scenario. |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| WindowState* window_state1 = WindowState::Get(window1.get()); |
| EXPECT_FALSE(window_state1->IsSnapped()); |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| // Since the only window is snapped, overview and splitview should be both |
| // ended. |
| EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 2. Test if one window is snapped, the other windows are showing in |
| // overview, close all windows in overview will end overview and also |
| // splitview. |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| ToggleOverview(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(600, 300)); |
| // SplitView and overview are both active at the moment. |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get())); |
| EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview( |
| window2.get())); |
| EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kSecondarySnapped); |
| // Close |window2| will end overview and splitview. |
| window2.reset(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 3. Test that snap 2 windows will end overview and splitview. |
| std::unique_ptr<aura::Window> window3(CreateWindow(bounds)); |
| ToggleOverview(); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| OverviewItem* overview_item3 = GetOverviewItemForWindow(window3.get()); |
| DragWindowTo(overview_item3, gfx::PointF(600, 300)); |
| EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped); |
| EXPECT_EQ(WindowState::Get(window3.get())->GetStateType(), |
| WindowStateType::kSecondarySnapped); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 4. Test if one window is snapped, the other windows are showing in |
| // overview, we can drag another window in overview to snap in splitview, and |
| // the previous snapped window will be put back into overview. |
| std::unique_ptr<aura::Window> window4(CreateWindow(bounds)); |
| ToggleOverview(); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_FALSE(GetOverviewController()->overview_session()->IsWindowInOverview( |
| window1.get())); |
| overview_item3 = GetOverviewItemForWindow(window3.get()); |
| DragWindowTo(overview_item3, gfx::PointF(0, 0)); |
| EXPECT_FALSE(GetOverviewController()->overview_session()->IsWindowInOverview( |
| window3.get())); |
| EXPECT_TRUE(GetOverviewController()->overview_session()->IsWindowInOverview( |
| window1.get())); |
| EXPECT_EQ(window_state1->GetStateType(), WindowStateType::kPrimarySnapped); |
| EXPECT_EQ(WindowState::Get(window3.get())->GetStateType(), |
| WindowStateType::kPrimarySnapped); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| // End overview, test that we'll not auto-snap a window to the right side of |
| // the screen. |
| EXPECT_EQ(WindowState::Get(window4.get())->GetStateType(), |
| WindowStateType::kDefault); |
| ToggleOverview(); |
| EXPECT_EQ(WindowState::Get(window4.get())->GetStateType(), |
| WindowStateType::kDefault); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 5. Test if one window is snapped, the other window are showing in overview, |
| // activating an new window will not auto-snap the new window. Overview and |
| // splitview should be ended. |
| ToggleOverview(); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| std::unique_ptr<aura::Window> window5(CreateWindow(bounds)); |
| EXPECT_EQ(WindowState::Get(window5.get())->GetStateType(), |
| WindowStateType::kDefault); |
| wm::ActivateWindow(window5.get()); |
| EXPECT_EQ(WindowState::Get(window5.get())->GetStateType(), |
| WindowStateType::kDefault); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 6. Test if one window is snapped, the other window is showing in overview, |
| // close the snapped window will end split view, but overview is still active. |
| ToggleOverview(); |
| const gfx::Rect overview_bounds = GetGridBounds(); |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_NE(GetGridBounds(), overview_bounds); |
| EXPECT_EQ(GetGridBounds(), GetSplitViewRightWindowBounds()); |
| window1.reset(); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| // Overview bounds will adjust from snapped bounds to fullscreen bounds. |
| EXPECT_EQ(GetGridBounds(), overview_bounds); |
| |
| // 7. Test if split view mode is active, open the app list will end both |
| // overview and splitview. |
| overview_item3 = GetOverviewItemForWindow(window3.get()); |
| DragWindowTo(overview_item3, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| // Open app list. |
| AppListControllerImpl* app_list_controller = |
| Shell::Get()->app_list_controller(); |
| app_list_controller->ToggleAppList( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(window3.get()).id(), |
| AppListShowSource::kSearchKey, base::TimeTicks()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // 8. Test if splitview is not active, open the app list will end overview if |
| // overview is active. |
| ToggleOverview(); |
| // Open app list. |
| app_list_controller->ToggleAppList( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(window3.get()).id(), |
| AppListShowSource::kSearchKey, base::TimeTicks()); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| // Test overview exit animation histograms when you drag to snap two windows on |
| // opposite sides. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| BothSnappedOverviewExitAnimationHistogramTest) { |
| ui::ScopedAnimationDurationScaleMode anmatin_scale( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> left_window(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> right_window(CreateWindow(bounds)); |
| CheckOverviewEnterExitHistogram("Init", {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}); |
| |
| ToggleOverview(); |
| WaitForOverviewEnterAnimation(); |
| CheckOverviewEnterExitHistogram("EnterOverview", {1, 0, 0, 0, 0}, |
| {0, 0, 0, 0, 0}); |
| |
| DragWindowTo(GetOverviewItemForWindow(left_window.get()), gfx::PointF(0, 0)); |
| DragWindowTo(GetOverviewItemForWindow(right_window.get()), |
| gfx::PointF(799, 300)); |
| WaitForOverviewExitAnimation(); |
| CheckOverviewEnterExitHistogram("SnapBothSides", {1, 0, 0, 0, 0}, |
| {1, 0, 0, 0, 1}); |
| } |
| |
| // Test that when overview and splitview are both active, only resize that |
| // happens on eligible window components will change snapped window bounds and |
| // overview bounds at the same time. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, ResizeWindowTest) { |
| UpdateDisplay("600x400"); |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds)); |
| std::unique_ptr<aura::Window> window2( |
| CreateWindowWithHitTestComponent(HTLEFT, bounds)); |
| std::unique_ptr<aura::Window> window3( |
| CreateWindowWithHitTestComponent(HTTOP, bounds)); |
| std::unique_ptr<aura::Window> window4( |
| CreateWindowWithHitTestComponent(HTBOTTOM, bounds)); |
| |
| ToggleOverview(); |
| gfx::Rect overview_full_bounds = GetGridBounds(); |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_NE(GetGridBounds(), overview_full_bounds); |
| EXPECT_EQ(GetGridBounds(), GetSplitViewRightWindowBounds()); |
| gfx::Rect overview_snapped_bounds = GetGridBounds(); |
| |
| // Resize that happens on the right edge of the left snapped window will |
| // resize the window and overview at the same time. |
| ui::test::EventGenerator generator1(Shell::GetPrimaryRootWindow(), |
| window1.get()); |
| generator1.PressLeftButton(); |
| CheckWindowResizingPerformanceHistograms("BeforeResizingLeftSnappedWindow1", |
| 0, 0, 0, 0); |
| generator1.MoveMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("WhileResizingLeftSnappedWindow1", 0, |
| 0, 1, 0); |
| generator1.ReleaseLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow1", 0, |
| 0, 1, 1); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_NE(GetGridBounds(), overview_full_bounds); |
| EXPECT_NE(GetGridBounds(), overview_snapped_bounds); |
| EXPECT_EQ(GetGridBounds(), GetSplitViewRightWindowBounds()); |
| |
| // Resize that happens on the left edge of the left snapped window will end |
| // overview. The same for the resize that happens on the top or bottom edge of |
| // the left snapped window. |
| OverviewItem* overview_item2 = GetOverviewItemForWindow(window2.get()); |
| DragWindowTo(overview_item2, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| ui::test::EventGenerator generator2(Shell::GetPrimaryRootWindow(), |
| window2.get()); |
| generator2.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow2", 0, |
| 0, 1, 1); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item3 = GetOverviewItemForWindow(window3.get()); |
| DragWindowTo(overview_item3, gfx::PointF(0, 0)); |
| ui::test::EventGenerator generator3(Shell::GetPrimaryRootWindow(), |
| window3.get()); |
| generator3.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow3", 0, |
| 0, 1, 1); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item4 = GetOverviewItemForWindow(window4.get()); |
| DragWindowTo(overview_item4, gfx::PointF(0, 0)); |
| ui::test::EventGenerator generator4(Shell::GetPrimaryRootWindow(), |
| window4.get()); |
| generator4.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingLeftSnappedWindow4", 0, |
| 0, 1, 1); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| // Now try snapping on the right. |
| ToggleOverview(); |
| overview_full_bounds = GetGridBounds(); |
| overview_item2 = GetOverviewItemForWindow(window2.get()); |
| DragWindowTo(overview_item2, gfx::PointF(599, 0)); |
| EXPECT_NE(GetGridBounds(), overview_full_bounds); |
| EXPECT_EQ(GetGridBounds(), GetSplitViewLeftWindowBounds()); |
| overview_snapped_bounds = GetGridBounds(); |
| |
| ui::test::EventGenerator generator5(Shell::GetPrimaryRootWindow(), |
| window2.get()); |
| generator5.PressLeftButton(); |
| CheckWindowResizingPerformanceHistograms("BeforeResizingRightSnappedWindow2", |
| 0, 0, 1, 1); |
| generator5.MoveMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("WhileResizingRightSnappedWindow2", |
| 0, 0, 2, 1); |
| generator5.ReleaseLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow2", |
| 0, 0, 2, 2); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_NE(GetGridBounds(), overview_full_bounds); |
| EXPECT_NE(GetGridBounds(), overview_snapped_bounds); |
| EXPECT_EQ(GetGridBounds(), GetSplitViewLeftWindowBounds()); |
| |
| overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(599, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| ui::test::EventGenerator generator6(Shell::GetPrimaryRootWindow(), |
| window1.get()); |
| generator6.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow1", |
| 0, 0, 2, 2); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| ToggleOverview(); |
| overview_item3 = GetOverviewItemForWindow(window3.get()); |
| DragWindowTo(overview_item3, gfx::PointF(599, 0)); |
| ui::test::EventGenerator generator7(Shell::GetPrimaryRootWindow(), |
| window3.get()); |
| generator7.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow3", |
| 0, 0, 2, 2); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| |
| ToggleOverview(); |
| overview_item4 = GetOverviewItemForWindow(window4.get()); |
| DragWindowTo(overview_item4, gfx::PointF(599, 0)); |
| ui::test::EventGenerator generator8(Shell::GetPrimaryRootWindow(), |
| window4.get()); |
| generator8.DragMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("AfterResizingRightSnappedWindow4", |
| 0, 0, 2, 2); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| // Test closing the split view window while resizing it. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| CloseWindowWhileResizingItTest) { |
| UpdateDisplay("600x400"); |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> split_view_window( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds)); |
| std::unique_ptr<aura::Window> overview_window(CreateWindow(bounds)); |
| ToggleOverview(); |
| DragWindowTo(GetOverviewItemForWindow(split_view_window.get()), |
| gfx::PointF(0.f, 0.f)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), |
| split_view_window.get()); |
| generator.PressLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterPressingMouseButton", 0, 0, 0, |
| 0); |
| generator.MoveMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("WhileResizing", 0, 0, 1, 0); |
| split_view_window.reset(); |
| CheckWindowResizingPerformanceHistograms("AfterClosing", 0, 0, 1, 1); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| generator.ReleaseLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterReleasingMouseButton", 0, 0, 1, |
| 1); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| class TestWindowStateDelegate : public WindowStateDelegate { |
| public: |
| TestWindowStateDelegate() = default; |
| TestWindowStateDelegate(const TestWindowStateDelegate&) = delete; |
| TestWindowStateDelegate& operator=(const TestWindowStateDelegate&) = delete; |
| ~TestWindowStateDelegate() override = default; |
| |
| // WindowStateDelegate: |
| std::unique_ptr<PresentationTimeRecorder> OnDragStarted( |
| int component) override { |
| drag_in_progress_ = true; |
| return nullptr; |
| } |
| void OnDragFinished(bool cancel, const gfx::PointF& location) override { |
| drag_in_progress_ = false; |
| } |
| |
| bool drag_in_progress() { return drag_in_progress_; } |
| |
| private: |
| bool drag_in_progress_ = false; |
| }; |
| |
| // Tests that when a split view window carries over to clamshell split view |
| // while the divider is being dragged, the window resize is properly completed. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| CarryOverToClamshellSplitViewWhileResizing) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| TestWindowStateDelegate* snapped_window_state_delegate = |
| new TestWindowStateDelegate(); |
| snapped_window_state->SetDelegate( |
| base::WrapUnique(snapped_window_state_delegate)); |
| |
| // Enter clamshell split view and then switch to tablet mode. |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| EnterTabletMode(); |
| ASSERT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| ASSERT_EQ(snapped_window.get(), split_view_controller()->left_window()); |
| |
| // Start dragging the divider. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->set_current_screen_location( |
| split_view_controller() |
| ->split_view_divider() |
| ->GetDividerBoundsInScreen(/*is_dragging=*/false) |
| .CenterPoint()); |
| generator->PressTouch(); |
| generator->MoveTouchBy(5, 0); |
| EXPECT_TRUE(snapped_window_state_delegate->drag_in_progress()); |
| EXPECT_NE(nullptr, snapped_window_state->drag_details()); |
| |
| // End tablet mode. |
| Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false); |
| ASSERT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| ASSERT_EQ(snapped_window.get(), split_view_controller()->left_window()); |
| EXPECT_FALSE(snapped_window_state_delegate->drag_in_progress()); |
| EXPECT_EQ(nullptr, snapped_window_state->drag_details()); |
| } |
| |
| // Test that overview and clamshell split view end if you double click the edge |
| // of the split view window where it meets the overview grid. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, HorizontalMaximizeTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> snapped_window( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds)); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(bounds); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| ui::test::EventGenerator(Shell::GetPrimaryRootWindow(), snapped_window.get()) |
| .DoubleClickLeftButton(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| // Test that when laptop splitview mode is active, moving the snapped window |
| // will end splitview and overview at the same time. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, MoveWindowTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1( |
| CreateWindowWithHitTestComponent(HTCAPTION, bounds)); |
| std::unique_ptr<aura::Window> window2( |
| CreateWindowWithHitTestComponent(HTCAPTION, bounds)); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| ui::test::EventGenerator generator1(Shell::GetPrimaryRootWindow(), |
| window1.get()); |
| generator1.DragMouseBy(50, 50); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| // Test that in clamshell splitview mode, if the snapped window is minimized, |
| // splitview mode and overview mode are both ended. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, MinimizedWindowTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1(CreateWindow(bounds)); |
| std::unique_ptr<aura::Window> window2(CreateWindow(bounds)); |
| |
| ToggleOverview(); |
| // Drag |window1| selector item to snap to left. |
| OverviewItem* overview_item1 = GetOverviewItemForWindow(window1.get()); |
| DragWindowTo(overview_item1, gfx::PointF(0, 0)); |
| EXPECT_TRUE(GetOverviewController()->InOverviewSession()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| |
| // Now minimize the snapped |window1|. |
| WindowState::Get(window1.get())->Minimize(); |
| EXPECT_FALSE(GetOverviewController()->InOverviewSession()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| } |
| |
| // Test snapped window bounds with adjustment for the minimum size of a window. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| SnappedWindowBoundsWithMinimumSizeTest) { |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> window1( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds)); |
| const int window2_minimum_size = 350; |
| std::unique_ptr<aura::Window> window2( |
| CreateWindowWithMinimumSize(bounds, gfx::Size(window2_minimum_size, 0))); |
| |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(window1.get(), SplitViewController::LEFT); |
| ui::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), |
| window1.get()); |
| int divider_position = split_view_controller()->divider_position(); |
| generator.MoveMouseTo(divider_position, 10); |
| divider_position = 300; |
| generator.DragMouseTo(divider_position, 10); |
| EXPECT_EQ(divider_position, split_view_controller() |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr) |
| .width()); |
| EXPECT_EQ(window2_minimum_size, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| window2.get()) |
| .width()); |
| const int work_area_length = |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| window1.get()) |
| .width(); |
| EXPECT_EQ( |
| work_area_length - divider_position, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr) |
| .width()); |
| EXPECT_EQ(work_area_length - divider_position, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| window2.get()) |
| .width()); |
| divider_position = 500; |
| generator.DragMouseTo(divider_position, 10); |
| EXPECT_EQ(divider_position, split_view_controller() |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr) |
| .width()); |
| EXPECT_EQ(divider_position, split_view_controller() |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, window2.get()) |
| .width()); |
| EXPECT_EQ( |
| work_area_length - divider_position, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr) |
| .width()); |
| EXPECT_EQ(window2_minimum_size, |
| split_view_controller() |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| window2.get()) |
| .width()); |
| } |
| |
| // Tests that on a display in portrait orientation, clamshell split view still |
| // uses snap positions on the left and right. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| PortraitClamshellSplitViewSnapPositionsTest) { |
| UpdateDisplay("800x600/l"); |
| const int height = 800 - ShelfConfig::Get()->shelf_size(); |
| ASSERT_EQ(gfx::Rect(0, 0, 600, height), |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| Shell::GetPrimaryRootWindow())); |
| // Check that snapped window bounds represent snapping on the left and right. |
| const gfx::Rect top_snapped_bounds(600, height / 2); |
| const gfx::Rect bottom_snapped_bounds(0, height / 2, 600, height / 2); |
| const gfx::Rect left_snapped_bounds(300, height); |
| const gfx::Rect right_snapped_bounds(300, 0, 300, height); |
| EXPECT_EQ( |
| top_snapped_bounds, |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr)); |
| EXPECT_EQ( |
| bottom_snapped_bounds, |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr)); |
| |
| // Switch from clamshell mode to tablet mode and then back to clamshell mode. |
| display::test::DisplayManagerTestApi(Shell::Get()->display_manager()) |
| .SetFirstDisplayAsInternalDisplay(); |
| TabletModeControllerTestApi tablet_mode_controller_test_api; |
| tablet_mode_controller_test_api.DetachAllMice(); |
| EXPECT_FALSE(tablet_mode_controller_test_api.IsTabletModeStarted()); |
| tablet_mode_controller_test_api.OpenLidToAngle(315.0f); |
| EXPECT_TRUE(tablet_mode_controller_test_api.IsTabletModeStarted()); |
| tablet_mode_controller_test_api.OpenLidToAngle(90.0f); |
| EXPECT_FALSE(tablet_mode_controller_test_api.IsTabletModeStarted()); |
| // Check the snapped window bounds again. They should be the same as before. |
| EXPECT_EQ( |
| top_snapped_bounds, |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr)); |
| EXPECT_EQ( |
| bottom_snapped_bounds, |
| split_view_controller()->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr)); |
| } |
| |
| // Tests that the ratio between the divider position and the work area width is |
| // the same before and after changing the display orientation in clamshell mode. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, DisplayOrientationChangeTest) { |
| UpdateDisplay("600x400"); |
| const gfx::Rect bounds(400, 400); |
| std::unique_ptr<aura::Window> split_view_window( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds)); |
| std::unique_ptr<aura::Window> overview_window(CreateWindow(bounds)); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(split_view_window.get(), |
| SplitViewController::LEFT); |
| const auto test_many_orientation_changes = |
| [this](const std::string& description) { |
| SCOPED_TRACE(description); |
| for (display::Display::Rotation rotation : |
| {display::Display::ROTATE_270, display::Display::ROTATE_180, |
| display::Display::ROTATE_90, display::Display::ROTATE_0, |
| display::Display::ROTATE_180, display::Display::ROTATE_0}) { |
| const auto compute_divider_position_ratio = [this]() { |
| const display::Display& display = |
| display::Screen::GetScreen()->GetPrimaryDisplay(); |
| const bool is_horizontal = |
| SplitViewController::IsLayoutHorizontal(display); |
| const gfx::Rect work_area = display.work_area(); |
| const int size = |
| is_horizontal ? work_area.width() : work_area.height(); |
| return static_cast<float>( |
| split_view_controller()->divider_position()) / |
| static_cast<float>(size); |
| }; |
| const float before = compute_divider_position_ratio(); |
| Shell::Get()->display_manager()->SetDisplayRotation( |
| display::Screen::GetScreen()->GetPrimaryDisplay().id(), rotation, |
| display::Display::RotationSource::ACTIVE); |
| const float after = compute_divider_position_ratio(); |
| EXPECT_NEAR(before, after, 0.001f); |
| } |
| }; |
| EXPECT_EQ(split_view_controller()->GetDefaultDividerPosition(), |
| split_view_controller()->divider_position()); |
| test_many_orientation_changes("centered divider"); |
| EXPECT_EQ(split_view_controller()->GetDefaultDividerPosition(), |
| split_view_controller()->divider_position()); |
| ui::test::EventGenerator(Shell::GetPrimaryRootWindow(), |
| split_view_window.get()) |
| .DragMouseBy(50, 50); |
| EXPECT_NE(split_view_controller()->GetDefaultDividerPosition(), |
| split_view_controller()->divider_position()); |
| test_many_orientation_changes("off-center divider"); |
| } |
| |
| // Verify that an item's unsnappable indicator is updated for display rotation. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| OverviewUnsnappableIndicatorVisibilityAfterDisplayRotation) { |
| UpdateDisplay("900x600"); |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| // Because of its minimum size, |overview_window| is snappable in clamshell |
| // split view with landscape display orientation but not with portrait display |
| // orientation. |
| std::unique_ptr<aura::Window> overview_window( |
| CreateWindowWithMinimumSize(gfx::Rect(400, 400), gfx::Size(400, 500))); |
| ToggleOverview(); |
| ASSERT_TRUE(GetOverviewController()->InOverviewSession()); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| ASSERT_TRUE(split_view_controller()->InSplitViewMode()); |
| OverviewItem* overview_item = GetOverviewItemForWindow(overview_window.get()); |
| // Note: |cannot_snap_label_view_| and its parent will be created on demand. |
| EXPECT_FALSE(overview_item->cannot_snap_widget_for_testing()); |
| |
| // Rotate to primary portrait orientation. The unsnappable indicator appears. |
| display::DisplayManager* display_manager = Shell::Get()->display_manager(); |
| const int64_t display_id = |
| display::Screen::GetScreen()->GetPrimaryDisplay().id(); |
| display_manager->SetDisplayRotation(display_id, display::Display::ROTATE_270, |
| display::Display::RotationSource::ACTIVE); |
| ASSERT_TRUE(overview_item->cannot_snap_widget_for_testing()); |
| ui::Layer* unsnappable_layer = |
| overview_item->cannot_snap_widget_for_testing()->GetLayer(); |
| EXPECT_EQ(1.f, unsnappable_layer->opacity()); |
| |
| // Rotate to primary landscape orientation. The unsnappable indicator hides. |
| display_manager->SetDisplayRotation(display_id, display::Display::ROTATE_0, |
| display::Display::RotationSource::ACTIVE); |
| EXPECT_EQ(0.f, unsnappable_layer->opacity()); |
| } |
| |
| // Tests that dragging a window from overview creates a drop target on the same |
| // display, even if the window bounds are mostly on another display. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| DragFromOverviewWithBoundsMostlyOnAnotherDisplay) { |
| UpdateDisplay("700x600,700x600"); |
| const aura::Window::Windows root_windows = Shell::Get()->GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const display::DisplayIdList display_ids = |
| display_manager()->GetConnectedDisplayIdList(); |
| ASSERT_EQ(2u, display_ids.size()); |
| ASSERT_EQ(root_windows[0], Shell::GetRootWindowForDisplayId(display_ids[0])); |
| ASSERT_EQ(root_windows[1], Shell::GetRootWindowForDisplayId(display_ids[1])); |
| |
| display::Screen* screen = display::Screen::GetScreen(); |
| const gfx::Rect creation_bounds(0, 0, 600, 600); |
| ASSERT_EQ(display_ids[0], screen->GetDisplayMatching(creation_bounds).id()); |
| const gfx::Rect bounds(550, 0, 600, 600); |
| ASSERT_EQ(display_ids[1], screen->GetDisplayMatching(bounds).id()); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(creation_bounds); |
| window->SetBoundsInScreen(bounds, |
| display_manager()->GetDisplayForId(display_ids[0])); |
| |
| ToggleOverview(); |
| OverviewItem* overview_item = GetOverviewItemForWindow(window.get()); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| gfx::PointF drag_point = overview_item->target_bounds().CenterPoint(); |
| GetOverviewSession()->InitiateDrag(overview_item, drag_point, |
| /*is_touch_dragging=*/false); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| drag_point.Offset(5.f, 0.f); |
| GetOverviewSession()->Drag(overview_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(1)); |
| ASSERT_TRUE(GetDropTarget(0)); |
| EXPECT_EQ(root_windows[0], GetDropTarget(0)->root_window()); |
| GetOverviewSession()->CompleteDrag(overview_item, drag_point); |
| EXPECT_FALSE(GetDropTarget(0)); |
| EXPECT_FALSE(GetDropTarget(1)); |
| } |
| |
| // Tests that Alt+[ and Alt+] do not start overview. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| AltSquareBracketNotStartOverview) { |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(); |
| wm::ActivateWindow(window1.get()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| // Alt+[ |
| const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| WindowState* window1_state = WindowState::Get(window1.get()); |
| window1_state->OnWMEvent(&alt_left_square_bracket); |
| EXPECT_EQ(WindowStateType::kPrimarySnapped, window1_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| // Alt+] |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| window1_state->OnWMEvent(&alt_right_square_bracket); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, window1_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests using Alt+[ on a left split view window. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| AltLeftSquareBracketOnLeftSplitViewWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::LEFT); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| EXPECT_EQ(WindowStateType::kPrimarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(InOverviewSession()); |
| const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| snapped_window_state->OnWMEvent(&alt_left_square_bracket); |
| EXPECT_EQ(WindowStateType::kNormal, snapped_window_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests using Alt+] on a right split view window. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| AltRightSquareBracketOnRightSplitViewWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::RIGHT); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_TRUE(split_view_controller()->InSplitViewMode()); |
| EXPECT_TRUE(InOverviewSession()); |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| snapped_window_state->OnWMEvent(&alt_right_square_bracket); |
| EXPECT_EQ(WindowStateType::kNormal, snapped_window_state->GetStateType()); |
| EXPECT_FALSE(split_view_controller()->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Tests using Alt+[ on a right split view window, and Alt+] on a left split |
| // view window. |
| TEST_F(SplitViewOverviewSessionInClamshellTest, |
| AltSquareBracketOnSplitViewWindow) { |
| std::unique_ptr<aura::Window> snapped_window = CreateTestWindow(); |
| std::unique_ptr<aura::Window> overview_window = CreateTestWindow(); |
| // Enter clamshell split view with |snapped_window| on the right. |
| ToggleOverview(); |
| split_view_controller()->SnapWindow(snapped_window.get(), |
| SplitViewController::RIGHT); |
| wm::ActivateWindow(snapped_window.get()); |
| WindowState* snapped_window_state = WindowState::Get(snapped_window.get()); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(snapped_window.get(), split_view_controller()->right_window()); |
| EXPECT_TRUE(InOverviewSession()); |
| // Test using Alt+[ to put |snapped_window| on the left. |
| const WindowSnapWMEvent alt_left_square_bracket(WM_EVENT_CYCLE_SNAP_PRIMARY); |
| snapped_window_state->OnWMEvent(&alt_left_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get())); |
| EXPECT_EQ(WindowStateType::kPrimarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(snapped_window.get(), split_view_controller()->left_window()); |
| EXPECT_TRUE(InOverviewSession()); |
| // Test using Alt+] to put |snapped_window| on the right. |
| const WindowSnapWMEvent alt_right_square_bracket( |
| WM_EVENT_CYCLE_SNAP_SECONDARY); |
| snapped_window_state->OnWMEvent(&alt_right_square_bracket); |
| EXPECT_TRUE(wm::IsActiveWindow(snapped_window.get())); |
| EXPECT_EQ(WindowStateType::kSecondarySnapped, |
| snapped_window_state->GetStateType()); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller()->state()); |
| EXPECT_EQ(snapped_window.get(), split_view_controller()->right_window()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| using SplitViewOverviewSessionInClamshellTestMultiDisplayOnly = |
| SplitViewOverviewSessionInClamshellTest; |
| |
| // Test |SplitViewController::Get|. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| GetSplitViewController) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2); |
| EXPECT_EQ(root_windows[0], |
| SplitViewController::Get(window1.get())->root_window()); |
| EXPECT_EQ(root_windows[1], |
| SplitViewController::Get(window2.get())->root_window()); |
| } |
| |
| // Test |SplitViewController::GetSnappedWindowBoundsInScreen|. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| GetSnappedBounds) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const int height = 600 - ShelfConfig::Get()->shelf_size(); |
| ASSERT_EQ(gfx::Rect(0, 0, 800, height), |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| root_windows[0])); |
| ASSERT_EQ(gfx::Rect(800, 0, 800, height), |
| screen_util::GetDisplayWorkAreaBoundsInScreenForActiveDeskContainer( |
| root_windows[1])); |
| |
| EXPECT_EQ( |
| gfx::Rect(0, 0, 400, height), |
| SplitViewController::Get(root_windows[0]) |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr)); |
| EXPECT_EQ( |
| gfx::Rect(400, 0, 400, height), |
| SplitViewController::Get(root_windows[0]) |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr)); |
| EXPECT_EQ( |
| gfx::Rect(800, 0, 400, height), |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::LEFT, /*window_for_minimum_size=*/nullptr)); |
| EXPECT_EQ( |
| gfx::Rect(1200, 0, 400, height), |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen( |
| SplitViewController::RIGHT, /*window_for_minimum_size=*/nullptr)); |
| } |
| |
| // Test that if clamshell split view is started by snapping a window that is the |
| // only overview window, then split view ends as soon as it starts, and overview |
| // ends along with it. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| SplitViewEndsImmediatelyIfOverviewIsEmpty) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(bounds_within_root1); |
| ToggleOverview(); |
| SplitViewController::Get(root_windows[0]) |
| ->SnapWindow(window.get(), SplitViewController::LEFT); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| } |
| |
| // Test that if clamshell split view is started by snapping a window on one |
| // display while there is an overview window on another display, then split view |
| // stays active (instead of ending as soon as it starts), and overview also |
| // stays active. Then close the overview window and verify that split view and |
| // overview are ended. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| SplitViewViableWithOverviewWindowOnOtherDisplay) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2); |
| ToggleOverview(); |
| SplitViewController::Get(root_windows[0]) |
| ->SnapWindow(window1.get(), SplitViewController::LEFT); |
| EXPECT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| EXPECT_TRUE(InOverviewSession()); |
| window2.reset(); |
| EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| EXPECT_FALSE(InOverviewSession()); |
| } |
| |
| // Test dragging to snap an overview item on an external display. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DraggingOnExternalDisplay) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2); |
| ToggleOverview(); |
| OverviewGrid* grid_on_root2 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[1]); |
| OverviewItem* item1 = grid_on_root2->GetOverviewItemContaining(window1.get()); |
| OverviewItem* item2 = grid_on_root2->GetOverviewItemContaining(window2.get()); |
| SplitViewController* split_view_controller = |
| SplitViewController::Get(root_windows[1]); |
| SplitViewDragIndicators* indicators = |
| grid_on_root2->split_view_drag_indicators(); |
| |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1])); |
| GetOverviewSession()->InitiateDrag(item1, |
| item1->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| const gfx::PointF right_snap_point(1599.f, 300.f); |
| GetOverviewSession()->Drag(item1, right_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapRight, |
| indicators->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root2->bounds()); |
| GetOverviewSession()->CompleteDrag(item1, right_snap_point); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller->state()); |
| EXPECT_EQ(window1.get(), split_view_controller->right_window()); |
| |
| GetOverviewSession()->InitiateDrag(item2, |
| item2->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| const gfx::PointF left_of_middle(1150.f, 300.f); |
| GetOverviewSession()->Drag(item2, left_of_middle); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| indicators->current_window_dragging_state()); |
| GetOverviewSession()->CompleteDrag(item2, left_of_middle); |
| EXPECT_EQ(SplitViewController::State::kRightSnapped, |
| split_view_controller->state()); |
| EXPECT_EQ(window1.get(), split_view_controller->right_window()); |
| |
| GetOverviewSession()->InitiateDrag(item2, |
| item2->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| const gfx::PointF left_snap_point(810.f, 300.f); |
| GetOverviewSession()->Drag(item2, left_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapLeft, |
| indicators->current_window_dragging_state()); |
| GetOverviewSession()->CompleteDrag(item2, left_snap_point); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller->state()); |
| } |
| |
| // Test dragging from one display to another. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| MultiDisplayDragging) { |
| wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const display::Display display_with_root1 = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[0]); |
| const display::Display display_with_root2 = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2); |
| ToggleOverview(); |
| OverviewGrid* grid_on_root1 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[0]); |
| OverviewGrid* grid_on_root2 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[1]); |
| OverviewItem* item1 = grid_on_root1->GetOverviewItemContaining(window1.get()); |
| SplitViewDragIndicators* indicators_on_root1 = |
| grid_on_root1->split_view_drag_indicators(); |
| SplitViewDragIndicators* indicators_on_root2 = |
| grid_on_root2->split_view_drag_indicators(); |
| |
| ASSERT_EQ(display_with_root1.id(), cursor_manager->GetDisplay().id()); |
| GetOverviewSession()->InitiateDrag(item1, |
| item1->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| |
| const gfx::PointF root1_left_snap_point(0.f, 300.f); |
| GetOverviewSession()->Drag(item1, root1_left_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapLeft, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[0]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| |
| const gfx::PointF root1_middle_point(400.f, 300.f); |
| GetOverviewSession()->Drag(item1, root1_middle_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| |
| const gfx::PointF root1_right_snap_point(799.f, 300.f); |
| GetOverviewSession()->Drag(item1, root1_right_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapRight, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[0]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| |
| const gfx::PointF root2_left_snap_point(800.f, 300.f); |
| cursor_manager->SetDisplay(display_with_root2); |
| GetOverviewSession()->Drag(item1, root2_left_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapLeft, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root2->bounds()); |
| |
| const gfx::PointF root2_left_snap_point_away_from_edge(816.f, 300.f); |
| GetOverviewSession()->Drag(item1, root2_left_snap_point_away_from_edge); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapLeft, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::RIGHT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root2->bounds()); |
| |
| const gfx::PointF root2_right_snap_point(1599.f, 300.f); |
| GetOverviewSession()->Drag(item1, root2_right_snap_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapRight, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ( |
| SplitViewController::Get(root_windows[1]) |
| ->GetSnappedWindowBoundsInScreen(SplitViewController::LEFT, |
| /*window_for_minimum_size=*/nullptr), |
| grid_on_root2->bounds()); |
| |
| const gfx::PointF root2_middle_point(1200.f, 300.f); |
| GetOverviewSession()->Drag(item1, root2_middle_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kOtherDisplay, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromOverview, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| |
| GetOverviewSession()->CompleteDrag(item1, root2_middle_point); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag, |
| indicators_on_root1->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root1.work_area(), grid_on_root1->bounds()); |
| EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kNoDrag, |
| indicators_on_root2->current_window_dragging_state()); |
| EXPECT_EQ(display_with_root2.work_area(), grid_on_root2->bounds()); |
| } |
| |
| // Verify the drop target positions for multi-display dragging. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DropTargetPositionTest) { |
| wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const display::Display display_with_root1 = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[0]); |
| const display::Display display_with_root2 = |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1]); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| // Named for MRU order, which is in reverse of creation order. |
| std::unique_ptr<aura::Window> window6 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| ToggleOverview(); |
| OverviewGrid* grid1 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[0]); |
| OverviewGrid* grid2 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[1]); |
| OverviewItem* item4 = grid2->GetOverviewItemContaining(window4.get()); |
| // Start dragging |item4| from |grid2|. |
| cursor_manager->SetDisplay(display_with_root2); |
| GetOverviewSession()->InitiateDrag(item4, |
| item4->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| GetOverviewSession()->Drag(item4, gfx::PointF(1200.f, 0.f)); |
| // On the grid where the drag starts (|grid2|), the drop target is inserted at |
| // the index immediately following the dragged item (|item4|). |
| ASSERT_EQ(4u, grid2->window_list().size()); |
| EXPECT_EQ(grid2->GetDropTarget(), grid2->window_list()[2].get()); |
| // Drag over |grid1|. |
| cursor_manager->SetDisplay(display_with_root1); |
| GetOverviewSession()->Drag(item4, gfx::PointF(400.f, 0.f)); |
| // On other grids (such as |grid1|), the drop target is inserted at the |
| // correct position according to MRU order (between the overview items for |
| // |window3| and |window5|). |
| ASSERT_EQ(4u, grid1->window_list().size()); |
| EXPECT_EQ(grid1->GetDropTarget(), grid1->window_list()[2].get()); |
| } |
| |
| // Verify that the drop target in each overview grid has the correct bounds when |
| // a maximized window is being dragged. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DropTargetBoundsForMaximizedWindowDraggedToOtherDisplay) { |
| UpdateDisplay("1000x400,1000x400/l"); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| WindowState::Get(window.get())->Maximize(); |
| ToggleOverview(); |
| OverviewItem* item = GetOverviewItemForWindow(window.get()); |
| // Verify that |item| is letter boxed. The bounds of |item|, minus the margin |
| // should have an aspect ratio of 2 : 1. |
| gfx::RectF item_bounds = item->target_bounds(); |
| item_bounds.Inset(gfx::InsetsF(kWindowMargin)); |
| EXPECT_EQ(OverviewGridWindowFillMode::kLetterBoxed, |
| item->GetWindowDimensionsType()); |
| EXPECT_EQ(2.f, item_bounds.width() / item_bounds.height()); |
| GetOverviewSession()->InitiateDrag(item, item->target_bounds().CenterPoint(), |
| /*is_touch_dragging=*/false); |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::test::DisplayManagerTestApi(display_manager()) |
| .GetSecondaryDisplay()); |
| // Drag to the middle of the secondary display to avoid triggering the drag |
| // snap indicator animation. |
| GetOverviewSession()->Drag(item, gfx::PointF(1200.f, 500.f)); |
| OverviewItem* drop_target = GetDropTarget(1); |
| ASSERT_TRUE(drop_target); |
| // Verify that |drop_target| is effectively pillar boxed. Avoid calling |
| // |OverviewItem::GetWindowDimensionsType|, because it does not work for drop |
| // targets (and that is okay). The bounds of |drop_target|, minus the margin |
| // should have an aspect ratio of 1 : 2. |
| gfx::RectF drop_target_bounds = drop_target->target_bounds(); |
| drop_target_bounds.Inset(gfx::InsetsF(kWindowMargin)); |
| EXPECT_EQ(0.5f, drop_target_bounds.width() / drop_target_bounds.height()); |
| } |
| |
| // Verify that the drop target in each overview grid has bounds representing |
| // anticipation that if the dragged window is dropped into that grid, it will |
| // shrink to fit into the corresponding work area. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DropTargetBoundsOnDisplayWhereDraggedWindowDoesNotFitIntoWorkArea) { |
| UpdateDisplay("600x500,1200x1000"); |
| // Drags |item| from the right display to the left display and back, and |
| // returns the bounds of the drop target that appears on the left display. |
| const auto root1_drop_target_bounds = [this](OverviewItem* item) { |
| wm::CursorManager* cursor_manager = Shell::Get()->cursor_manager(); |
| const gfx::PointF drag_starting_point = item->target_bounds().CenterPoint(); |
| display::test::DisplayManagerTestApi display_manager_test( |
| display_manager()); |
| cursor_manager->SetDisplay(display_manager_test.GetSecondaryDisplay()); |
| GetOverviewSession()->InitiateDrag(item, drag_starting_point, |
| /*is_touch_dragging=*/false); |
| cursor_manager->SetDisplay( |
| display::Screen::GetScreen()->GetPrimaryDisplay()); |
| GetOverviewSession()->Drag(item, gfx::PointF(300.f, 0.f)); |
| cursor_manager->SetDisplay(display_manager_test.GetSecondaryDisplay()); |
| GetOverviewSession()->Drag(item, drag_starting_point); |
| DCHECK(GetDropTarget(0)); |
| const gfx::RectF result = GetDropTarget(0)->target_bounds(); |
| GetOverviewSession()->CompleteDrag(item, drag_starting_point); |
| return result; |
| }; |
| |
| // |window1| has the size that |window2| would become if moved to the left |
| // display. |
| std::unique_ptr<aura::Window> window1 = |
| CreateTestWindow(gfx::Rect(600, 0, 600, 400)); |
| std::unique_ptr<aura::Window> window2 = |
| CreateTestWindow(gfx::Rect(600, 0, 1000, 400)); |
| // |window3| has the size that |window4| would become if moved to the left |
| // display. |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow( |
| gfx::Rect(600, 0, 400, 600 - ShelfConfig::Get()->shelf_size())); |
| std::unique_ptr<aura::Window> window4 = |
| CreateTestWindow(gfx::Rect(600, 0, 400, 1000)); |
| |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| OverviewItem* item4 = GetOverviewItemForWindow(window4.get()); |
| |
| // For good test coverage in each case, the dragged window and the drop target |
| // have different |OverviewGridWindowFillMode| values. |
| EXPECT_EQ(OverviewGridWindowFillMode::kNormal, |
| item1->GetWindowDimensionsType()); |
| EXPECT_EQ(OverviewGridWindowFillMode::kLetterBoxed, |
| item2->GetWindowDimensionsType()); |
| EXPECT_EQ(OverviewGridWindowFillMode::kNormal, |
| item3->GetWindowDimensionsType()); |
| EXPECT_EQ(OverviewGridWindowFillMode::kPillarBoxed, |
| item4->GetWindowDimensionsType()); |
| |
| EXPECT_EQ(root1_drop_target_bounds(item1), root1_drop_target_bounds(item2)); |
| EXPECT_EQ(root1_drop_target_bounds(item3), root1_drop_target_bounds(item4)); |
| } |
| |
| // Test dragging from one overview grid and dropping into another overview grid. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DragAndDropIntoAnotherOverviewGrid) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| std::unique_ptr<aura::Window> window = CreateTestWindow(); |
| ASSERT_EQ(root_windows[0], window->GetRootWindow()); |
| ToggleOverview(); |
| OverviewGrid* grid1 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[0]); |
| OverviewGrid* grid2 = |
| GetOverviewSession()->GetGridWithRootWindow(root_windows[1]); |
| |
| // Drag |window| from |grid1| and drop into |grid2|. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo( |
| gfx::ToRoundedPoint(grid1->GetOverviewItemContaining(window.get()) |
| ->target_bounds() |
| .CenterPoint())); |
| generator->PressLeftButton(); |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1])); |
| generator->MoveMouseTo(1200, 300); |
| generator->ReleaseLeftButton(); |
| |
| EXPECT_EQ(root_windows[1], window->GetRootWindow()); |
| EXPECT_TRUE(grid1->empty()); |
| OverviewItem* item = grid2->GetOverviewItemContaining(window.get()); |
| ASSERT_TRUE(item); |
| EXPECT_EQ(root_windows[1], item->root_window()); |
| } |
| |
| // Test that overview widgets are stacked in the correct order after an overview |
| // window is dragged from one overview grid and dropped into another. Also test |
| // that the destination overview grid is arranged in the correct order. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| OverviewWidgetStackingOrderAndGridOrderWithMultiDisplayDragging) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root2); |
| aura::Window* parent_on_root1 = window2->parent(); |
| aura::Window* parent_on_root2 = window1->parent(); |
| ASSERT_NE(parent_on_root1, parent_on_root2); |
| ASSERT_EQ(window3->parent(), parent_on_root2); |
| ToggleOverview(); |
| OverviewItem* item1 = GetOverviewItemForWindow(window1.get()); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| |
| ASSERT_EQ(root_windows[0], item2->root_window()); |
| // Verify that |item1| is stacked above |item3| (because we created |window1| |
| // after |window3|). |
| EXPECT_GT(IndexOf(item1->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(item3->item_widget()->GetNativeWindow(), parent_on_root2)); |
| // Verify that the item widget for each window is stacked below that window. |
| EXPECT_LT(IndexOf(item1->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(window1.get(), parent_on_root2)); |
| EXPECT_LT(IndexOf(item2->item_widget()->GetNativeWindow(), parent_on_root1), |
| IndexOf(window2.get(), parent_on_root1)); |
| EXPECT_LT(IndexOf(item3->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(window3.get(), parent_on_root2)); |
| |
| // Drag |item2| from the left display and drop into the right display. |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo( |
| gfx::ToRoundedPoint(item2->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1])); |
| generator->MoveMouseTo(1200, 300); |
| generator->ReleaseLeftButton(); |
| // |item2| is now a dangling pointer and we have to refresh it, because when |
| // an overview window is dragged from one grid and dropped into another, the |
| // original item is destroyed and a new one is created. |
| item2 = GetOverviewItemForWindow(window2.get()); |
| |
| ASSERT_EQ(window2->parent(), parent_on_root2); |
| ASSERT_EQ(root_windows[1], item2->root_window()); |
| // With all three items on one grid, verify that their stacking order |
| // corresponds to the MRU order of the windows. The new |item2| is sandwiched |
| // between |item1| and |item3|. |
| EXPECT_GT(IndexOf(item1->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(item2->item_widget()->GetNativeWindow(), parent_on_root2)); |
| EXPECT_GT(IndexOf(item2->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(item3->item_widget()->GetNativeWindow(), parent_on_root2)); |
| // Verify that the item widget for the new |item2| is stacked below |window2|. |
| EXPECT_LT(IndexOf(item2->item_widget()->GetNativeWindow(), parent_on_root2), |
| IndexOf(window2.get(), parent_on_root2)); |
| |
| // Verify that the right grid is in MRU order. |
| const std::vector<aura::Window*> expected_order = { |
| window1.get(), window2.get(), window3.get()}; |
| EXPECT_EQ(expected_order, |
| GetOverviewController()->GetWindowsListInOverviewGridsForTest()); |
| } |
| |
| // Test dragging from one display to another and then snapping. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DragFromOneDisplayToAnotherAndSnap) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| SplitViewController* split_view_controller1 = |
| SplitViewController::Get(root_windows[0]); |
| SplitViewController* split_view_controller2 = |
| SplitViewController::Get(root_windows[1]); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| ToggleOverview(); |
| ui::test::EventGenerator* generator = GetEventGenerator(); |
| generator->MoveMouseTo(gfx::ToRoundedPoint( |
| GetOverviewItemForWindow(window2.get())->target_bounds().CenterPoint())); |
| generator->PressLeftButton(); |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1])); |
| generator->MoveMouseTo(800, 300); |
| generator->ReleaseLeftButton(); |
| EXPECT_EQ(SplitViewController::State::kNoSnap, |
| split_view_controller1->state()); |
| EXPECT_EQ(SplitViewController::State::kLeftSnapped, |
| split_view_controller2->state()); |
| EXPECT_EQ(window2.get(), split_view_controller2->left_window()); |
| EXPECT_EQ(root_windows[1], window2->GetRootWindow()); |
| EXPECT_TRUE(InOverviewSession()); |
| } |
| |
| // Verify that window resizing performance is recorded to the correct histogram |
| // depending on whether the overview grid is empty. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| WindowResizingPerformanceHistogramsTest) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds_within_root1)); |
| std::unique_ptr<aura::Window> window2( |
| CreateWindowWithHitTestComponent(HTRIGHT, bounds_within_root2)); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2); |
| ToggleOverview(); |
| SplitViewController::Get(root_windows[0]) |
| ->SnapWindow(window1.get(), SplitViewController::LEFT); |
| SplitViewController::Get(root_windows[1]) |
| ->SnapWindow(window2.get(), SplitViewController::LEFT); |
| // Resize |window1|, which is in split view with an empty overview grid. |
| ui::test::EventGenerator generator1(root_windows[0], window1.get()); |
| generator1.PressLeftButton(); |
| CheckWindowResizingPerformanceHistograms("BeforeResizingWindow1", 0, 0, 0, 0); |
| generator1.MoveMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("WhileResizingWindow1", 1, 0, 0, 0); |
| generator1.ReleaseLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterResizingWindow1", 1, 1, 0, 0); |
| // Resize |window2|, which is in split view with a nonempty overview grid. |
| Shell::Get()->cursor_manager()->SetDisplay( |
| display::Screen::GetScreen()->GetDisplayNearestWindow(root_windows[1])); |
| ui::test::EventGenerator generator2(root_windows[1], window2.get()); |
| generator2.PressLeftButton(); |
| CheckWindowResizingPerformanceHistograms("BeforeResizingWindow2", 1, 1, 0, 0); |
| generator2.MoveMouseBy(50, 50); |
| CheckWindowResizingPerformanceHistograms("WhileResizingWindow2", 1, 1, 1, 0); |
| generator2.ReleaseLeftButton(); |
| CheckWindowResizingPerformanceHistograms("AfterResizingWindow2", 1, 1, 1, 1); |
| } |
| |
| // Verify that the user action "SplitView_MultiDisplaySplitView" is recorded |
| // when multi-display split view starts, and that a value is recorded to the |
| // histogram "Ash.SplitView.TimeInMultiDisplaySplitView" when multi-display |
| // split view ends. This test does not actually examine the timing values |
| // recorded to the histogram, but this test does provide evidence of timing |
| // accuracy as the time in multi-display split view is measured from the time |
| // when the user action "SplitView_MultiDisplaySplitView" is recorded. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| MultiDisplaySplitViewMetrics) { |
| base::UserActionTester user_action_tester; |
| base::HistogramTester histogram_tester; |
| // Verifies that multi-display split view has started exactly |start_count| |
| // times and ended exactly |end_count| times. If not, then the output will |
| // include |description| to indicate where the test failed. |
| const auto verify = [&user_action_tester, &histogram_tester]( |
| const char* description, int start_count, |
| int end_count) { |
| SCOPED_TRACE(description); |
| EXPECT_EQ(start_count, user_action_tester.GetActionCount( |
| "SplitView_MultiDisplaySplitView")); |
| histogram_tester.ExpectTotalCount( |
| "Ash.SplitView.TimeInMultiDisplaySplitView", end_count); |
| }; |
| |
| UpdateDisplay("800x600,800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(3u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| const gfx::Rect bounds_within_root3(1600, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root3); |
| SplitViewController* split_view_controller1 = |
| SplitViewController::Get(root_windows[0]); |
| SplitViewController* split_view_controller2 = |
| SplitViewController::Get(root_windows[1]); |
| SplitViewController* split_view_controller3 = |
| SplitViewController::Get(root_windows[2]); |
| verify("1. Unit test set up", 0, 0); |
| ToggleOverview(); |
| split_view_controller1->SnapWindow(window1.get(), SplitViewController::LEFT); |
| verify("2. Number of displays in split view changed from 0 to 1", 0, 0); |
| split_view_controller2->SnapWindow(window3.get(), SplitViewController::LEFT); |
| verify("3. Number of displays in split view changed from 1 to 2", 1, 0); |
| ToggleOverview(); |
| verify("4. Number of displays in split view changed from 2 to 0", 1, 1); |
| ToggleOverview(); |
| split_view_controller1->SnapWindow(window1.get(), SplitViewController::LEFT); |
| verify("5. Number of displays in split view changed from 0 to 1", 1, 1); |
| split_view_controller2->SnapWindow(window3.get(), SplitViewController::LEFT); |
| verify("6. Number of displays in split view changed from 1 to 2", 2, 1); |
| split_view_controller3->SnapWindow(window5.get(), SplitViewController::LEFT); |
| verify("7. Number of displays in split view changed from 2 to 3", 2, 1); |
| ToggleOverview(); |
| verify("8. Number of displays in split view changed from 3 to 0", 2, 2); |
| ToggleOverview(); |
| split_view_controller1->SnapWindow(window1.get(), SplitViewController::LEFT); |
| verify("9. Number of displays in split view changed from 0 to 1", 2, 2); |
| split_view_controller2->SnapWindow(window3.get(), SplitViewController::LEFT); |
| verify("10. Number of displays in split view changed from 1 to 2", 3, 2); |
| split_view_controller3->SnapWindow(window5.get(), SplitViewController::LEFT); |
| verify("11. Number of displays in split view changed from 2 to 3", 3, 2); |
| // For good test coverage, after multi-display split view started with |
| // |split_view_controller2|, now we end split view on |split_view_controller2| |
| // first, and then end multi-display split view with |split_view_controller3|. |
| window3.reset(); |
| verify("12. Number of displays in split view changed from 3 to 2", 3, 2); |
| window5.reset(); |
| verify("13. Number of displays in split view changed from 2 to 1", 3, 3); |
| window1.reset(); |
| verify("14. Number of displays in split view changed from 1 to 0", 3, 3); |
| split_view_controller1->SnapWindow(window2.get(), SplitViewController::LEFT); |
| verify("15. Number of displays in split view changed from 0 to 1", 3, 3); |
| // In this case, multi-display split view ends as soon as it starts. The |
| // metrics should report that as starting and ending multi-display split view. |
| split_view_controller2->SnapWindow(window4.get(), SplitViewController::LEFT); |
| verify( |
| "16. Multi-display split view started by snapping last overview window", |
| 4, 4); |
| } |
| |
| // Verify that |SplitViewController::CanSnapWindow| checks that the minimum size |
| // of the window fits into the left or top, with the default divider position. |
| // (If the work area length is odd, then the right or bottom will be one pixel |
| // larger.) |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| SnapWindowWithMinimumSizeTest) { |
| // The divider is 8 thick. For the default divider position, the remaining 792 |
| // of the work area on the first root window is divided into 396 on each side, |
| // and the remaining 791 of the work area on the second root window is divided |
| // into 395 on the left and 396 on the right (the left side is what matters). |
| UpdateDisplay("800x600,799x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| // It should make no difference which root window has the window passed to |
| // |SplitViewController::CanSnapWindow|. What should matter is the root window |
| // of the |SplitViewController|. To verify, we test with |bounds_within_root1| |
| // and |bounds_within_root2|, and expect the same results. |
| for (const gfx::Rect& bounds : {bounds_within_root1, bounds_within_root2}) { |
| SCOPED_TRACE(bounds.ToString()); |
| aura::test::TestWindowDelegate* delegate = |
| aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate(); |
| std::unique_ptr<aura::Window> window( |
| CreateTestWindowInShellWithDelegate(delegate, /*id=*/-1, bounds)); |
| // Before setting a minimum size, expect that |window| can be snapped in |
| // split view on either root window. |
| EXPECT_TRUE( |
| SplitViewController::Get(root_windows[0])->CanSnapWindow(window.get())); |
| EXPECT_TRUE( |
| SplitViewController::Get(root_windows[1])->CanSnapWindow(window.get())); |
| // Either root window can accommodate a minimum size 395 wide. |
| delegate->set_minimum_size(gfx::Size(395, 0)); |
| EXPECT_TRUE( |
| SplitViewController::Get(root_windows[0])->CanSnapWindow(window.get())); |
| EXPECT_TRUE( |
| SplitViewController::Get(root_windows[1])->CanSnapWindow(window.get())); |
| // Only the first root window can accommodate a minimum size 396 wide. |
| delegate->set_minimum_size(gfx::Size(396, 0)); |
| EXPECT_TRUE( |
| SplitViewController::Get(root_windows[0])->CanSnapWindow(window.get())); |
| EXPECT_FALSE( |
| SplitViewController::Get(root_windows[1])->CanSnapWindow(window.get())); |
| // Neither root window can accommodate a minimum size 397 wide. |
| delegate->set_minimum_size(gfx::Size(397, 0)); |
| EXPECT_FALSE( |
| SplitViewController::Get(root_windows[0])->CanSnapWindow(window.get())); |
| EXPECT_FALSE( |
| SplitViewController::Get(root_windows[1])->CanSnapWindow(window.get())); |
| } |
| } |
| |
| // Verify that when in overview mode, the selector items unsnappable indicator |
| // shows up when expected. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| OverviewUnsnappableIndicatorVisibility) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window3 = |
| CreateUnsnappableWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window4 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window5 = CreateTestWindow(bounds_within_root2); |
| std::unique_ptr<aura::Window> window6 = |
| CreateUnsnappableWindow(bounds_within_root2); |
| ToggleOverview(); |
| OverviewItem* item2 = GetOverviewItemForWindow(window2.get()); |
| OverviewItem* item3 = GetOverviewItemForWindow(window3.get()); |
| OverviewItem* item5 = GetOverviewItemForWindow(window5.get()); |
| OverviewItem* item6 = GetOverviewItemForWindow(window6.get()); |
| |
| // Note: |cannot_snap_label_view_| and its parent will be created on demand. |
| ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| EXPECT_FALSE(item2->cannot_snap_widget_for_testing()); |
| EXPECT_FALSE(item3->cannot_snap_widget_for_testing()); |
| EXPECT_FALSE(item5->cannot_snap_widget_for_testing()); |
| EXPECT_FALSE(item6->cannot_snap_widget_for_testing()); |
| |
| SplitViewController::Get(root_windows[0]) |
| ->SnapWindow(window1.get(), SplitViewController::LEFT); |
| ASSERT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| EXPECT_FALSE(item2->cannot_snap_widget_for_testing()); |
| ASSERT_TRUE(item3->cannot_snap_widget_for_testing()); |
| ui::Layer* item3_unsnappable_layer = |
| item3->cannot_snap_widget_for_testing()->GetNativeWindow()->layer(); |
| EXPECT_EQ(1.f, item3_unsnappable_layer->opacity()); |
| EXPECT_FALSE(item5->cannot_snap_widget_for_testing()); |
| EXPECT_FALSE(item6->cannot_snap_widget_for_testing()); |
| |
| SplitViewController::Get(root_windows[1]) |
| ->SnapWindow(window4.get(), SplitViewController::LEFT); |
| ASSERT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| ASSERT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| EXPECT_FALSE(item2->cannot_snap_widget_for_testing()); |
| EXPECT_EQ(1.f, item3_unsnappable_layer->opacity()); |
| EXPECT_FALSE(item5->cannot_snap_widget_for_testing()); |
| ASSERT_TRUE(item6->cannot_snap_widget_for_testing()); |
| ui::Layer* item6_unsnappable_layer = |
| item6->cannot_snap_widget_for_testing()->GetNativeWindow()->layer(); |
| EXPECT_EQ(1.f, item6_unsnappable_layer->opacity()); |
| |
| SplitViewController::Get(root_windows[0])->EndSplitView(); |
| ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| ASSERT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| EXPECT_FALSE(item2->cannot_snap_widget_for_testing()); |
| EXPECT_EQ(0.f, item3_unsnappable_layer->opacity()); |
| EXPECT_FALSE(item5->cannot_snap_widget_for_testing()); |
| EXPECT_EQ(1.f, item6_unsnappable_layer->opacity()); |
| |
| SplitViewController::Get(root_windows[1])->EndSplitView(); |
| ASSERT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| ASSERT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| EXPECT_FALSE(item2->cannot_snap_widget_for_testing()); |
| EXPECT_EQ(0.f, item3_unsnappable_layer->opacity()); |
| EXPECT_FALSE(item5->cannot_snap_widget_for_testing()); |
| EXPECT_EQ(0.f, item6_unsnappable_layer->opacity()); |
| } |
| |
| // Test that enabling the docked magnifier ends clamshell split view on all |
| // displays. |
| TEST_F(SplitViewOverviewSessionInClamshellTestMultiDisplayOnly, |
| DockedMagnifierEndsClamshellSplitView) { |
| UpdateDisplay("800x600,800x600"); |
| aura::Window::Windows root_windows = Shell::GetAllRootWindows(); |
| ASSERT_EQ(2u, root_windows.size()); |
| const gfx::Rect bounds_within_root1(0, 0, 400, 400); |
| const gfx::Rect bounds_within_root2(800, 0, 400, 400); |
| std::unique_ptr<aura::Window> window1 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window2 = CreateTestWindow(bounds_within_root1); |
| std::unique_ptr<aura::Window> window3 = CreateTestWindow(bounds_within_root2); |
| ToggleOverview(); |
| SplitViewController::Get(root_windows[0]) |
| ->SnapWindow(window1.get(), SplitViewController::LEFT); |
| SplitViewController::Get(root_windows[1]) |
| ->SnapWindow(window3.get(), SplitViewController::LEFT); |
| EXPECT_TRUE(InOverviewSession()); |
| EXPECT_TRUE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| EXPECT_TRUE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| Shell::Get()->docked_magnifier_controller()->SetEnabled(true); |
| EXPECT_FALSE(InOverviewSession()); |
| EXPECT_FALSE(SplitViewController::Get(root_windows[0])->InSplitViewMode()); |
| EXPECT_FALSE(SplitViewController::Get(root_windows[1])->InSplitViewMode()); |
| } |
| |
| } // namespace ash |