#include "ash/wm/float/float_controller.h"
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/accessibility/magnifier/docked_magnifier_controller.h"
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/display/screen_orientation_controller_test_api.h"
#include "ash/frame/frame_view_ash.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/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/cursor_manager_chromeos.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desk_mini_view.h"
#include "ash/wm/desks/desks_test_api.h"
#include "ash/wm/desks/desks_test_util.h"
#include "ash/wm/desks/overview_desk_bar_view.h"
#include "ash/wm/float/float_test_api.h"
#include "ash/wm/float/tablet_mode_float_window_resizer.h"
#include "ash/wm/float/tablet_mode_tuck_education.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_item.h"
#include "ash/wm/overview/overview_test_util.h"
#include "ash/wm/scoped_window_tucker.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_metrics_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/test/test_frame_view_ash.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/work_area_insets.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/scoped_observation.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/time/clock.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/caption_buttons/snap_controller.h"
#include "chromeos/ui/frame/header_view.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
#include "chromeos/ui/wm/constants.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window_observer.h"
#include "ui/base/hit_test.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/display/display_switches.h"
#include "ui/display/test/display_manager_test_api.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/gfx/scoped_animation_duration_scale_mode.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
chromeos::HeaderView* GetHeaderView(aura::Window* window) {
auto* frame = FrameViewAsh::Get(window);
DCHECK(frame);
views::test::RunScheduledLayout(frame);
return frame->GetHeaderView();
}
bool IsVisiblyAnimating(aura::Window* window) {
DCHECK(window);
ui::LayerAnimator* animator = window->layer()->GetAnimator();
return animator->is_animating() && animator->tween_type() != gfx::Tween::ZERO;
}
class WindowLayerRecreatedCounter : public aura::WindowObserver {
public:
explicit WindowLayerRecreatedCounter(aura::Window* window) {
window_observation_.Observe(window);
}
WindowLayerRecreatedCounter(const WindowLayerRecreatedCounter&) = delete;
WindowLayerRecreatedCounter& operator=(const WindowLayerRecreatedCounter&) =
delete;
~WindowLayerRecreatedCounter() override = default;
int recreated_count() const { return recreated_count_; }
void OnWindowDestroying(aura::Window* window) override {
window_observation_.Reset();
}
void OnWindowLayerRecreated(aura::Window* window) override {
++recreated_count_;
}
private:
int recreated_count_ = 0;
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
};
}
class WindowFloatTest : public AshTestBase {
public:
WindowFloatTest() = default;
explicit WindowFloatTest(base::test::TaskEnvironment::TimeSource time)
: AshTestBase(time) {}
WindowFloatTest(const WindowFloatTest&) = delete;
WindowFloatTest& operator=(const WindowFloatTest&) = delete;
~WindowFloatTest() override = default;
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);
CHECK(WindowState::Get(floated_window.get())->IsFloated());
return floated_window;
}
};
TEST_F(WindowFloatTest, WindowFloatingSwitch) {
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
EXPECT_FALSE(WindowState::Get(window_1.get())->IsFloated());
wm::ActivateWindow(window_2.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(WindowState::Get(window_2.get())->IsFloated());
}
TEST_F(WindowFloatTest, DoubleClickOnCaption) {
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
chromeos::HeaderView* header_view = GetHeaderView(window.get());
auto* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(
header_view->GetBoundsInScreen().CenterPoint());
event_generator->DoubleClickLeftButton();
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
}
TEST_F(WindowFloatTest, FloatWindowAnimatesInOverview) {
std::unique_ptr<aura::Window> floated_window = CreateFloatedWindow();
std::unique_ptr<aura::Window> maximized_window = CreateAppWindow();
const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
WindowState::Get(maximized_window.get())->OnWMEvent(&maximize_event);
wm::ActivateWindow(maximized_window.get());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ToggleOverview();
EXPECT_TRUE(floated_window->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(maximized_window->layer()->GetAnimator()->is_animating());
WaitForOverviewEnterAnimation();
ToggleOverview();
EXPECT_TRUE(floated_window->layer()->GetAnimator()->is_animating());
EXPECT_TRUE(maximized_window->layer()->GetAnimator()->is_animating());
}
TEST_F(WindowFloatTest, FloatToMaximizeWindowAnimates) {
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
const WMEvent maximize_event(WM_EVENT_MAXIMIZE);
WindowState::Get(window.get())->OnWMEvent(&maximize_event);
EXPECT_TRUE(window->layer()->GetAnimator()->is_animating());
EXPECT_NE(window->layer()->transform(),
window->layer()->GetTargetTransform());
}
TEST_F(WindowFloatTest, WindowFloatingResize) {
std::unique_ptr<aura::Window> window = CreateAppWindow(gfx::Rect(200, 200));
auto* window_state = WindowState::Get(window.get());
window_state->Maximize();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(window_state->IsFloated());
gfx::Rect default_float_bounds =
FloatController::GetFloatWindowClamshellBounds(
window.get(), chromeos::FloatStartLocation::kBottomRight);
EXPECT_EQ(default_float_bounds, window->GetBoundsInScreen());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(window_state->IsFloated());
EXPECT_TRUE(window_state->IsMaximized());
const WMEvent toggle_fullscreen_event(WM_EVENT_TOGGLE_FULLSCREEN);
window_state->OnWMEvent(&toggle_fullscreen_event);
EXPECT_TRUE(window_state->IsFullscreen());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(window_state->IsFloated());
default_float_bounds = FloatController::GetFloatWindowClamshellBounds(
window.get(), chromeos::FloatStartLocation::kBottomRight);
EXPECT_EQ(default_float_bounds, window->GetBoundsInScreen());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(window_state->IsFloated());
EXPECT_TRUE(window_state->IsMaximized());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(window_state->IsFloated());
const gfx::Rect curr_bounds = window->GetBoundsInScreen();
window_state->Minimize();
window_state->Restore();
EXPECT_EQ(curr_bounds, window->GetBoundsInScreen());
EXPECT_TRUE(window_state->IsFloated());
auto window2 = CreateAppWindow(default_float_bounds);
auto* window_state2 = WindowState::Get(window2.get());
AcceleratorControllerImpl* acc_controller =
Shell::Get()->accelerator_controller();
acc_controller->PerformActionIfEnabled(
AcceleratorAction::kWindowCycleSnapLeft, {});
ASSERT_EQ(chromeos::WindowStateType::kPrimarySnapped,
window_state2->GetStateType());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_EQ(default_float_bounds, window2->GetBoundsInScreen());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_EQ(chromeos::WindowStateType::kPrimarySnapped,
window_state2->GetStateType());
acc_controller->PerformActionIfEnabled(
AcceleratorAction::kWindowCycleSnapRight, {});
ASSERT_EQ(chromeos::WindowStateType::kSecondarySnapped,
WindowState::Get(window2.get())->GetStateType());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_EQ(default_float_bounds, window2->GetBoundsInScreen());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_EQ(chromeos::WindowStateType::kSecondarySnapped,
window_state2->GetStateType());
}
TEST_F(WindowFloatTest, RestoreResizeBounds) {
auto* desks_controller = DesksController::Get();
NewDesk();
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* event_generator = GetEventGenerator();
chromeos::HeaderView* header_view = GetHeaderView(window.get());
event_generator->set_current_screen_location(
header_view->GetBoundsInScreen().CenterPoint());
event_generator->DragMouseTo(gfx::Point(100, 100));
const gfx::Rect resize_bounds(window->bounds());
ActivateDesk(desks_controller->desks()[1].get());
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_FALSE(window->IsVisible());
ActivateDesk(desks_controller->desks()[0].get());
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_TRUE(window->IsVisible());
EXPECT_EQ(resize_bounds, window->bounds());
auto* window_state = WindowState::Get(window.get());
window_state->Minimize();
window_state->Restore();
EXPECT_EQ(resize_bounds, window->bounds());
}
TEST_F(WindowFloatTest, CantFloatAccelerator) {
auto window = CreateTestWindow();
EXPECT_EQ(window.get(), window_util::GetActiveWindow());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(WindowFloatTest, DragToOtherDisplayThenMaximize) {
UpdateDisplay("1200x800,1201+0-1200x800");
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
ASSERT_EQ(Shell::GetAllRootWindows()[0], window->GetRootWindow());
chromeos::HeaderView* header_view = GetHeaderView(window.get());
auto* event_generator = GetEventGenerator();
event_generator->set_current_screen_location(
header_view->GetBoundsInScreen().CenterPoint());
const gfx::Point point(1600, 400);
Shell::Get()->cursor_manager()->SetDisplay(
display::Screen::Get()->GetDisplayNearestPoint(point));
event_generator->DragMouseTo(point);
ASSERT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
WindowState* window_state = WindowState::Get(window.get());
ASSERT_TRUE(window_state->IsFloated());
const WMEvent maximize(WM_EVENT_MAXIMIZE);
window_state->OnWMEvent(&maximize);
EXPECT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
}
TEST_F(WindowFloatTest, FloatOnOtherDisplay) {
UpdateDisplay("1200x800,1201+0-1200x800");
std::unique_ptr<aura::Window> window =
CreateAppWindow(gfx::Rect(1200, 0, 300, 300));
ASSERT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(
gfx::Rect(1200, 0, 1200, 800).Contains(window->GetBoundsInScreen()));
}
TEST_F(WindowFloatTest, MoveFloatedWindowToOtherDisplay) {
UpdateDisplay("1200x800,1201+0-600x800");
auto window = CreateAppWindow(gfx::Rect(1100, 700));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
CHECK(WindowState::Get(window.get())->IsFloated());
CHECK_NE(gfx::Size(1100, 700), window->bounds().size());
const gfx::Size primary_display_size = window->bounds().size();
PressAndReleaseKey(ui::VKEY_M, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
EXPECT_TRUE(Shell::GetAllRootWindows()[1]->GetBoundsInScreen().Intersects(
window->GetBoundsInScreen()));
EXPECT_EQ(primary_display_size, window->bounds().size());
PressAndReleaseKey(ui::VKEY_M, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_EQ(Shell::GetAllRootWindows()[0], window->GetRootWindow());
EXPECT_TRUE(Shell::GetAllRootWindows()[0]->GetBoundsInScreen().Intersects(
window->GetBoundsInScreen()));
EXPECT_EQ(primary_display_size, window->bounds().size());
}
TEST_F(WindowFloatTest, FloatWindowBoundsWithZoomDisplay) {
UpdateDisplay("1600x1000");
std::unique_ptr<aura::Window> window =
CreateAppWindow(gfx::Rect(1200, 0, 400, 300));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
PressAndReleaseKey(ui::VKEY_OEM_PLUS,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
PressAndReleaseKey(ui::VKEY_OEM_PLUS,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
PressAndReleaseKey(ui::VKEY_OEM_PLUS,
ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
EXPECT_TRUE(WorkAreaInsets::ForWindow(window.get())
->user_work_area_bounds()
.Contains(window->GetBoundsInScreen()));
}
TEST_F(WindowFloatTest, FloatWindowBoundsWithShelfChange) {
UpdateDisplay("1600x1000");
ASSERT_EQ(ShelfAlignment::kBottom, GetPrimaryShelf()->alignment());
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
const gfx::Rect ideal_bounds(1400, 0, 400, 300);
const SetBoundsWMEvent set_bounds_event(ideal_bounds);
WindowState::Get(window.get())->OnWMEvent(&set_bounds_event);
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
EXPECT_EQ(ideal_bounds, window->GetBoundsInScreen());
}
TEST_F(WindowFloatTest, OneFloatWindowPerDeskLogic) {
auto* desks_controller = DesksController::Get();
NewDesk();
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
EXPECT_FALSE(window_1->IsVisible());
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
EXPECT_TRUE(desk_2->is_active());
EXPECT_TRUE(WindowState::Get(window_1.get())->IsFloated());
EXPECT_TRUE(WindowState::Get(window_2.get())->IsFloated());
}
TEST_F(WindowFloatTest, BelongsToActiveDesk) {
NewDesk();
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
EXPECT_TRUE(desks_util::BelongsToActiveDesk(window.get()));
ActivateDesk(DesksController::Get()->desks()[1].get());
EXPECT_FALSE(desks_util::BelongsToActiveDesk(window.get()));
}
TEST_F(WindowFloatTest, FloatWindowWithDeskRemoval) {
auto* desks_controller = DesksController::Get();
NewDesk();
auto* desk_1 = desks_controller->desks()[0].get();
auto* desk_2 = desks_controller->desks()[1].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
ActivateDesk(desk_2);
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_2.get()), desk_2);
RemoveDesk(desk_2);
EXPECT_TRUE(WindowState::Get(window_1.get())->IsFloated());
EXPECT_FALSE(WindowState::Get(window_2.get())->IsFloated());
EXPECT_TRUE(window_1->IsVisible());
EXPECT_TRUE(window_2->IsVisible());
NewDesk();
desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
RemoveDesk(desk_1);
EXPECT_TRUE(WindowState::Get(window_1.get())->IsFloated());
}
TEST_F(WindowFloatTest, FloatWindowWithDeskRemovalUndo) {
auto* desks_controller = DesksController::Get();
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
std::unique_ptr<aura::Window> window(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window.get()), desk_2);
EnterOverview();
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
RemoveDesk(desk_2, DeskCloseType::kCloseAllWindowsAndWait);
ASSERT_TRUE(desk_2->is_desk_being_removed());
EXPECT_FALSE(window->IsVisible());
views::LabelButton* dismiss_button =
DesksTestApi::GetCloseAllUndoToastDismissButton();
LeftClickOn(dismiss_button);
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_EQ(float_controller->FindFloatedWindowOfDesk(desk_2), window.get());
EXPECT_TRUE(window->IsVisible());
auto* window_widget = views::Widget::GetWidgetForNativeView(window.get());
views::test::TestWidgetObserver observer(window_widget);
window.release();
RemoveDesk(desk_2, DeskCloseType::kCloseAllWindows);
ASSERT_EQ(desks_controller->desks().size(), 1u);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(observer.widget_closed());
}
TEST_F(WindowFloatTest, FloatWindowWithMRUWindowList) {
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_2.get()), desk_2);
ActivateDesk(desk_1);
EXPECT_FALSE(desk_2->is_active());
auto active_only_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
EXPECT_TRUE(base::Contains(active_only_list, window_1.get()));
EXPECT_FALSE(base::Contains(active_only_list, window_2.get()));
auto all_desks_mru_list =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
EXPECT_TRUE(base::Contains(all_desks_mru_list, window_1.get()));
EXPECT_TRUE(base::Contains(all_desks_mru_list, window_2.get()));
}
TEST_F(WindowFloatTest, MoveFloatWindowBetweenDesks) {
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_1);
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
ActivateDesk(desk_1);
auto* overview_controller = OverviewController::Get();
EnterOverview();
auto* overview_session = overview_controller->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window_1.get());
auto* grid =
overview_session->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, grid->GetNumWindows());
const auto* desks_bar_view = grid->desks_bar_view();
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
gfx::Point desk_2_mini_view_center =
desk_2_mini_view->GetBoundsInScreen().CenterPoint();
DragItemToPoint(overview_item, desk_2_mini_view_center, GetEventGenerator(),
false,
true);
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_2);
ASSERT_FALSE(WindowState::Get(window_2.get())->IsFloated());
}
TEST_F(WindowFloatTest, MoveFloatWindowBetweenDesksOnDifferentDisplay) {
UpdateDisplay("1000x400,1000+0-1000x400");
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_1);
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
std::unique_ptr<aura::Window> window_2(CreateFloatedWindow());
ActivateDesk(desk_1);
auto* overview_controller = OverviewController::Get();
EnterOverview();
auto* overview_session = overview_controller->overview_session();
auto roots = Shell::GetAllRootWindows();
ASSERT_EQ(2u, roots.size());
aura::Window* primary_root = roots[0];
aura::Window* secondary_root = roots[1];
auto* overview_item =
overview_session->GetOverviewItemForWindow(window_1.get());
auto* grid1 = overview_session->GetGridWithRootWindow(primary_root);
auto* grid2 = overview_session->GetGridWithRootWindow(secondary_root);
EXPECT_EQ(1u, grid1->GetNumWindows());
EXPECT_EQ(grid1, overview_item->overview_grid());
EXPECT_EQ(0u, grid2->GetNumWindows());
const auto* desks_bar_view = grid2->desks_bar_view();
auto* desk_2_mini_view = desks_bar_view->mini_views()[1].get();
gfx::Point desk_2_mini_view_center =
desk_2_mini_view->GetBoundsInScreen().CenterPoint();
DragItemToPoint(overview_item, desk_2_mini_view_center, GetEventGenerator(),
false,
true);
ASSERT_FALSE(WindowState::Get(window_2.get())->IsFloated());
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_2);
EXPECT_TRUE(secondary_root->Contains(window_1.get()));
}
TEST_F(WindowFloatTest, FloatWindowWorkAreaConsiderations) {
UpdateDisplay("1600x1000");
std::unique_ptr<aura::Window> window =
CreateAppWindow(gfx::Rect(1000, 100, 300, 300));
DockedMagnifierController* docked_magnifier_controller =
Shell::Get()->docked_magnifier_controller();
docked_magnifier_controller->SetEnabled(true);
ASSERT_GT(docked_magnifier_controller->GetMagnifierHeightForTesting(), 0);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_GT(window->GetBoundsInScreen().y(),
docked_magnifier_controller->GetMagnifierHeightForTesting());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
GetEventGenerator()->GestureScrollSequence(
GetHeaderView(window.get())->GetBoundsInScreen().CenterPoint(),
gfx::Point(1000, 300), base::Seconds(3),
10);
EXPECT_GT(window->GetBoundsInScreen().y(),
docked_magnifier_controller->GetMagnifierHeightForTesting());
}
TEST_F(WindowFloatTest, UnminimizeWithFloatedWindow) {
auto window1 = CreateAppWindow();
auto window2 = CreateFloatedWindow();
WindowState::Get(window2.get())->Minimize();
ASSERT_EQ(window1.get(), window_util::GetActiveWindow());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window1.get())->IsFloated());
WindowState::Get(window2.get())->Unminimize();
EXPECT_TRUE(WindowState::Get(window1.get())->IsFloated());
}
TEST_F(WindowFloatTest, FloatWindowShouldNotBlockKeyboardEvents) {
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_1);
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
auto* overview_controller = OverviewController::Get();
ASSERT_FALSE(overview_controller->InOverviewSession());
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE);
ASSERT_TRUE(overview_controller->InOverviewSession());
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE);
ASSERT_FALSE(overview_controller->InOverviewSession());
PressAndReleaseKey(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE);
ASSERT_TRUE(overview_controller->InOverviewSession());
}
TEST_F(WindowFloatTest, FloatWindowActivationActivatesBelongingDesk) {
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window_1(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_1);
NewDesk();
auto* desk_2 = desks_controller->desks()[1].get();
ActivateDesk(desk_2);
DeskSwitchAnimationWaiter waiter;
wm::ActivateWindow(window_1.get());
EXPECT_TRUE(wm::IsActiveWindow(window_1.get()));
waiter.Wait();
EXPECT_TRUE(desk_1->is_active());
}
TEST_F(WindowFloatTest, FloatWindowActivatesWhenChangingDesks) {
auto* desks_controller = DesksController::Get();
std::unique_ptr<aura::Window> floated_window1 = CreateFloatedWindow();
ASSERT_TRUE(WindowState::Get(floated_window1.get())->IsActive());
NewDesk();
ActivateDesk(desks_controller->desks()[1].get());
std::unique_ptr<aura::Window> floated_window2 = CreateFloatedWindow();
std::unique_ptr<aura::Window> normal_window = CreateAppWindow();
ASSERT_TRUE(WindowState::Get(normal_window.get())->IsActive());
ActivateDesk(desks_controller->desks()[0].get());
EXPECT_TRUE(WindowState::Get(floated_window1.get())->IsActive());
ActivateDesk(desks_controller->desks()[1].get());
EXPECT_TRUE(WindowState::Get(normal_window.get())->IsActive());
}
TEST_F(WindowFloatTest, FloatWindowUpdatedOnOverview) {
auto* desks_controller = DesksController::Get();
auto* desk_1 = desks_controller->desks()[0].get();
std::unique_ptr<aura::Window> window(CreateFloatedWindow());
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window.get()), desk_1);
NewDesk();
ASSERT_EQ(desks_controller->desks().size(), 2u);
EnterOverview();
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
RemoveDesk(desk_1, DeskCloseType::kCombineDesks);
ASSERT_EQ(desks_controller->desks().size(), 1u);
const std::vector<std::unique_ptr<OverviewItemBase>>& overview_items =
GetOverviewItemsForRoot(0);
ASSERT_EQ(overview_items.size(), 1u);
EXPECT_EQ(window.get(), overview_items[0]->GetWindow());
}
TEST_F(WindowFloatTest, PinnedWindow) {
std::unique_ptr<aura::Window> floated_window = CreateFloatedWindow();
std::unique_ptr<aura::Window> pinned_window = CreateAppWindow();
wm::ActivateWindow(pinned_window.get());
window_util::PinWindow(pinned_window.get(), false);
EXPECT_FALSE(floated_window->IsVisible());
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
AcceleratorAction::kUnpin, {});
EXPECT_TRUE(floated_window->IsVisible());
window_util::PinWindow(pinned_window.get(), true);
EXPECT_FALSE(floated_window->IsVisible());
pinned_window.reset();
EXPECT_TRUE(floated_window->IsVisible());
window_util::PinWindow(floated_window.get(), true);
EXPECT_TRUE(floated_window->IsVisible());
}
TEST_F(WindowFloatTest, AlwaysOnTopWindow) {
std::unique_ptr<aura::Window> always_on_top_window = CreateAppWindow();
always_on_top_window->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(WindowState::Get(always_on_top_window.get())->IsFloated());
}
TEST_F(WindowFloatTest, UnresizableFloatPerWindowState) {
std::unique_ptr<aura::Window> window = CreateAppWindow(gfx::Rect(600, 600));
window->SetProperty(aura::client::kResizeBehaviorKey,
aura::client::kResizeBehaviorNone);
auto* const window_state = WindowState::Get(window.get());
WMEvent restore_event(WM_EVENT_NORMAL);
window_state->OnWMEvent(&restore_event);
ASSERT_TRUE(window_state->IsNormalStateType());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(window_state->IsFloated());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_FALSE(window_state->IsFloated());
WMEvent maximize_event(WM_EVENT_MAXIMIZE);
window_state->OnWMEvent(&maximize_event);
ASSERT_TRUE(window_state->IsMaximized());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(window_state->IsFloated());
WMEvent fullscreen_event(WM_EVENT_FULLSCREEN);
window_state->OnWMEvent(&fullscreen_event);
ASSERT_TRUE(window_state->IsFullscreen());
window_state->Maximize();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(window_state->IsFloated());
}
TEST_F(WindowFloatTest, FloatAllDesksWindow) {
NewDesk();
NewDesk();
auto* desks_controller = DesksController::Get();
ASSERT_EQ(3u, desks_controller->desks().size());
auto first_floated_window = CreateFloatedWindow();
auto all_desks_window = CreateAppWindow();
views::Widget::GetWidgetForNativeWindow(all_desks_window.get())
->SetVisibleOnAllWorkspaces(true);
ASSERT_TRUE(
desks_util::IsWindowVisibleOnAllWorkspaces(all_desks_window.get()));
ASSERT_EQ(1u, desks_controller->visible_on_all_desks_windows().size());
ActivateDesk(desks_controller->desks()[1].get());
ASSERT_TRUE(wm::IsActiveWindow(all_desks_window.get()));
auto second_floated_window = CreateFloatedWindow();
ActivateDesk(desks_controller->desks()[2].get());
ASSERT_TRUE(wm::IsActiveWindow(all_desks_window.get()));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(WindowState::Get(all_desks_window.get())->IsFloated());
ActivateDesk(desks_controller->desks()[0].get());
EXPECT_FALSE(WindowState::Get(first_floated_window.get())->IsFloated());
EXPECT_TRUE(WindowState::Get(all_desks_window.get())->IsFloated());
ActivateDesk(desks_controller->desks()[1].get());
EXPECT_FALSE(WindowState::Get(second_floated_window.get())->IsFloated());
EXPECT_TRUE(WindowState::Get(all_desks_window.get())->IsFloated());
}
TEST_F(WindowFloatTest, SendFloatedWindowToAllDesks) {
NewDesk();
auto* desks_controller = DesksController::Get();
ASSERT_EQ(2u, desks_controller->desks().size());
auto first_floated_window = CreateFloatedWindow();
ActivateDesk(desks_controller->desks()[1].get());
auto all_floated_window = CreateFloatedWindow();
views::Widget::GetWidgetForNativeWindow(all_floated_window.get())
->SetVisibleOnAllWorkspaces(true);
ASSERT_EQ(1u, desks_controller->visible_on_all_desks_windows().size());
ASSERT_TRUE(
desks_util::IsWindowVisibleOnAllWorkspaces(all_floated_window.get()));
ASSERT_TRUE(desks_util::BelongsToActiveDesk(all_floated_window.get()));
ActivateDesk(desks_controller->desks()[0].get());
ASSERT_TRUE(desks_util::BelongsToActiveDesk(all_floated_window.get()));
EXPECT_TRUE(WindowState::Get(all_floated_window.get())->IsFloated());
ASSERT_TRUE(desks_util::BelongsToActiveDesk(first_floated_window.get()));
EXPECT_FALSE(WindowState::Get(first_floated_window.get())->IsFloated());
desks_controller->SendToDeskAtIndex(all_floated_window.get(), 1);
ASSERT_FALSE(desks_util::BelongsToActiveDesk(all_floated_window.get()));
ActivateDesk(desks_controller->desks()[1].get());
ASSERT_TRUE(desks_util::BelongsToActiveDesk(all_floated_window.get()));
EXPECT_TRUE(WindowState::Get(all_floated_window.get())->IsFloated());
}
class WindowFloatMetricsTest : public WindowFloatTest {
public:
WindowFloatMetricsTest()
: WindowFloatTest(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
WindowFloatMetricsTest(const WindowFloatMetricsTest&) = delete;
WindowFloatMetricsTest& operator=(const WindowFloatMetricsTest&) = delete;
~WindowFloatMetricsTest() override = default;
protected:
base::HistogramTester histogram_tester_;
};
TEST_F(WindowFloatMetricsTest, FloatWindowCountPerSession) {
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_FALSE(WindowState::Get(window.get())->IsFloated());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
NewDesk();
std::unique_ptr<aura::Window> window_2 = CreateFloatedWindow();
EXPECT_EQ(FloatTestApi::GetFloatedWindowCounter(), 3);
}
TEST_F(WindowFloatMetricsTest, FloatWindowMovedToAnotherDeskCountPerSession) {
std::unique_ptr<aura::Window> window_1 = CreateFloatedWindow();
NewDesk();
auto* desks_controller = DesksController::Get();
auto* desk_2 = desks_controller->desks()[1].get();
EnterOverview();
auto* overview_session = OverviewController::Get()->overview_session();
auto* overview_item =
overview_session->GetOverviewItemForWindow(window_1.get());
auto* grid =
overview_session->GetGridWithRootWindow(Shell::GetPrimaryRootWindow());
EXPECT_EQ(1u, grid->GetNumWindows());
const auto* desks_bar_view = grid->desks_bar_view();
gfx::Point desk_2_mini_view_center =
desks_bar_view->mini_views()[1]->GetBoundsInScreen().CenterPoint();
DragItemToPoint(overview_item, desk_2_mini_view_center, GetEventGenerator(),
false,
true);
auto* float_controller = Shell::Get()->float_controller();
ASSERT_EQ(float_controller->FindDeskOfFloatedWindow(window_1.get()), desk_2);
EXPECT_EQ(FloatTestApi::GetFloatedWindowMoveToAnotherDeskCounter(), 1);
ActivateDesk(desk_2);
RemoveDesk(desk_2, DeskCloseType::kCombineDesks);
EXPECT_EQ(FloatTestApi::GetFloatedWindowMoveToAnotherDeskCounter(), 2);
}
TEST_F(WindowFloatMetricsTest, FloatWindowDuration) {
constexpr char kHistogramName[] = "Ash.Float.FloatWindowDuration";
auto* desks_controller = DesksController::Get();
NewDesk();
ASSERT_EQ(2u, desks_controller->desks().size());
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
task_environment()->AdvanceClock(base::Minutes(3));
task_environment()->RunUntilIdle();
WindowState::Get(window.get())->Maximize();
histogram_tester_.ExpectBucketCount(kHistogramName, 3, 1);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
task_environment()->AdvanceClock(base::Hours(3));
task_environment()->RunUntilIdle();
WindowState::Get(window.get())->Maximize();
histogram_tester_.ExpectBucketCount(kHistogramName, 180, 1);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
task_environment()->AdvanceClock(base::Minutes(3));
task_environment()->RunUntilIdle();
ActivateDesk(desks_controller->desks()[1].get());
histogram_tester_.ExpectBucketCount(kHistogramName, 3, 2);
ActivateDesk(desks_controller->desks()[0].get());
task_environment()->AdvanceClock(base::Minutes(3));
task_environment()->RunUntilIdle();
desks_controller->SendToDeskAtIndex(window.get(), 1);
histogram_tester_.ExpectBucketCount(kHistogramName, 3, 3);
ActivateDesk(desks_controller->desks()[1].get());
window->Hide();
task_environment()->AdvanceClock(base::Minutes(3));
task_environment()->RunUntilIdle();
window->Show();
histogram_tester_.ExpectBucketCount(kHistogramName, 3, 3);
window.reset();
histogram_tester_.ExpectBucketCount(kHistogramName, 3, 4);
}
TEST_F(WindowFloatTest, BoundsForUnresizableWindow) {
std::unique_ptr<aura::Window> window = CreateAppWindow(gfx::Rect(600, 600));
window->SetProperty(aura::client::kResizeBehaviorKey,
aura::client::kResizeBehaviorNone);
const gfx::Size window_size = window->GetBoundsInScreen().size();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
const gfx::Rect work_area_bounds =
display::Screen::Get()->GetPrimaryDisplay().work_area();
const gfx::Rect window_bounds = window->GetBoundsInScreen();
EXPECT_EQ(window_size, window_bounds.size());
EXPECT_NEAR(window_bounds.bottom(), work_area_bounds.bottom(), 10);
EXPECT_NEAR(window_bounds.right(), work_area_bounds.right(), 10);
}
class TabletWindowFloatTest : public WindowFloatTest,
public display::DisplayObserver {
public:
TabletWindowFloatTest() = default;
TabletWindowFloatTest(const TabletWindowFloatTest&) = delete;
TabletWindowFloatTest& operator=(const TabletWindowFloatTest&) = delete;
~TabletWindowFloatTest() override = default;
void FlingWindow(aura::Window* window, bool left, bool up) {
const gfx::Point start =
GetHeaderView(window)->GetBoundsInScreen().CenterPoint();
const gfx::Vector2d offset(left ? -10 : 10, up ? -10 : 10);
GetEventGenerator()->GestureScrollSequence(
start, start + offset, base::Milliseconds(10), 1);
}
void MagnetizeWindow(aura::Window* window,
FloatController::MagnetismCorner corner) {
const gfx::Rect area =
WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
gfx::Point end;
switch (corner) {
case FloatController::MagnetismCorner::kTopLeft:
end = area.origin();
break;
case FloatController::MagnetismCorner::kBottomLeft:
end = area.bottom_left();
break;
case FloatController::MagnetismCorner::kTopRight:
end = area.top_right();
break;
case FloatController::MagnetismCorner::kBottomRight:
end = area.bottom_right();
break;
}
GetEventGenerator()->GestureScrollSequence(
GetHeaderView(window)->GetBoundsInScreen().CenterPoint(), end,
base::Milliseconds(100), 3);
}
void CheckMagnetized(aura::Window* window,
FloatController::MagnetismCorner corner) {
const gfx::Rect work_area =
WorkAreaInsets::ForWindow(window)->user_work_area_bounds();
const int padding = chromeos::wm::kFloatedWindowPaddingDp;
switch (corner) {
case FloatController::MagnetismCorner::kTopLeft:
EXPECT_EQ(gfx::Point(padding, padding), window->bounds().origin());
return;
case FloatController::MagnetismCorner::kBottomLeft:
EXPECT_EQ(gfx::Point(padding, work_area.bottom() - padding),
window->bounds().bottom_left());
return;
case FloatController::MagnetismCorner::kTopRight:
EXPECT_EQ(gfx::Point(work_area.right() - padding, padding),
window->bounds().top_right());
return;
case FloatController::MagnetismCorner::kBottomRight:
EXPECT_EQ(gfx::Point(work_area.right() - padding,
work_area.bottom() - padding),
window->bounds().bottom_right());
return;
}
}
void SetOnTabletStateChangedCallback(
base::RepeatingCallback<void(display::TabletState)> callback) {
on_tablet_state_changed_callback_ = callback;
display_observer_.emplace(this);
}
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
::switches::kUseFirstDisplayAsInternal);
WindowFloatTest::SetUp();
}
void TearDown() override {
display_observer_.reset();
WindowFloatTest::TearDown();
}
void OnDisplayTabletStateChanged(display::TabletState state) override {
on_tablet_state_changed_callback_.Run(state);
}
protected:
base::UserActionTester user_action_tester_;
private:
base::RepeatingCallback<void(display::TabletState)>
on_tablet_state_changed_callback_;
std::optional<display::ScopedDisplayObserver> display_observer_;
};
class NudgeCounter : public aura::WindowObserver {
public:
NudgeCounter() {
window_observation_.Observe(Shell::Get()->GetPrimaryRootWindow());
}
NudgeCounter(const NudgeCounter&) = delete;
NudgeCounter& operator=(const NudgeCounter&) = delete;
~NudgeCounter() override = default;
int nudge_count() const { return nudge_count_; }
void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override {
if (params.target->GetName() == "TuckEducationNudgeWidget" &&
params.new_parent) {
nudge_count_++;
}
}
void OnWindowDestroying(aura::Window* window) override {
window_observation_.Reset();
}
private:
int nudge_count_ = 0;
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
};
TEST_F(TabletWindowFloatTest, TabletClamshellTransition) {
auto window1 = CreateFloatedWindow();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_TRUE(WindowState::Get(window1.get())->IsFloated());
auto window2 = CreateFloatedWindow();
EXPECT_FALSE(WindowState::Get(window1.get())->IsFloated());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_TRUE(WindowState::Get(window2.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, ClamshellToTabletMagnetism) {
auto window = CreateFloatedWindow();
window->SetBounds(gfx::Rect(300, 300));
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopLeft);
}
TEST_F(TabletWindowFloatTest, TabletClamshellTransitionAnimation) {
auto normal_window = CreateAppWindow();
auto floated_window = CreateFloatedWindow();
auto wait_for_windows_finish_animating = [&]() {
ShellTestApi().WaitForWindowFinishAnimating(normal_window.get());
if (floated_window->layer()->GetAnimator()->is_animating())
ShellTestApi().WaitForWindowFinishAnimating(floated_window.get());
};
wm::ActivateWindow(normal_window.get());
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
ASSERT_TRUE(IsVisiblyAnimating(normal_window.get()));
ASSERT_TRUE(IsVisiblyAnimating(floated_window.get()));
wait_for_windows_finish_animating();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
ASSERT_TRUE(IsVisiblyAnimating(normal_window.get()));
ASSERT_TRUE(IsVisiblyAnimating(floated_window.get()));
wait_for_windows_finish_animating();
wm::ActivateWindow(floated_window.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
ASSERT_TRUE(IsVisiblyAnimating(normal_window.get()));
ASSERT_TRUE(IsVisiblyAnimating(floated_window.get()));
wait_for_windows_finish_animating();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_TRUE(IsVisiblyAnimating(normal_window.get()));
EXPECT_TRUE(IsVisiblyAnimating(floated_window.get()));
}
TEST_F(TabletWindowFloatTest, MinimumSizeChangeOnTablet) {
UpdateDisplay("800x600");
auto window =
CreateAppWindow(gfx::Rect(500, 500), chromeos::AppType::SYSTEM_APP,
kShellWindowId_DeskContainerA, new TestWidgetDelegateAsh);
auto* custom_frame =
static_cast<TestFrameViewAsh*>(FrameViewAsh::Get(window.get()));
wm::ActivateWindow(window.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
const gfx::Size tablet_minimum_size(300, 300);
SetOnTabletStateChangedCallback(
base::BindLambdaForTesting([&](display::TabletState state) {
switch (state) {
case display::TabletState::kInTabletMode:
custom_frame->SetMinimumSize(tablet_minimum_size);
return;
case display::TabletState::kInClamshellMode:
custom_frame->SetMinimumSize(gfx::Size());
return;
case display::TabletState::kEnteringTabletMode:
case display::TabletState::kExitingTabletMode:
break;
}
}));
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_EQ(tablet_minimum_size.width(), window->bounds().width());
custom_frame->SetMinimumSize(gfx::Size(350, 350));
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_EQ(350, window->bounds().width());
}
TEST_F(TabletWindowFloatTest, CanBrowsersFloat) {
UpdateDisplay("800x600");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
const int work_area_width =
display::Screen::Get()->GetPrimaryDisplay().work_area().width();
const int maximum_float_width =
(work_area_width - chromeos::wm::kSplitviewDividerShortSideLength) / 2 -
chromeos::wm::kFloatedWindowPaddingDp * 2;
aura::test::TestWindowDelegate window_delegate;
std::unique_ptr<aura::Window> window(CreateTestWindowInShell(
{.delegate = &window_delegate, .bounds = {500, 500}}));
window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
wm::ActivateWindow(window.get());
window_delegate.set_minimum_size(gfx::Size(maximum_float_width + 5, 100));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_FALSE(WindowState::Get(window.get())->IsFloated());
window_delegate.set_minimum_size(gfx::Size(maximum_float_width - 20, 100));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_EQ(maximum_float_width, window->bounds().width());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_FALSE(WindowState::Get(window.get())->IsFloated());
window_delegate.set_minimum_size(gfx::Size(250, 100));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_EQ(250 + chromeos::wm::kBrowserExtraPaddingDp,
window->bounds().width());
}
TEST_F(TabletWindowFloatTest, TabletPositioningLandscape) {
UpdateDisplay("800x600");
aura::test::TestWindowDelegate window_delegate;
std::unique_ptr<aura::Window> window(CreateTestWindowInShell(
{.delegate = &window_delegate, .bounds = {300, 300}}));
window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
wm::ActivateWindow(window.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_FALSE(WindowState::Get(window.get())->IsFloated());
window_delegate.set_minimum_size(gfx::Size(600, 600));
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, FloatWindowUnfloatsEnterTablet) {
UpdateDisplay("800x600");
aura::test::TestWindowDelegate window_delegate;
std::unique_ptr<aura::Window> window(CreateTestWindowInShell(
{.delegate = &window_delegate, .bounds = {850, 850}}));
window_delegate.set_minimum_size(gfx::Size(500, 500));
window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
wm::ActivateWindow(window.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_FALSE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, FloatWindowUnfloatsDisplayChange) {
UpdateDisplay("1800x1000");
aura::test::TestWindowDelegate window_delegate;
std::unique_ptr<aura::Window> window(CreateTestWindowInShell(
{.delegate = &window_delegate, .bounds = {300, 300}}));
window_delegate.set_minimum_size(gfx::Size(400, 400));
window->SetProperty(chromeos::kAppTypeKey, chromeos::AppType::BROWSER);
wm::ActivateWindow(window.get());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
UpdateDisplay("700x600");
EXPECT_FALSE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, ImmersiveMode) {
auto window = CreateAppWindow();
auto* immersive_controller = chromeos::ImmersiveFullscreenController::Get(
views::Widget::GetWidgetForNativeView(window.get()));
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_TRUE(immersive_controller->IsEnabled());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(immersive_controller->IsEnabled());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(immersive_controller->IsEnabled());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_FALSE(immersive_controller->IsEnabled());
}
TEST_F(TabletWindowFloatTest, UnminimizeFloatWindow) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kTopLeft);
auto* window_state = WindowState::Get(window.get());
window_state->Minimize();
window_state->Unminimize();
EXPECT_TRUE(window_state->IsFloated());
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopLeft);
}
TEST_F(TabletWindowFloatTest, Rotation) {
UpdateDisplay("1800x1000");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
const gfx::Rect no_rotation_bounds = window->bounds();
ScreenOrientationControllerTestApi orientation_test_api(
Shell::Get()->screen_orientation_controller());
orientation_test_api.SetDisplayRotation(
display::Display::ROTATE_180, display::Display::RotationSource::ACTIVE);
EXPECT_EQ(window->bounds(), no_rotation_bounds);
const int shelf_size = ShelfConfig::Get()->shelf_size();
orientation_test_api.SetDisplayRotation(
display::Display::ROTATE_90, display::Display::RotationSource::ACTIVE);
EXPECT_NEAR(no_rotation_bounds.width(), window->bounds().width(), shelf_size);
EXPECT_NEAR(no_rotation_bounds.height(), window->bounds().height(),
shelf_size);
orientation_test_api.SetDisplayRotation(
display::Display::ROTATE_270, display::Display::RotationSource::ACTIVE);
EXPECT_NEAR(no_rotation_bounds.width(), window->bounds().width(), shelf_size);
EXPECT_NEAR(no_rotation_bounds.height(), window->bounds().height(),
shelf_size);
}
TEST_F(TabletWindowFloatTest, Dragging) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
chromeos::HeaderView* header_view = GetHeaderView(window.get());
auto* event_generator = GetEventGenerator();
event_generator->PressTouch(header_view->GetBoundsInScreen().CenterPoint());
WindowLayerRecreatedCounter recreated_counter(window.get());
for (const gfx::Point& point :
{gfx::Point(10, 10), gfx::Point(100, 10), gfx::Point(400, 400)}) {
event_generator->MoveTouch(point);
EXPECT_EQ(point, header_view->GetBoundsInScreen().CenterPoint());
}
EXPECT_EQ(0, recreated_counter.recreated_count());
}
TEST_F(TabletWindowFloatTest, MaximizeWhileDragging) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
chromeos::HeaderView* header_view = GetHeaderView(window.get());
auto* event_generator = GetEventGenerator();
event_generator->PressTouch(header_view->GetBoundsInScreen().CenterPoint());
event_generator->MoveTouch(gfx::Point(100, 100));
PressAndReleaseKey(ui::VKEY_OEM_PLUS, ui::EF_ALT_DOWN);
EXPECT_TRUE(WindowState::Get(window.get())->IsMaximized());
event_generator->PressTouch(header_view->GetBoundsInScreen().CenterPoint());
event_generator->MoveTouch(gfx::Point(100, 100));
PressAndReleaseKey(ui::VKEY_OEM_MINUS, ui::EF_ALT_DOWN);
EXPECT_TRUE(WindowState::Get(window.get())->IsMinimized());
}
TEST_F(TabletWindowFloatTest, DraggingMagnetism) {
UpdateDisplay("1600x1000");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
const int padding = chromeos::wm::kFloatedWindowPaddingDp;
const int shelf_size = ShelfConfig::Get()->shelf_size();
EXPECT_EQ(gfx::Point(1600 - padding, 1000 - padding - shelf_size),
window->bounds().bottom_right());
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kBottomRight);
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kTopRight);
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopRight);
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kTopLeft);
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopLeft);
UpdateDisplay("1000x1600");
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kBottomLeft);
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomLeft);
}
TEST_F(TabletWindowFloatTest, UntuckWindowOnExitTabletMode) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
EXPECT_TRUE(screen_util::GetDisplayBoundsInParent(window.get())
.Contains(window->bounds()));
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, UntuckWindowOnActivation) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
wm::ActivateWindow(window.get());
EXPECT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
EXPECT_TRUE(screen_util::GetDisplayBoundsInParent(window.get())
.Contains(window->bounds()));
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
}
TEST_F(TabletWindowFloatTest, UntuckWindowOnDeskChange) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
auto* desks_controller = DesksController::Get();
NewDesk();
ASSERT_EQ(2u, desks_controller->desks().size());
ActivateDesk(desks_controller->desks()[1].get());
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
const gfx::Rect pre_tucked_bounds = window->bounds();
FlingWindow(window.get(), false, false);
ASSERT_TRUE(desks_util::BelongsToActiveDesk(window.get()));
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_TRUE(Shell::Get()->float_controller()->IsFloatedWindowTuckedForTablet(
window.get()));
ActivateDesk(desks_controller->desks()[0].get());
EXPECT_TRUE(WindowState::Get(window.get())->IsFloated());
EXPECT_FALSE(window->IsVisible());
EXPECT_EQ(pre_tucked_bounds, window->bounds());
EXPECT_FALSE(Shell::Get()->float_controller()->IsFloatedWindowTuckedForTablet(
window.get()));
}
TEST_F(TabletWindowFloatTest, TuckedWindowVisibility) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
gfx::ScopedAnimationDurationScaleMode test_duration_mode(
gfx::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
EXPECT_TRUE(window->IsVisible());
ShellTestApi().WaitForWindowFinishAnimating(window.get());
EXPECT_FALSE(window->IsVisible());
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
ToggleOverview();
WaitForOverviewEnterAnimation();
EXPECT_TRUE(GetOverviewItemForWindow(window.get()));
wm::ActivateWindow(window.get());
ShellTestApi().WaitForWindowFinishAnimating(window.get());
EXPECT_TRUE(window->IsVisible());
}
TEST_F(TabletWindowFloatTest, UntuckedWindowVisibility) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
gfx::ScopedAnimationDurationScaleMode test_duration(
gfx::ScopedAnimationDurationScaleMode::SLOW_DURATION);
FlingWindow(window.get(), false, false);
auto* float_controller = Shell::Get()->float_controller();
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
ASSERT_TRUE(tuck_handle_widget);
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_TRUE(window->IsVisible());
EXPECT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
}
TEST_F(TabletWindowFloatTest, WindowActivationAfterTuckingUntucking) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> float_window = CreateFloatedWindow();
ASSERT_EQ(float_window.get(), window_util::GetActiveWindow());
FlingWindow(float_window.get(), false, false);
auto* float_controller = Shell::Get()->float_controller();
ASSERT_TRUE(
float_controller->IsFloatedWindowTuckedForTablet(float_window.get()));
EXPECT_EQ(Shell::Get()->app_list_controller()->GetWindow(),
window_util::GetActiveWindow());
std::unique_ptr<aura::Window> window2 = CreateAppWindow();
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(float_window.get());
ASSERT_TRUE(tuck_handle_widget);
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(
float_controller->IsFloatedWindowTuckedForTablet(float_window.get()));
ASSERT_EQ(float_window.get(), window_util::GetActiveWindow());
FlingWindow(float_window.get(), false, false);
ASSERT_TRUE(
float_controller->IsFloatedWindowTuckedForTablet(float_window.get()));
EXPECT_EQ(window2.get(), window_util::GetActiveWindow());
tuck_handle_widget =
float_controller->GetTuckHandleWidget(float_window.get());
ASSERT_TRUE(tuck_handle_widget);
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(
float_controller->IsFloatedWindowTuckedForTablet(float_window.get()));
WindowState::Get(window2.get())->Minimize();
ASSERT_EQ(float_window.get(), window_util::GetActiveWindow());
FlingWindow(float_window.get(), false, false);
ASSERT_TRUE(
float_controller->IsFloatedWindowTuckedForTablet(float_window.get()));
EXPECT_EQ(Shell::Get()->app_list_controller()->GetWindow(),
window_util::GetActiveWindow());
}
TEST_F(TabletWindowFloatTest, TuckWindowLeft) {
UpdateDisplay("1600x1000");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
MagnetizeWindow(window.get(), FloatController::MagnetismCorner::kTopLeft);
auto* float_controller = Shell::Get()->float_controller();
EXPECT_EQ(0, user_action_tester_.GetActionCount(kTuckUserAction));
EXPECT_EQ(0, user_action_tester_.GetActionCount(kUntuckUserAction));
FlingWindow(window.get(), true, true);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const int padding = chromeos::wm::kFloatedWindowPaddingDp;
EXPECT_EQ(gfx::Point(0, padding), window->bounds().top_right());
EXPECT_EQ(1, user_action_tester_.GetActionCount(kTuckUserAction));
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
ASSERT_TRUE(tuck_handle_widget);
EXPECT_EQ(window->bounds().right_center(),
tuck_handle_widget->GetWindowBoundsInScreen().left_center());
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopLeft);
EXPECT_EQ(1, user_action_tester_.GetActionCount(kUntuckUserAction));
FlingWindow(window.get(), true, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const gfx::Rect work_area = WorkAreaInsets::ForWindow(window->GetRootWindow())
->user_work_area_bounds();
EXPECT_EQ(gfx::Point(0, work_area.bottom() - padding),
window->bounds().bottom_right());
EXPECT_EQ(2, user_action_tester_.GetActionCount(kTuckUserAction));
tuck_handle_widget = float_controller->GetTuckHandleWidget(window.get());
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomLeft);
EXPECT_EQ(2, user_action_tester_.GetActionCount(kUntuckUserAction));
}
TEST_F(TabletWindowFloatTest, TuckWindowRight) {
UpdateDisplay("1600x1000");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
const gfx::Rect work_area = WorkAreaInsets::ForWindow(window->GetRootWindow())
->user_work_area_bounds();
EXPECT_EQ(0, user_action_tester_.GetActionCount(kTuckUserAction));
EXPECT_EQ(0, user_action_tester_.GetActionCount(kUntuckUserAction));
FlingWindow(window.get(), false, true);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const int padding = chromeos::wm::kFloatedWindowPaddingDp;
EXPECT_EQ(gfx::Point(work_area.right(), padding), window->bounds().origin());
EXPECT_EQ(1, user_action_tester_.GetActionCount(kTuckUserAction));
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
ASSERT_TRUE(tuck_handle_widget);
EXPECT_EQ(window->bounds().left_center(),
tuck_handle_widget->GetWindowBoundsInScreen().right_center());
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopRight);
EXPECT_EQ(1, user_action_tester_.GetActionCount(kUntuckUserAction));
FlingWindow(window.get(), false, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
EXPECT_EQ(gfx::Point(work_area.right(), work_area.bottom() - padding),
window->bounds().bottom_left());
EXPECT_EQ(2, user_action_tester_.GetActionCount(kTuckUserAction));
tuck_handle_widget = float_controller->GetTuckHandleWidget(window.get());
GestureTapOn(tuck_handle_widget->GetContentsView());
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
EXPECT_EQ(2, user_action_tester_.GetActionCount(kUntuckUserAction));
}
TEST_F(TabletWindowFloatTest, TuckToMagnetismCorner) {
UpdateDisplay("1600x1000");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), true, true);
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopLeft);
FlingWindow(window.get(), true, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const gfx::Rect work_area = WorkAreaInsets::ForWindow(window->GetRootWindow())
->user_work_area_bounds();
const int padding = chromeos::wm::kFloatedWindowPaddingDp;
EXPECT_EQ(gfx::Point(0, work_area.bottom() - padding),
window->bounds().bottom_right());
}
TEST_F(TabletWindowFloatTest, TapOnEdgeDoesNotUntuck) {
UpdateDisplay("800x600");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const gfx::Point point(799, window->GetBoundsInScreen().y());
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
ASSERT_FALSE(tuck_handle_widget->GetWindowBoundsInScreen().Contains(point));
GetEventGenerator()->GestureTapAt(point);
EXPECT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
}
TEST_F(TabletWindowFloatTest, TuckHandleTapTarget) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
const gfx::Rect tuck_handle_bounds =
float_controller->GetTuckHandleWidget(window.get())
->GetWindowBoundsInScreen();
GetEventGenerator()->GestureTapAt(tuck_handle_bounds.origin() -
gfx::Vector2d(5, 5));
EXPECT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
}
TEST_F(TabletWindowFloatTest, TuckHandleOffscreenInOverview) {
const gfx::Rect display_bounds =
display::Screen::Get()->GetPrimaryDisplay().bounds();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
EXPECT_TRUE(
display_bounds.Contains(tuck_handle_widget->GetWindowBoundsInScreen()));
EnterOverview();
EXPECT_FALSE(
display_bounds.Contains(tuck_handle_widget->GetWindowBoundsInScreen()));
ExitOverview();
EXPECT_TRUE(
display_bounds.Contains(tuck_handle_widget->GetWindowBoundsInScreen()));
}
TEST_F(TabletWindowFloatTest, UntuckWindowGestures) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* float_controller = Shell::Get()->float_controller();
FlingWindow(window.get(), false, false);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
views::Widget* tuck_handle_widget =
float_controller->GetTuckHandleWidget(window.get());
const gfx::Point start(
tuck_handle_widget->GetWindowBoundsInScreen().CenterPoint());
GetEventGenerator()->GestureScrollSequence(
start, start + gfx::Vector2d(0, -8), base::Milliseconds(100),
3);
ASSERT_TRUE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
EXPECT_EQ(0, user_action_tester_.GetActionCount(kUntuckUserAction));
GetEventGenerator()->GestureScrollSequence(
start, start + gfx::Vector2d(-8, 0), base::Milliseconds(100),
3);
EXPECT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
EXPECT_EQ(1, user_action_tester_.GetActionCount(kUntuckUserAction));
}
TEST_F(TabletWindowFloatTest, FlingVertical) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
const gfx::Point start =
GetHeaderView(window.get())->GetBoundsInScreen().CenterPoint();
GetEventGenerator()->GestureScrollSequence(
start, start + gfx::Vector2d(0, 10), base::Milliseconds(10), 1);
auto* float_controller = Shell::Get()->float_controller();
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kBottomRight);
EXPECT_EQ(0, user_action_tester_.GetActionCount(kTuckUserAction));
GetEventGenerator()->GestureScrollSequence(
start, start + gfx::Vector2d(0, -10), base::Milliseconds(10),
1);
ASSERT_FALSE(float_controller->IsFloatedWindowTuckedForTablet(window.get()));
CheckMagnetized(window.get(), FloatController::MagnetismCorner::kTopRight);
EXPECT_EQ(0, user_action_tester_.GetActionCount(kTuckUserAction));
}
TEST_F(TabletWindowFloatTest, BasicTuckNudge) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateAppWindow();
views::NamedWidgetShownWaiter widget_waiter(
views::test::AnyWidgetTestPasskey{}, "TuckEducationNudgeWidget");
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
widget_waiter.WaitIfNeededAndGet();
EXPECT_TRUE(window->children().empty());
}
TEST_F(TabletWindowFloatTest, EducationPreferences) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
base::SimpleTestClock test_clock;
TabletModeTuckEducation::SetOverrideClockForTesting(&test_clock);
test_clock.Advance(base::Hours(25));
NudgeCounter nudge_counter;
for (int i = 0; i < 3; i++) {
std::unique_ptr<aura::Window> window = CreateAppWindow();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
window.reset();
test_clock.Advance(base::Hours(25));
}
EXPECT_EQ(3, nudge_counter.nudge_count());
std::unique_ptr<aura::Window> window = CreateAppWindow();
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
window.reset();
EXPECT_EQ(3, nudge_counter.nudge_count());
TabletModeTuckEducation::SetOverrideClockForTesting(nullptr);
}
using TabletWindowFloatSplitviewTest = TabletWindowFloatTest;
TEST_F(TabletWindowFloatSplitviewTest, BothSnappedToFloat) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
auto left_window = CreateAppWindow();
const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
WindowState::Get(left_window.get())->OnWMEvent(&snap_left);
auto right_window = CreateAppWindow();
const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
WindowState::Get(right_window.get())->OnWMEvent(&snap_right);
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
ASSERT_EQ(split_view_controller->state(),
SplitViewController::State::kBothSnapped);
wm::ActivateWindow(left_window.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_TRUE(WindowState::Get(left_window.get())->IsFloated());
EXPECT_TRUE(WindowState::Get(right_window.get())->IsMaximized());
EXPECT_FALSE(split_view_controller->InSplitViewMode());
}
TEST_F(TabletWindowFloatSplitviewTest, FloatToSnapped) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
WindowState::Get(window.get())->OnWMEvent(&snap_left);
ASSERT_TRUE(OverviewController::Get()->InOverviewSession());
ASSERT_TRUE(split_view_controller->InSplitViewMode());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window.get())->IsFloated());
ASSERT_FALSE(OverviewController::Get()->InOverviewSession());
ASSERT_FALSE(split_view_controller->InSplitViewMode());
auto other_window = CreateAppWindow();
wm::ActivateWindow(window.get());
WindowState::Get(window.get())->OnWMEvent(&snap_left);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
EXPECT_EQ(split_view_controller->state(),
SplitViewController::State::kBothSnapped);
EXPECT_EQ(split_view_controller->primary_window(), window.get());
EXPECT_EQ(split_view_controller->secondary_window(), other_window.get());
ToggleOverview();
wm::ActivateWindow(window.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
EXPECT_FALSE(OverviewController::Get()->InOverviewSession());
EXPECT_FALSE(split_view_controller->InSplitViewMode());
const WindowSnapWMEvent snap_primary_two_third(WM_EVENT_SNAP_PRIMARY,
chromeos::kTwoThirdSnapRatio);
WindowState::Get(other_window.get())->OnWMEvent(&snap_primary_two_third);
ASSERT_TRUE(WindowState::Get(other_window.get())->IsSnapped());
wm::ActivateWindow(window.get());
EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
EXPECT_EQ(split_view_controller->state(),
SplitViewController::State::kBothSnapped);
EXPECT_EQ(split_view_controller->primary_window(), other_window.get());
EXPECT_EQ(split_view_controller->secondary_window(), window.get());
EXPECT_NEAR(chromeos::kOneThirdSnapRatio,
WindowState::Get(window.get())->snap_ratio().value(),
0.1);
EXPECT_NEAR(chromeos::kTwoThirdSnapRatio,
WindowState::Get(other_window.get())->snap_ratio().value(),
0.1);
}
TEST_F(TabletWindowFloatSplitviewTest, ResetFloatToMaximize) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window_1 = CreateAppWindow();
std::unique_ptr<aura::Window> window_2 = CreateAppWindow();
auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
const WindowSnapWMEvent snap_left(WM_EVENT_SNAP_PRIMARY);
WindowState::Get(window_1.get())->OnWMEvent(&snap_left);
const WindowSnapWMEvent snap_right(WM_EVENT_SNAP_SECONDARY);
WindowState::Get(window_2.get())->OnWMEvent(&snap_right);
EXPECT_EQ(split_view_controller->state(),
SplitViewController::State::kBothSnapped);
EXPECT_EQ(split_view_controller->primary_window(), window_1.get());
EXPECT_EQ(split_view_controller->secondary_window(), window_2.get());
wm::ActivateWindow(window_1.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window_1.get())->IsFloated());
ASSERT_TRUE(WindowState::Get(window_2.get())->IsMaximized());
wm::ActivateWindow(window_2.get());
PressAndReleaseKey(ui::VKEY_F, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ASSERT_TRUE(WindowState::Get(window_2.get())->IsFloated());
ASSERT_TRUE(WindowState::Get(window_1.get())->IsMaximized());
}
TEST_F(TabletWindowFloatTest, FloatStateToAlwaysOnTop) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
std::unique_ptr<aura::Window> window = CreateFloatedWindow();
window->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
EXPECT_FALSE(WindowState::Get(window.get())->IsFloated());
}
}