// Copyright 2019 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/shelf/drag_window_from_shelf_controller.h"
#include <tuple>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/app_list/test/app_list_test_helper.h"
#include "ash/app_list/views/app_list_view.h"
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/overview_test_api.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_backdrop.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/drag_window_from_shelf_controller_test_api.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_metrics.h"
#include "ash/shelf/window_scale_animation.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_drop_target.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/splitview/split_view_constants.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
#include "ash/wm/window_properties.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/work_area_insets.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "chromeos/constants/chromeos_features.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/test/layer_animation_stopped_waiter.h"
#include "ui/compositor/test/test_utils.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/transient_window_manager.h"
#include "ui/wm/core/window_modality_controller.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
gfx::Rect GetShelfBounds() {
return Shelf::ForWindow(Shell::GetPrimaryRootWindow())->GetIdealBounds();
}
// Gets the drag controller owned by the shelf.
DragWindowFromShelfController* GetDragWindowFromShelfController() {
return AshTestBase::GetPrimaryShelf()
->shelf_layout_manager()
->window_drag_controller_for_testing();
}
} // namespace
// This definition is needed because this constant is odr-used.
// https://en.cppreference.com/w/cpp/language/static#Constant_static_members
const float kVelocityToRestoreBoundsThreshold =
DragWindowFromShelfController::kVelocityToRestoreBoundsThreshold;
class DragWindowFromShelfControllerTest : public AshTestBase {
public:
DragWindowFromShelfControllerTest() = default;
DragWindowFromShelfControllerTest(const DragWindowFromShelfControllerTest&) =
delete;
DragWindowFromShelfControllerTest& operator=(
const DragWindowFromShelfControllerTest&) = delete;
~DragWindowFromShelfControllerTest() override = default;
// AshTestBase:
void SetUp() override {
AshTestBase::SetUp();
TabletModeControllerTestApi().EnterTabletMode();
base::RunLoop().RunUntilIdle();
}
void TearDown() override {
// Destroy |window_drag_controller_| so that its scheduled task won't get
// run after the test environment is gone.
window_drag_controller_.reset();
AshTestBase::TearDown();
}
void StartDrag(aura::Window* window, const gfx::Point& location_in_screen) {
window_drag_controller_ = std::make_unique<DragWindowFromShelfController>(
window, gfx::PointF(location_in_screen));
}
void Drag(const gfx::Point& location_in_screen,
float scroll_x,
float scroll_y) {
window_drag_controller_->Drag(gfx::PointF(location_in_screen), scroll_x,
scroll_y);
}
void EndDrag(const gfx::Point& location_in_screen,
std::optional<float> velocity_y) {
window_drag_controller_->EndDrag(gfx::PointF(location_in_screen),
velocity_y);
window_drag_controller_->FinalizeDraggedWindow();
}
void CancelDrag() { window_drag_controller_->CancelDrag(); }
void WaitForHomeLauncherAnimationToFinish() {
// Wait until home launcher animation finishes.
ui::Layer* const layer =
GetAppListTestHelper()->GetAppListView()->GetWidget()->GetLayer();
ui::Compositor* const compositor = layer->GetCompositor();
ui::LayerAnimationStoppedWaiter animation_waiter;
animation_waiter.Wait(layer);
// Force frames and wait for all throughput trackers to be gone to allow
// animation throughput data to be passed from cc to ui.
while (compositor->has_throughput_trackers_for_testing()) {
compositor->ScheduleFullRedraw();
std::ignore = ui::WaitForNextFrameToBePresented(compositor,
base::Milliseconds(500));
}
}
SplitViewController* split_view_controller() {
return SplitViewController::Get(Shell::GetPrimaryRootWindow());
}
DragWindowFromShelfController* window_drag_controller() {
return window_drag_controller_.get();
}
std::unique_ptr<aura::Window> CreateTransientModalChildWindow(
aura::Window* transient_parent,
const gfx::Rect& bounds) {
auto child = std::make_unique<aura::Window>(
nullptr, aura::client::WINDOW_TYPE_POPUP);
child->Init(ui::LAYER_NOT_DRAWN);
child->SetBounds(bounds);
wm::AddTransientChild(transient_parent, child.get());
aura::client::ParentWindowWithContext(child.get(),
transient_parent->GetRootWindow(),
bounds, display::kInvalidDisplayId);
child->Show();
child->SetProperty(aura::client::kModalKey, ui::mojom::ModalType::kWindow);
wm::SetModalParent(child.get(), transient_parent);
return child;
}
private:
std::unique_ptr<DragWindowFromShelfController> window_drag_controller_;
};
// Tests that we may hide different sets of windows with a special flag
// kHideDuringWindowDragging.
TEST_F(DragWindowFromShelfControllerTest,
HideWindowDuringWindowDraggingWithFlag) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window3 = CreateTestWindow();
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
EXPECT_FALSE(window1->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window3->GetProperty(kHideDuringWindowDragging));
StartDrag(window1.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 1.f, 1.f);
EXPECT_TRUE(window1->IsVisible());
EXPECT_FALSE(window2->IsVisible());
EXPECT_FALSE(window3->IsVisible());
EXPECT_FALSE(window1->GetProperty(kHideDuringWindowDragging));
EXPECT_TRUE(window2->GetProperty(kHideDuringWindowDragging));
EXPECT_TRUE(window3->GetProperty(kHideDuringWindowDragging));
EndDrag(shelf_bounds.CenterPoint(), /*velocity_y=*/std::nullopt);
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
EXPECT_FALSE(window1->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window3->GetProperty(kHideDuringWindowDragging));
}
// Tests that we may hide different sets of windows in splitview and restores
// windows correctly after dragging.
TEST_F(DragWindowFromShelfControllerTest,
HideWindowDuringWindowDraggingInSplitView) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window3 = CreateTestWindow();
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
// In splitview mode, the snapped windows will stay visible during dragging.
split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
// Try to drag a left snapped window
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 1.f, 1.f);
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_FALSE(window3->IsVisible());
EndDrag(shelf_bounds.bottom_left(), /*velocity_y=*/std::nullopt);
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
// Ensure that all windows are restored correctly without triggering auto
// snapping.
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window1.get()),
SnapPosition::kPrimary);
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window2.get()),
SnapPosition::kSecondary);
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window3.get()));
// Try to drag a right snapped window
StartDrag(window2.get(), shelf_bounds.right_center());
Drag(gfx::Point(400, 200), 1.f, 1.f);
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_FALSE(window3->IsVisible());
EndDrag(shelf_bounds.bottom_right(), /*velocity_y=*/std::nullopt);
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
// Ensure that all windows are restored correctly without triggering auto
// snapping.
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window1.get()),
SnapPosition::kPrimary);
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window2.get()),
SnapPosition::kSecondary);
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window3.get()));
}
// Test home launcher is hidden during dragging.
TEST_F(DragWindowFromShelfControllerTest, HideHomeLauncherDuringDraggingTest) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(0, 200), 0.f, 1.f);
aura::Window* home_screen_window =
Shell::Get()->app_list_controller()->GetHomeScreenWindow();
EXPECT_TRUE(home_screen_window);
EXPECT_FALSE(home_screen_window->IsVisible());
EndDrag(shelf_bounds.CenterPoint(),
/*velocity_y=*/std::nullopt);
EXPECT_TRUE(home_screen_window->IsVisible());
}
// Test that the "No recent items" label is not visible (not created) while
// dragging from shelf. Regression test for http://b/326091611.
TEST_F(DragWindowFromShelfControllerTest, NoWindowsWidget) {
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(0, 200), 0.f, 1.f);
OverviewSession* overview_session =
OverviewController::Get()->overview_session();
ASSERT_TRUE(overview_session);
EXPECT_FALSE(overview_session->grid_list()[0]->no_windows_widget());
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_FALSE(overview_session->grid_list()[0]->no_windows_widget());
}
// Test the windows that were hidden before drag started may or may not reshow,
// depending on different scenarios.
TEST_F(DragWindowFromShelfControllerTest, MayOrMayNotReShowHiddenWindows) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
EXPECT_FALSE(window1->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
// If the dragged window restores to its original position, reshow the hidden
// windows.
StartDrag(window1.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
EXPECT_FALSE(window2->IsVisible());
EXPECT_TRUE(window2->GetProperty(kHideDuringWindowDragging));
EndDrag(shelf_bounds.CenterPoint(), std::nullopt);
EXPECT_TRUE(window2->IsVisible());
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
// If fling to homescreen, do not reshow the hidden windows.
StartDrag(window1.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
EXPECT_TRUE(window2->GetProperty(kHideDuringWindowDragging));
EXPECT_FALSE(window2->IsVisible());
EndDrag(gfx::Point(200, 200),
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold);
EXPECT_FALSE(window1->IsVisible());
EXPECT_FALSE(window2->IsVisible());
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
// If the dragged window is added to overview, do not reshow the hidden
// windows.
window2->Show();
window1->Show();
StartDrag(window1.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
EXPECT_FALSE(window2->IsVisible());
EXPECT_TRUE(window2->GetProperty(kHideDuringWindowDragging));
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
EXPECT_FALSE(window2->IsVisible());
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
ExitOverview();
// If the dragged window is snapped in splitview, while the other windows are
// showing in overview, do not reshow the hidden windows.
window2->Show();
window1->Show();
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 0.f, 1.f);
EXPECT_FALSE(window2->IsVisible());
EXPECT_TRUE(window2->GetProperty(kHideDuringWindowDragging));
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(0, 200), std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_FALSE(window2->IsVisible());
EXPECT_FALSE(window2->GetProperty(kHideDuringWindowDragging));
}
// Test during window dragging, if overview is open, the minimized windows can
// show correctly in overview.
TEST_F(DragWindowFromShelfControllerTest, MinimizedWindowsShowInOverview) {
UpdateDisplay("500x400");
auto window3 = CreateTestWindow();
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
StartDrag(window1.get(), GetShelfBounds().CenterPoint());
// Drag it far enough so overview should be open behind the dragged window.
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(window1->IsVisible());
EXPECT_FALSE(window2->IsVisible());
EXPECT_TRUE(WindowState::Get(window2.get())->IsMinimized());
EXPECT_FALSE(window3->IsVisible());
EXPECT_TRUE(WindowState::Get(window3.get())->IsMinimized());
EXPECT_FALSE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window2.get()));
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window3.get()));
// Release the drag, the window should be added to overview.
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
}
// Test when swiping up from the shelf, we only open overview when the y scroll
// delta (velocity) decrease to kOpenOverviewThreshold or less.
TEST_F(DragWindowFromShelfControllerTest, OpenOverviewWhenHold) {
UpdateDisplay("500x400");
auto window = CreateTestWindow();
StartDrag(window.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.f,
DragWindowFromShelfController::kOpenOverviewThreshold + 1);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_FALSE(overview_controller->InOverviewSession());
Drag(gfx::Point(200, 200), 0.f,
DragWindowFromShelfController::kOpenOverviewThreshold);
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(200, 200), std::nullopt);
}
// Test if the dragged window is not dragged far enough than
// |GetReturnToMaximizedThreshold| (the top of the hotseat), it will restore
// back to its original position.
TEST_F(DragWindowFromShelfControllerTest, RestoreWindowToOriginalBounds) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
const gfx::Rect display_bounds = display::Screen::GetScreen()
->GetDisplayNearestWindow(window.get())
.bounds();
// Drag it for a small distance and then release.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f,
DragWindowFromShelfController::kShowOverviewThreshold + 1);
EXPECT_FALSE(window->layer()->GetTargetTransform().IsIdentity());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_FALSE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(200, 400), std::nullopt);
EXPECT_TRUE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
// Drag it for a large distance and then drag back to release.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
EXPECT_FALSE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_TRUE(overview_controller->InOverviewSession());
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(
gfx::Point(
200,
display_bounds.bottom() -
DragWindowFromShelfController::GetReturnToMaximizedThreshold() +
1),
std::nullopt);
EXPECT_TRUE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
// The same thing should happen if splitview mode is active.
auto window2 = CreateTestWindow();
split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 0.f, 1.f);
EXPECT_FALSE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(0, 400), std::nullopt);
EXPECT_TRUE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_EQ(split_view_controller()->primary_window(), window.get());
}
// Test if overview is active and splitview is not active, fling in overview may
// or may not head to the home screen.
TEST_F(DragWindowFromShelfControllerTest, FlingInOverview) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
// If downward fling velocity is equal or larger than
// kVelocityToRestoreBoundsThreshold.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(200, 200), kVelocityToRestoreBoundsThreshold);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
// If upward fling velocity is smaller than kVelocityToHomeScreenThreshold,
// decide where the window should go based on the release position.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(
gfx::Point(0, 350),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold + 10));
// The window should restore back to its original position.
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
// If upward fling velocity is equal or larger than
// kVelocityToHomeScreenThreshold.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(0, 350),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
}
// Verify that metrics of home launcher animation are recorded correctly when
// swiping up from shelf with sufficient velocity.
TEST_F(DragWindowFromShelfControllerTest, VerifyHomeLauncherAnimationMetrics) {
// Set non-zero animation duration to report animation metrics.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
UpdateDisplay("500x400");
auto window = CreateTestWindow();
base::HistogramTester histogram_tester;
// Ensure that fling velocity is sufficient to show homelauncher without
// triggering overview mode.
StartDrag(window.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.f,
DragWindowFromShelfController::kOpenOverviewThreshold + 1);
EndDrag(gfx::Point(0, 350),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
WaitForHomeLauncherAnimationToFinish();
// Verify that animation to show the home launcher is recorded.
histogram_tester.ExpectTotalCount(
"Apps.HomeLauncherTransition.AnimationSmoothness.FadeOutOverview", 1);
}
// Test if splitview is active when fling happens, the window will be put in
// overview.
TEST_F(DragWindowFromShelfControllerTest, DragOrFlingInSplitView) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window1 = CreateTestWindow();
auto window2 = CreateTestWindow();
OverviewController* overview_controller = OverviewController::Get();
split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
// If the window is only dragged for a small distance:
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(100, 200), 0.f, 1.f);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(100, 350), std::nullopt);
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
// If the window is dragged for a long distance:
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(100, 200), 0.f, 1.f);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(100, 200), std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
ExitOverview();
// If the window is flung with a small velocity:
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(100, 200), 0.f, 1.f);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(
gfx::Point(100, 350),
std::make_optional(
-DragWindowFromShelfController::kVelocityToOverviewThreshold + 10));
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
// If the window is flung with a large velocity:
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(100, 200), 0.f, 1.f);
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(overview_controller->InOverviewSession());
EndDrag(gfx::Point(100, 150),
std::make_optional(
-DragWindowFromShelfController::kVelocityToOverviewThreshold));
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window2.get()));
ExitOverview();
}
// Test overview is hidden during dragging and shown when drag slows down or
// stops.
TEST_F(DragWindowFromShelfControllerTest, HideOverviewDuringDragging) {
UpdateDisplay("500x400");
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
StartDrag(window1.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
// We test the visibility of overview by testing the drop target widget's
// visibility in the overview.
OverviewGrid* current_grid =
overview_controller->overview_session()->GetGridWithRootWindow(
window1->GetRootWindow());
auto* drop_target = current_grid->drop_target();
EXPECT_TRUE(drop_target);
EXPECT_EQ(drop_target->item_widget()->GetLayer()->GetTargetOpacity(), 1.f);
Drag(gfx::Point(200, 200), 0.5f,
DragWindowFromShelfController::kShowOverviewThreshold + 1);
// Test that the overview drop target is invisible.
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_EQ(drop_target->item_widget()->GetLayer()->GetTargetOpacity(), 0.f);
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200),
/*velocity_y=*/std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
// |window1| should have added to overview. Test its visibility.
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window1.get()));
EXPECT_EQ(window1->layer()->GetTargetOpacity(), 1.f);
}
// Check the split view drag indicators window dragging states.
TEST_F(DragWindowFromShelfControllerTest,
SplitViewDragIndicatorsWindowDraggingStates) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
OverviewSession* overview_session = overview_controller->overview_session();
ASSERT_EQ(1u, overview_session->grid_list().size());
const SplitViewDragIndicators* drag_indicators =
overview_session->grid_list()[0]->split_view_drag_indicators();
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromShelf,
drag_indicators->current_window_dragging_state());
Drag(gfx::Point(0, 200), 0.5f, 0.5f);
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
drag_indicators->current_window_dragging_state());
Drag(gfx::Point(0, 350), 0.5f, 0.5f);
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromShelf,
drag_indicators->current_window_dragging_state());
Drag(gfx::Point(0, 200), 0.5f, 0.5f);
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kToSnapPrimary,
drag_indicators->current_window_dragging_state());
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
EXPECT_EQ(SplitViewDragIndicators::WindowDraggingState::kFromShelf,
drag_indicators->current_window_dragging_state());
EndDrag(shelf_bounds.CenterPoint(), /*velocity_y=*/std::nullopt);
}
// Tests no crash on dragging from shelf from the split view drag indicators.
// Regression test for http://b/339071708.
TEST_F(DragWindowFromShelfControllerTest, NoCrashOnSplitViewDragIndicators) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
// Drag just enough to show the shelf.
StartDrag(window.get(), shelf_bounds.CenterPoint());
Drag(gfx::Point(200, 200), 0.f,
DragWindowFromShelfController::kShowOverviewThreshold + 1);
ASSERT_FALSE(OverviewController::Get()->InOverviewSession());
// Enable chromevox to destroy the drag indicators.
Shell::Get()->accessibility_controller()->spoken_feedback().SetEnabled(true);
// Continue dragging to the top.
Drag(gfx::Point(10, 399), 0.5f, 0.5f);
}
// Test there is no black backdrop behind the dragged window if we're doing the
// scale down animation for the dragged window.
TEST_F(DragWindowFromShelfControllerTest, NoBackdropDuringWindowScaleDown) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto window = CreateTestWindow();
EXPECT_TRUE(window->layer()->GetTargetTransform().IsIdentity());
WindowBackdrop* window_backdrop = WindowBackdrop::Get(window.get());
EXPECT_NE(window_backdrop->mode(), WindowBackdrop::BackdropMode::kDisabled);
StartDrag(window.get(), GetShelfBounds().left_center());
Drag(gfx::Point(0, 200), 0.f, 10.f);
EndDrag(gfx::Point(0, 200),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
EXPECT_FALSE(window->layer()->GetTargetTransform().IsIdentity());
EXPECT_NE(window_backdrop->mode(), WindowBackdrop::BackdropMode::kDisabled);
EXPECT_TRUE(window_backdrop->temporarily_disabled());
}
// Test that if drag is cancelled, overview should be dismissed and other
// hidden windows should restore to its previous visibility state.
TEST_F(DragWindowFromShelfControllerTest, CancelDragDismissOverview) {
auto window3 = CreateTestWindow();
auto window2 = CreateTestWindow();
auto window1 = CreateTestWindow();
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
StartDrag(window1.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(window1->IsVisible());
EXPECT_FALSE(window2->IsVisible());
EXPECT_FALSE(window3->IsVisible());
CancelDrag();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_TRUE(window1->IsVisible());
EXPECT_TRUE(window2->IsVisible());
EXPECT_TRUE(window3->IsVisible());
}
TEST_F(DragWindowFromShelfControllerTest, CancelDragIfWindowDestroyed) {
auto window = CreateTestWindow();
StartDrag(window.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EXPECT_EQ(window_drag_controller()->dragged_window(), window.get());
EXPECT_TRUE(window_drag_controller()->drag_started());
base::HistogramTester histogram_tester;
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kDragCanceled, 0);
window.reset();
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kDragCanceled, 1);
EXPECT_EQ(window_drag_controller()->dragged_window(), nullptr);
EXPECT_FALSE(window_drag_controller()->drag_started());
// No crash should happen if Drag() call still comes in.
Drag(gfx::Point(200, 200), 0.5f, 0.5f);
CancelDrag();
}
TEST_F(DragWindowFromShelfControllerTest, FlingWithHiddenHotseat) {
base::HistogramTester histogram_tester;
histogram_tester.ExpectBucketCount(
kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kRestoreToOriginalBounds, 0);
auto window = CreateTestWindow();
gfx::Point start = GetShelfBounds().CenterPoint();
StartDrag(window.get(), start);
// Only drag for a small distance and then fling.
Drag(gfx::Point(start.x(), start.y() - 10), 0.5f, 0.5f);
EndDrag(gfx::Point(start.x(), start.y() - 10),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
// The window should restore back to its original position.
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
histogram_tester.ExpectBucketCount(
kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kRestoreToOriginalBounds, 1);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToHomeScreen, 0);
// Now a bigger distance to fling.
StartDrag(window.get(), start);
Drag(gfx::Point(start.x(), start.y() - 200), 0.5f, 0.5f);
EndDrag(gfx::Point(start.x(), start.y() - 200),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
// The window should be minimized.
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToHomeScreen, 1);
}
TEST_F(DragWindowFromShelfControllerTest, DragToSnapMinDistance) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window1 = CreateTestWindow();
auto window2 = CreateTestWindow();
const gfx::Rect display_bounds = display::Screen::GetScreen()
->GetDisplayNearestWindow(window1.get())
.bounds();
const int snap_edge_inset =
DragWindowFromShelfController::kScreenEdgeInsetForSnap;
base::HistogramTester histogram_tester;
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToOverviewMode,
0);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToSplitviewMode,
0);
// If the drag starts outside of the snap region and then into snap region,
// but the drag distance is not long enough.
gfx::Point start = gfx::Point(display_bounds.x() + snap_edge_inset + 50,
shelf_bounds.CenterPoint().y());
StartDrag(window1.get(), start);
Drag(start + gfx::Vector2d(0, 100), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
// Drag into the snap region and release.
gfx::Point end = gfx::Point(
start.x() - DragWindowFromShelfController::kMinDragDistance + 10, 200);
EndDrag(end, std::nullopt);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToOverviewMode,
1);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToSplitviewMode,
0);
wm::ActivateWindow(window1.get());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
// If the drag starts outside of the snap region and then into snap region
// (kScreenEdgeInsetForSnap), and the drag distance is long enough.
StartDrag(window1.get(), start);
Drag(start + gfx::Vector2d(0, 100), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
// Drag into the snap region and release.
end.set_x(start.x() - 10 - DragWindowFromShelfController::kMinDragDistance);
EndDrag(end, std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToOverviewMode,
1);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToSplitviewMode,
1);
WindowState::Get(window1.get())->Maximize();
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
// If the drag starts inside of the snap region (kScreenEdgeInsetForSnap), but
// the drag distance is not long enough.
start = gfx::Point(display_bounds.x() + snap_edge_inset - 5,
shelf_bounds.CenterPoint().y());
StartDrag(window1.get(), start);
Drag(start + gfx::Vector2d(0, 100), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
// Drag for a small distance and release.
end.set_x(start.x() - 10);
EndDrag(end, std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToOverviewMode,
2);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToSplitviewMode,
1);
wm::ActivateWindow(window1.get());
EXPECT_FALSE(overview_controller->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
// If the drag starts near the screen edge (kDistanceFromEdge), the window
// should snap directly.
start = gfx::Point(
display_bounds.x() + DragWindowFromShelfController::kDistanceFromEdge - 5,
shelf_bounds.CenterPoint().y());
StartDrag(window1.get(), start);
Drag(start + gfx::Vector2d(0, 100), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
end.set_x(start.x() - 5);
EndDrag(end, std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToOverviewMode,
2);
histogram_tester.ExpectBucketCount(kHandleDragWindowFromShelfHistogramName,
ShelfWindowDragResult::kGoToSplitviewMode,
2);
}
// Test that if overview is invisible when drag ends, the window will either be
// restored or taken to the home screen.
TEST_F(DragWindowFromShelfControllerTest, TestOverviewInvisible) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(200, 200), 0.f, 10.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
// End drag without any fling, the window should be added to overview.
EndDrag(gfx::Point(200, 200), std::nullopt);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window.get()));
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(200, 200), 0.f, 10.f);
// At this moment overview should be invisible. End the drag without any
// fling, the window should be taken to home screen.
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(200, 200), 0.f, 10.f);
// At this moment overview should be invisible. End the drag with upward
// velocity, the window should be taken to home screen.
EndDrag(gfx::Point(200, 200), -1.f);
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(200, 200), 0.f, 10.f);
// At this moment overview should be invisible. End the drag with downward
// velocity, the window should be restored.
EndDrag(gfx::Point(200, 200), 1.f);
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
}
// Test that if overview is invisible when drag ends, the window will be taken
// to the home screen, even if drag satisfied min snap distance.
TEST_F(DragWindowFromShelfControllerTest,
TestOverviewInvisibleWithMinSnapDistance) {
UpdateDisplay("500x400");
auto window = CreateTestWindow();
const gfx::Rect display_bounds = display::Screen::GetScreen()
->GetDisplayNearestWindow(window.get())
.bounds();
int snap_edge_inset =
display_bounds.width() * kHighlightScreenPrimaryAxisRatio +
kHighlightScreenEdgePaddingDp;
// Start the drag outside snap region.
gfx::Point start = gfx::Point(display_bounds.x() + snap_edge_inset + 70,
GetShelfBounds().CenterPoint().y());
StartDrag(window.get(), start);
// Drag into the snap region and release without a fling.
// At this moment overview should be invisible, so the window should be taken
// to the home screen.
gfx::Point end =
start -
gfx::Vector2d(10 + DragWindowFromShelfController::kMinDragDistance, 200);
EndDrag(end, std::nullopt);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
EXPECT_FALSE(split_view_controller()->InSplitViewMode());
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
}
// Test that the original backdrop is restored in the drag window after drag
// ends, no matter where the window ends.
TEST_F(DragWindowFromShelfControllerTest, RestoreBackdropAfterDragEnds) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window = CreateTestWindow();
WindowBackdrop* window_backdrop = WindowBackdrop::Get(window.get());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
// For window that ends in overview:
StartDrag(window.get(), shelf_bounds.CenterPoint());
EXPECT_TRUE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200), std::nullopt);
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_controller->overview_session()->IsWindowInOverview(
window.get()));
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
EXPECT_FALSE(window_backdrop->temporarily_disabled());
// For window that ends in homescreen:
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.CenterPoint());
EXPECT_TRUE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200),
std::make_optional(
-DragWindowFromShelfController::kVelocityToHomeScreenThreshold));
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
EXPECT_FALSE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
// For window that restores to its original bounds:
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.CenterPoint());
EXPECT_TRUE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
Drag(gfx::Point(200, 200), 0.f, 1.f);
EndDrag(shelf_bounds.CenterPoint(), std::nullopt);
EXPECT_FALSE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
// For window that ends in homescreen because overview did not start during
// the gesture:
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.CenterPoint());
EXPECT_TRUE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
EndDrag(gfx::Point(0, 200), std::nullopt);
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
EXPECT_FALSE(window_backdrop->temporarily_disabled());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
// For window that ends in splitscreen:
wm::ActivateWindow(window.get());
StartDrag(window.get(), shelf_bounds.CenterPoint());
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
EXPECT_TRUE(window_backdrop->temporarily_disabled());
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(0, 200), std::nullopt);
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window.get()));
EXPECT_EQ(window_backdrop->mode(), WindowBackdrop::BackdropMode::kAuto);
EXPECT_FALSE(window_backdrop->temporarily_disabled());
}
TEST_F(DragWindowFromShelfControllerTest,
DoNotChangeActiveWindowDuringDragging) {
UpdateDisplay("500x400");
auto window = CreateTestWindow();
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
StartDrag(window.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
EXPECT_TRUE(overview_controller->InOverviewSession());
// During dragging, the active window should not change.
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
OverviewSession* overview_session = overview_controller->overview_session();
EXPECT_TRUE(overview_session->IsWindowInOverview(window.get()));
// After window is added to overview, the active window should change to the
// overview focus widget.
EXPECT_EQ(overview_session->GetOverviewFocusWindow(),
window_util::GetActiveWindow());
}
// Test that if the window are dropped in overview before the overview start
// animation is completed, there is no crash.
TEST_F(DragWindowFromShelfControllerTest,
NoCrashIfDropWindowInOverviewBeforeStartAnimationComplete) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
OverviewController* overview_controller = OverviewController::Get();
overview_controller->set_delayed_animation_task_delay_for_test(
base::Milliseconds(100));
UpdateDisplay("500x400");
auto window = CreateTestWindow();
wm::ActivateWindow(window.get());
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
StartDrag(window.get(), GetShelfBounds().CenterPoint());
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EXPECT_TRUE(overview_controller->InOverviewSession());
// During dragging, the active window should not change.
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
OverviewSession* overview_session = overview_controller->overview_session();
EndDrag(gfx::Point(200, 200), std::nullopt);
EXPECT_TRUE(overview_controller->InOverviewSession());
EXPECT_TRUE(overview_session->IsWindowInOverview(window.get()));
// After window is added to overview, the active window should change to the
// overview focus widget.
EXPECT_EQ(overview_session->GetOverviewFocusWindow(),
window_util::GetActiveWindow());
ShellTestApi().WaitForOverviewAnimationState(
OverviewAnimationState::kEnterAnimationComplete);
// After start animation is done, active window should remain the same.
EXPECT_EQ(overview_session->GetOverviewFocusWindow(),
window_util::GetActiveWindow());
}
// Test that when the dragged window is dropped into overview, it is positioned
// and stacked correctly.
TEST_F(DragWindowFromShelfControllerTest, DropsIntoOverviewAtCorrectPosition) {
std::unique_ptr<aura::Window> window1 = CreateTestWindow();
std::unique_ptr<aura::Window> window2 = CreateTestWindow();
std::unique_ptr<aura::Window> window3 = CreateTestWindow();
ToggleOverview();
ui::test::EventGenerator* generator = GetEventGenerator();
generator->MoveMouseTo(gfx::ToRoundedPoint(
GetOverviewItemForWindow(window1.get())->target_bounds().CenterPoint()));
generator->DragMouseTo(0, 400);
generator->MoveMouseTo(gfx::ToRoundedPoint(
GetOverviewItemForWindow(window2.get())->target_bounds().CenterPoint()));
generator->DragMouseTo(799, 400);
EXPECT_EQ(window1.get(), split_view_controller()->primary_window());
EXPECT_EQ(window2.get(), split_view_controller()->secondary_window());
ToggleOverview();
StartDrag(window1.get(), GetShelfBounds().left_center());
Drag(gfx::Point(200, 200), 1.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200), std::nullopt);
// Verify the grid arrangement.
OverviewController* overview_controller = OverviewController::Get();
ASSERT_TRUE(overview_controller->InOverviewSession());
const std::vector<raw_ptr<aura::Window, VectorExperimental>>
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, GetWindowsListInOverviewGrids());
// Verify the stacking order.
aura::Window* parent = window1->parent();
ASSERT_EQ(parent, window2->parent());
ASSERT_EQ(parent, window3->parent());
EXPECT_TRUE(window_util::IsStackedBelow(
GetOverviewItemForWindow(window1.get())->item_widget()->GetNativeWindow(),
GetOverviewItemForWindow(window2.get())
->item_widget()
->GetNativeWindow()));
EXPECT_TRUE(window_util::IsStackedBelow(
GetOverviewItemForWindow(window3.get())->item_widget()->GetNativeWindow(),
GetOverviewItemForWindow(window1.get())
->item_widget()
->GetNativeWindow()));
}
// Test that when the dragged window is returned to maximized state, the
// overview grid does not animate as it can be jarring and use up unneeded
// resources. Regression test for http://crbug.com/1049206.
TEST_F(DragWindowFromShelfControllerTest, NoAnimationWhenReturnToMaximize) {
std::unique_ptr<aura::Window> window1 = CreateTestWindow();
std::unique_ptr<aura::Window> window2 = CreateTestWindow();
// Drag |window1| so that overview is shown.
const gfx::Point shelf_centerpoint = GetShelfBounds().CenterPoint();
StartDrag(window1.get(), shelf_centerpoint);
Drag(gfx::Point(200, 200), 1.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
// Get the bounds and transform of the item associated with |item2|.
OverviewController* overview_controller = OverviewController::Get();
ASSERT_TRUE(overview_controller->InOverviewSession());
auto* item = GetOverviewItemForWindow(window2.get());
ASSERT_TRUE(item);
aura::Window* item_window = item->item_widget()->GetNativeWindow();
const gfx::Rect pre_exit_bounds = item_window->bounds();
const gfx::Transform pre_exit_transform = item_window->transform();
// Drag back to the shelf, |window2|'s overview item should not move.
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
EndDrag(shelf_centerpoint, std::nullopt);
EXPECT_EQ(pre_exit_bounds, item_window->bounds());
EXPECT_EQ(pre_exit_transform, item_window->layer()->GetTargetTransform());
// Tests that the end drag actually exited and remaximized |window1|.
ShellTestApi().WaitForOverviewAnimationState(
OverviewAnimationState::kExitAnimationComplete);
EXPECT_TRUE(WindowState::Get(window1.get())->IsMaximized());
}
// Tests that when dragging a snapped window is cancelled, the window
// still keep at the original snap position.
TEST_F(DragWindowFromShelfControllerTest,
KeepSplitWindowSnappedAfterRestoreToOriginalBounds) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
auto window1 = CreateTestWindow();
auto window2 = CreateTestWindow();
// In splitview mode, the snapped windows will stay visible during dragging.
split_view_controller()->SnapWindow(window1.get(), SnapPosition::kPrimary);
split_view_controller()->SnapWindow(window2.get(), SnapPosition::kSecondary);
// Try to drag a left snapped window from shelf, but finally restore to
// original bounds.
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 1.f, 1.f);
EndDrag(shelf_bounds.bottom_left(), /*velocity_y=*/std::nullopt);
// Ensure that the window still keep its initial snap position.
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window1.get()),
SnapPosition::kPrimary);
// Try to drag a right snapped window from shelf, and finally drop to
// overview.
StartDrag(window2.get(), shelf_bounds.right_center());
Drag(gfx::Point(400, 200), 1.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
OverviewController* overview_controller = OverviewController::Get();
OverviewSession* overview_session = overview_controller->overview_session();
EndDrag(gfx::Point(200, 200), /*velocity_y=*/std::nullopt);
// Ensure that the window is not in splitview but in overview.
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window2.get()));
EXPECT_TRUE(overview_session->IsWindowInOverview(window2.get()));
// Try to drag the left window again within the restore distance.
StartDrag(window1.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 1.f, 1.f);
EndDrag(shelf_bounds.bottom_left(), /*velocity_y=*/std::nullopt);
// Ensure that the left window still keep snapped.
EXPECT_TRUE(split_view_controller()->IsWindowInSplitView(window1.get()));
EXPECT_EQ(split_view_controller()->GetPositionOfSnappedWindow(window1.get()),
SnapPosition::kPrimary);
// Ensure that the right window is still in the overview, and doesn't get
// minimized.
EXPECT_FALSE(split_view_controller()->IsWindowInSplitView(window2.get()));
EXPECT_FALSE(WindowState::Get(window2.get())->IsMinimized());
EXPECT_TRUE(overview_session->IsWindowInOverview(window2.get()));
}
// Tests that even if the animation for transient child is completed first, the
// transient child will become visible after returning back to the window from
// overview mode. Regression test for https://crbug.com/1240843.
TEST_F(DragWindowFromShelfControllerTest,
TransientChildWindowIsVisibleAfterMinimizingOnFastAnimation) {
// Use the fast animation for transient child window to ensure its animation
// could be done faster than its parent.
auto enable_fast_animation_for_transient_child =
WindowScaleAnimation::EnableScopedFastAnimationForTransientChildForTest();
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
UpdateDisplay("1366x768");
auto window = CreateTestWindow();
auto window_transient = CreateTransientModalChildWindow(
window.get(), gfx::Rect(0, 20, 1366, 728));
wm::TransientWindowManager::GetOrCreate(window_transient.get())
->set_parent_controls_visibility(true);
StartDrag(window_transient.get(), GetShelfBounds().right_center());
Drag(gfx::Point(745, 616), 47, -47);
EndDrag(gfx::Point(1366, 0), -1815.28);
WaitForHomeLauncherAnimationToFinish();
aura::Window* home_screen_window =
Shell::Get()->app_list_controller()->GetHomeScreenWindow();
EXPECT_TRUE(home_screen_window);
EXPECT_TRUE(home_screen_window->IsVisible());
EXPECT_FALSE(window->IsVisible());
EXPECT_FALSE(window_transient->IsVisible());
OverviewController* overview_controller = OverviewController::Get();
overview_controller->StartOverview(OverviewStartAction::kExitHomeLauncher);
ASSERT_TRUE(overview_controller->InOverviewSession());
auto* overview_grid =
overview_controller->overview_session()->GetGridWithRootWindow(
Shell::GetPrimaryRootWindow());
EXPECT_TRUE(overview_grid);
const auto& overview_items = overview_grid->item_list();
ASSERT_EQ(1u, overview_items.size());
// Press on `overview_item` to exit overview mode and show windows.
auto* overview_item = overview_items[0].get();
auto* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(
gfx::ToRoundedPoint(overview_item->GetTransformedBounds().CenterPoint()));
event_generator->PressTouch();
event_generator->ReleaseTouch();
ASSERT_FALSE(overview_controller->InOverviewSession());
// Both transient child and parent windows should become visible.
EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(window_transient->IsVisible());
}
// Tests that dragging a window which has multiple transient child windows from
// shelf should work properly. Regression test for crash in
// `WindowScaleAnimation::DestroyWindowAnimationObserver`.
// https://crbug.com/1263183
TEST_F(DragWindowFromShelfControllerTest,
DragWindowWithMultipleTransientChildWindows) {
// Specify using `ZERO_DURATION` here to make sure the drag will still work
// even all the animations are no-ops.
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
auto window = CreateTestWindow();
auto transient_child_win1 = CreateTransientModalChildWindow(
window.get(), gfx::Rect(0, 20, 1366, 728));
auto transient_child_win2 = CreateTransientModalChildWindow(
window.get(), gfx::Rect(100, 100, 1000, 800));
wm::TransientWindowManager::GetOrCreate(transient_child_win1.get())
->set_parent_controls_visibility(true);
wm::TransientWindowManager::GetOrCreate(transient_child_win2.get())
->set_parent_controls_visibility(true);
StartDrag(window.get(), GetShelfBounds().right_center());
Drag(gfx::Point(745, 616), 47, -47);
EndDrag(gfx::Point(1366, 0), -1815.28);
WaitForHomeLauncherAnimationToFinish();
aura::Window* home_screen_window =
Shell::Get()->app_list_controller()->GetHomeScreenWindow();
EXPECT_TRUE(home_screen_window);
EXPECT_TRUE(home_screen_window->IsVisible());
EXPECT_FALSE(window->IsVisible());
EXPECT_FALSE(transient_child_win1->IsVisible());
EXPECT_FALSE(transient_child_win2->IsVisible());
}
// Tests that destroying a trasient child that is being dragged from the shelf
// does not result in a crash. Regression test for https://crbug.com/1200596.
TEST_F(DragWindowFromShelfControllerTest, DestroyTransientWhileAnimating) {
const gfx::Rect shelf_bounds = GetShelfBounds();
// The crash occurred while destroying an animating window.
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
// The transient child needs to also be an app window.
auto window = CreateAppWindow();
auto child = CreateAppWindow();
wm::AddTransientChild(window.get(), child.get());
// Drag the child barely above the shelf so that it returns to its original
// position on release. The drag can go anywhere as long as the window moves
// and the release is close to the top of the shelf.
StartDrag(child.get(), shelf_bounds.right_center());
Drag(gfx::Point(100, 100), 1.f, 1.f);
EndDrag(gfx::Point(shelf_bounds.width() / 2, shelf_bounds.y() - 10),
/*velocity_y=*/std::nullopt);
ASSERT_TRUE(window->layer()->GetAnimator()->is_animating());
ASSERT_TRUE(child->layer()->GetAnimator()->is_animating());
// Destroy the transient child during animation. There should be no crash.
child.reset();
}
// Tests that destroying a dragged window in split view will not cause crash.
TEST_F(DragWindowFromShelfControllerTest,
DestroyWindowDuringDraggingInSplitView) {
UpdateDisplay("500x400");
const gfx::Rect shelf_bounds = GetShelfBounds();
// Create a window and snapped to the left in split screen.
auto window = CreateTestWindow();
split_view_controller()->SnapWindow(window.get(), SnapPosition::kPrimary);
// Try to drag the window from shelf.
StartDrag(window.get(), shelf_bounds.left_center());
Drag(gfx::Point(0, 200), 1.f, 1.f);
// Destroy the window while dragging. Expect no crash.
window.reset();
EndDrag(shelf_bounds.CenterPoint(), /*velocity_y=*/std::nullopt);
}
// Tests that there should be no crash if we exit overview by switching desks
// during window dragging. See details in http://b/326074747.
TEST_F(DragWindowFromShelfControllerTest, NoCrashDuringDraggingIfExitOverview) {
UpdateDisplay("500x400");
ui::ScopedAnimationDurationScaleMode animation_scale(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Create a new Desk.
auto* desk_controller = DesksController::Get();
desk_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
const Desk* new_desk = desk_controller->GetDeskAtIndex(1);
auto window1 = CreateAppWindow();
StartDrag(window1.get(), GetShelfBounds().CenterPoint());
// Drag it far enough so overview should be open behind the dragged window.
Drag(gfx::Point(200, 200), 0.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
// Switch desks which will end overview.
desk_controller->ActivateDesk(new_desk,
DesksSwitchSource::kDeskButtonMiniViewButton);
WaitForOverviewExitAnimation();
// Before desk switch animation is done, continue dragging the window. There
// should be no crash.
Drag(gfx::Point(200, 100), 0.f, 1.f);
EndDrag(GetShelfBounds().CenterPoint(), /*velocity_y=*/std::nullopt);
}
class FloatDragWindowFromShelfControllerTest
: public DragWindowFromShelfControllerTest {
public:
FloatDragWindowFromShelfControllerTest() = default;
FloatDragWindowFromShelfControllerTest(
const FloatDragWindowFromShelfControllerTest&) = delete;
FloatDragWindowFromShelfControllerTest& operator=(
const FloatDragWindowFromShelfControllerTest&) = delete;
~FloatDragWindowFromShelfControllerTest() override = default;
ui::Layer* GetOtherWindowCopyLayer() {
return DragWindowFromShelfControllerTestApi().GetOtherWindowCopyLayer(
window_drag_controller());
}
// Creates a floated application window.
std::unique_ptr<aura::Window> CreateFloatedWindow() {
std::unique_ptr<aura::Window> floated_window = CreateAppWindow();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
DCHECK(WindowState::Get(floated_window.get())->IsFloated());
return floated_window;
}
};
TEST_F(FloatDragWindowFromShelfControllerTest, DragFloatedWindow) {
const gfx::Rect shelf_bounds = GetShelfBounds();
// Create one maximized and one floated window.
auto maximized_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
wm::ActivateWindow(floated_window.get());
const gfx::Point start_drag_point(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
// Try to drag the window from shelf.
StartDrag(floated_window.get(), start_drag_point);
ui::Layer* other_window_copy_layer = GetOtherWindowCopyLayer();
ASSERT_TRUE(other_window_copy_layer);
// To check if the copy is of the maximized window, we check the parent and
// bounds.
EXPECT_EQ(maximized_window->layer()->parent(),
other_window_copy_layer->parent());
EXPECT_EQ(maximized_window->layer()->bounds(),
other_window_copy_layer->bounds());
Drag(gfx::Point(0, 200), 1.f, 1.f);
EndDrag(shelf_bounds.CenterPoint(), /*velocity_y=*/std::nullopt);
EXPECT_FALSE(GetOtherWindowCopyLayer());
}
TEST_F(FloatDragWindowFromShelfControllerTest, DragMaximizedWindow) {
const gfx::Rect shelf_bounds = GetShelfBounds();
// Create one maximized and one floated window.
auto maximized_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
wm::ActivateWindow(maximized_window.get());
const gfx::Point start_drag_point(
maximized_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
// Try to drag the window from shelf.
StartDrag(maximized_window.get(), start_drag_point);
ui::Layer* other_window_copy_layer = GetOtherWindowCopyLayer();
ASSERT_TRUE(other_window_copy_layer);
// To check if the copy is of the floated window, we check the bounds. The
// float container gets stacked under the desk containers during overview, so
// the copy should be on a different parent.
EXPECT_EQ(floated_window->layer()->bounds(),
other_window_copy_layer->bounds());
EXPECT_NE(floated_window->layer()->parent(),
other_window_copy_layer->parent());
Drag(gfx::Point(0, 200), 1.f, 1.f);
EndDrag(shelf_bounds.CenterPoint(), /*velocity_y=*/std::nullopt);
EXPECT_FALSE(GetOtherWindowCopyLayer());
}
// Tests that when dragging from shelf with a floated window into overview, the
// window state does not change on overview exit.
TEST_F(FloatDragWindowFromShelfControllerTest, WindowStatePreserved) {
// Create one maximized and one floated window.
auto maximized_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
wm::ActivateWindow(maximized_window.get());
// Perform a drag such that we end up in overview.
const gfx::Point start_drag_point(
floated_window->GetBoundsInScreen().CenterPoint().x(),
GetShelfBounds().CenterPoint().y());
StartDrag(floated_window.get(), start_drag_point);
Drag(gfx::Point(200, 200), 1.f, 1.f);
DragWindowFromShelfControllerTestApi().WaitUntilOverviewIsShown(
window_drag_controller());
EndDrag(gfx::Point(200, 200), /*velocity_y=*/std::nullopt);
// Verify that on exiting overview, the original window state is preserved
// (neither window is minimized).
OverviewController* overview_controller = OverviewController::Get();
ASSERT_TRUE(overview_controller->InOverviewSession());
ExitOverview();
EXPECT_TRUE(WindowState::Get(maximized_window.get())->IsMaximized());
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
}
// Tests that the correct window (if any) gets chosen by the shelf layout
// manager when there is a floated window.
TEST_F(FloatDragWindowFromShelfControllerTest, DraggingFloatedWindow) {
UpdateDisplay("800x600");
auto floated_window = CreateFloatedWindow();
// Start dragging on the shelf, but not under the floated window. Verify that
// nothing gets dragged.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_not_under_float(100,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_not_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_FALSE(drag_controller);
GetEventGenerator()->ReleaseTouch();
// Drag under the floated window. Verify that the float window gets dragged.
const gfx::Rect float_bounds = floated_window->GetBoundsInScreen();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());
// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();
ASSERT_FALSE(GetDragWindowFromShelfController()->drag_started());
// We need to force a layout to start dragging. Drag the window so that it is
// magnetized to the top edge.
views::test::RunScheduledLayout(
NonClientFrameViewAsh::Get(floated_window.get()));
GetEventGenerator()->PressTouch(float_bounds.top_center() +
gfx::Vector2d(0, 10));
GetEventGenerator()->MoveTouchBy(0, -100);
GetEventGenerator()->ReleaseTouch();
EXPECT_NE(float_bounds, floated_window->GetBoundsInScreen());
// Since the floated window is magnetized to the top, dragging on the shelf
// does nothing.
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
GetEventGenerator()->ReleaseTouch();
EXPECT_FALSE(GetDragWindowFromShelfController()->drag_started());
}
// Tests that the correct window gets chosen by the shelf layout manager when
// there is a floated and maximized window.
TEST_F(FloatDragWindowFromShelfControllerTest,
DraggingFloatedAndMaximizedWindow) {
UpdateDisplay("800x600");
// Create one maximized and one floated window.
auto maximized_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
wm::ActivateWindow(maximized_window.get());
// Drag under the floated window. Even though the maximized window is active,
// the floated window is the one that is dragged.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());
// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();
// Drag under the maximized window. Even though the floated window is active,
// the maximized window is the one that is dragged.
wm::ActivateWindow(floated_window.get());
const gfx::Point drag_point_under_maximize(100,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_maximize);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(maximized_window.get(), drag_controller->dragged_window());
}
// Tests that the correct window gets chosen by the shelf layout manager when
// there are floated and snapped windows.
TEST_F(FloatDragWindowFromShelfControllerTest,
DraggingFloatedAndSnappedWindow) {
UpdateDisplay("800x600");
// Create two snapped and one floated window.
auto left_window = CreateTestWindow();
auto right_window = CreateTestWindow();
auto floated_window = CreateFloatedWindow();
split_view_controller()->SnapWindow(left_window.get(),
SnapPosition::kPrimary);
split_view_controller()->SnapWindow(right_window.get(),
SnapPosition::kSecondary);
// Ensure we are in a both snapped state with a floated window.
wm::ActivateWindow(floated_window.get());
ASSERT_TRUE(WindowState::Get(left_window.get())->IsSnapped());
ASSERT_TRUE(WindowState::Get(right_window.get())->IsSnapped());
ASSERT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
// Verify that the floated window by default is magnetized to the bottom right
// corner.
const gfx::Rect work_area =
WorkAreaInsets::ForWindow(floated_window.get())->user_work_area_bounds();
ASSERT_EQ(
gfx::Point(work_area.right() - chromeos::wm::kFloatedWindowPaddingDp,
work_area.bottom() - chromeos::wm::kFloatedWindowPaddingDp),
floated_window->GetBoundsInScreen().bottom_right());
// Drag under the floated window. It should be the dragged window.
const gfx::Rect shelf_bounds = GetShelfBounds();
const gfx::Point drag_point_under_float(
floated_window->GetBoundsInScreen().CenterPoint().x(),
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_float);
GetEventGenerator()->MoveTouchBy(0, -200);
auto* drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(floated_window.get(), drag_controller->dragged_window());
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
// Move back towards the shelf to ensure we do not enter overview.
GetEventGenerator()->MoveTouchBy(0, 200);
GetEventGenerator()->ReleaseTouch();
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
// Drag under the right snapped window. It should be the dragged window.
const gfx::Point drag_point_under_right(
right_window->GetBoundsInScreen().x() + 10,
shelf_bounds.CenterPoint().y());
GetEventGenerator()->PressTouch(drag_point_under_right);
GetEventGenerator()->MoveTouchBy(0, -200);
drag_controller = GetDragWindowFromShelfController();
ASSERT_TRUE(drag_controller);
EXPECT_EQ(right_window.get(), drag_controller->dragged_window());
// Verify that all the window states remain the same.
EXPECT_TRUE(WindowState::Get(left_window.get())->IsSnapped());
EXPECT_TRUE(WindowState::Get(right_window.get())->IsSnapped());
EXPECT_TRUE(WindowState::Get(floated_window.get())->IsFloated());
}
} // namespace ash