0

full_restore: Restore windows that are out-of-bounds properly.

Currently, if a user has a window that is slightly out-of-bounds, upon
restoration it the restored window will be moved into the work area
such that it's fully contained. The ideal behavior is that out-of-bounds
windows are restored to their previous bounds. Additionally, if the
window would be placed fully out-of-bounds, we should ensure that a
portion of the window is within the work area.

This CL implements the ideal behavior by restoring out-of-bounds
windows with the FullRestoreController, ensuring these windows are at
least partially visible.

Test: added
Bug: 1208927
Change-Id: If43415c6abe39017183d0e11e81b60ca4e4d0ba1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2911344
Commit-Queue: Jeremy Chinsen <chinsenj@chromium.org>
Reviewed-by: Sammie Quon <sammiequon@chromium.org>
Cr-Commit-Position: refs/heads/master@{#886548}
This commit is contained in:
chinsenj
2021-05-26 01:29:43 +00:00
committed by Chromium LUCI CQ
parent 23afd147ab
commit 10c90140f1
2 changed files with 97 additions and 4 deletions

@ -16,6 +16,7 @@
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/auto_reset.h"
@ -121,6 +122,40 @@ aura::Window* GetSiblingToStackBelow(aura::Window* window) {
return nullptr;
}
// If `window`'s saved window info makes the `window` out-of-bounds for the
// display, manually restore its bounds. Also ensures that at least 30% of the
// window is visible to handle the case where the display a window is restored
// to is drastically smaller than the pre-restore display.
void MaybeRestoreOutOfBoundsWindows(aura::Window* window) {
std::unique_ptr<full_restore::WindowInfo> window_info = GetWindowInfo(window);
if (!window_info)
return;
gfx::Rect current_bounds =
window_info->current_bounds.value_or(gfx::Rect(0, 0));
if (current_bounds.IsEmpty())
return;
const auto& closest_display =
display::Screen::GetScreen()->GetDisplayNearestWindow(window);
gfx::Rect display_area = closest_display.work_area();
if (display_area.Contains(current_bounds))
return;
AdjustBoundsToEnsureMinimumWindowVisibility(display_area, &current_bounds);
auto* window_state = WindowState::Get(window);
if (window_state->HasRestoreBounds()) {
// When a `window` is in maximized, minimized, or snapped its restore bounds
// are saved in `WindowInfo.current_bounds` and its
// maximized/minimized/snapped bounds are determined by the system, so apply
// this adjustment to `window`'s restore bounds instead.
window_state->SetRestoreBoundsInScreen(current_bounds);
} else {
window->SetBoundsInScreen(current_bounds, closest_display);
}
}
} // namespace
FullRestoreController::FullRestoreController() {
@ -213,6 +248,14 @@ void FullRestoreController::OnWidgetInitialized(views::Widget* widget) {
return;
UpdateAndObserveWindow(window);
// If the restored bounds are out of the screen, move the window to the bounds
// manually as most widget types force windows to be within the work area on
// creation.
// TODO(chinsenj|sammiequon): The Files app uses async Mojo calls to activate
// and set its bounds, making this approach not work. In the future, we'll
// need to address the Files app.
MaybeRestoreOutOfBoundsWindows(window);
}
void FullRestoreController::OnARCTaskReadyForUnparentedWindow(

@ -933,12 +933,13 @@ TEST_F(FullRestoreControllerTest, HotseatIsHiddenOnRestoration) {
// Add two entries, where the window highest on the z-order is minimized.
// Restore both entries. The hotseat should now be hidden.
const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
AddEntryToFakeFile(/*restore_window_id=*/1, gfx::Rect(200, 200),
chromeos::WindowStateType::kMinimized,
/*activation_index=*/1);
/*activation_index=*/1, /*display_id=*/primary_id);
AddEntryToFakeFile(/*restore_window_id=*/2, gfx::Rect(200, 200),
chromeos::WindowStateType::kNormal,
/*activation_index=*/2);
/*activation_index=*/2, /*display_id=*/primary_id);
views::Widget* restored_widget_1 =
CreateTestFullRestoredWidgetFromRestoreId(/*restore_window_id=*/1);
views::Widget* restored_widget_2 =
@ -963,12 +964,13 @@ TEST_F(FullRestoreControllerTest,
// Create multiple minimized entries and restore them. The app list should
// still be active.
const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
AddEntryToFakeFile(/*restore_window_id=*/1, gfx::Rect(200, 200),
chromeos::WindowStateType::kMinimized,
/*activation_index=*/1);
/*activation_index=*/1, /*display_id=*/primary_id);
AddEntryToFakeFile(/*restore_window_id=*/2, gfx::Rect(200, 200),
chromeos::WindowStateType::kMinimized,
/*activation_index=*/2);
/*activation_index=*/2, /*display_id=*/primary_id);
views::Widget* restored_widget_1 =
CreateTestFullRestoredWidgetFromRestoreId(/*restore_window_id=*/1);
views::Widget* restored_widget_2 =
@ -1097,4 +1099,52 @@ TEST_F(FullRestoreControllerTest, ArcAppWindowCreatedWithoutTaskMultiDisplay) {
restored_window2->parent());
}
// Tests that windows that are out-of-bounds of the display they're being
// restored to are properly restored.
TEST_F(FullRestoreControllerTest, OutOfBoundsWindows) {
UpdateDisplay("800x800");
const gfx::Rect kScreenBounds(0, 0, 800, 800);
const gfx::Rect kPartialBounds(-100, 100, 200, 200);
const gfx::Rect kFullBounds(801, 801, 400, 200);
// Add an entry that is partially out-of-bounds, one that is completely
// out-of-bounds, and one that is completely out-of-bounds and snapped.
const int64_t primary_id = WindowTreeHostManager::GetPrimaryDisplayId();
AddEntryToFakeFile(/*restore_id=*/1, kPartialBounds,
chromeos::WindowStateType::kNormal, /*activation_index=*/1,
/*display_id=*/primary_id);
AddEntryToFakeFile(/*restore_id=*/2, kFullBounds,
chromeos::WindowStateType::kNormal, /*activation_index=*/2,
/*display_id=*/primary_id);
AddEntryToFakeFile(/*restore_id=*/3, kFullBounds,
chromeos::WindowStateType::kLeftSnapped,
/*activation_index=*/3, /*display_id=*/primary_id);
// Restore the first window. The window should have the exact same bounds.
const gfx::Rect& window_bounds_1 =
CreateTestFullRestoredWidgetFromRestoreId(/*restore_id=*/1)
->GetNativeWindow()
->GetBoundsInScreen();
EXPECT_EQ(kPartialBounds, window_bounds_1);
// Restore the second window. The window should be moved such that part of it
// is within the display.
gfx::Rect window_bounds_2(
CreateTestFullRestoredWidgetFromRestoreId(/*restore_id=*/2)
->GetNativeWindow()
->GetBoundsInScreen());
EXPECT_TRUE(window_bounds_2.Intersects(kScreenBounds));
EXPECT_LT(0, IntersectRects(kScreenBounds, window_bounds_2).size().GetArea());
// Restore the third window. The window's restore bounds should be moved such
// that part of it is within the display.
const gfx::Rect& window_bounds_3 =
WindowState::Get(
CreateTestFullRestoredWidgetFromRestoreId(/*restore_id=*/3)
->GetNativeWindow())
->GetRestoreBoundsInScreen();
EXPECT_TRUE(window_bounds_3.Intersects(kScreenBounds));
EXPECT_LT(0, IntersectRects(kScreenBounds, window_bounds_3).size().GetArea());
}
} // namespace ash