window_restore_controller.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. // Copyright 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ash/wm/window_restore/window_restore_controller.h"
  5. #include <cstdint>
  6. #include "ash/app_list/app_list_controller_impl.h"
  7. #include "ash/constants/app_types.h"
  8. #include "ash/public/cpp/app_types_util.h"
  9. #include "ash/public/cpp/shell_window_ids.h"
  10. #include "ash/public/cpp/window_properties.h"
  11. #include "ash/session/session_controller_impl.h"
  12. #include "ash/shell.h"
  13. #include "ash/wm/container_finder.h"
  14. #include "ash/wm/desks/desks_util.h"
  15. #include "ash/wm/mru_window_tracker.h"
  16. #include "ash/wm/tablet_mode/tablet_mode_controller.h"
  17. #include "ash/wm/window_positioning_utils.h"
  18. #include "ash/wm/window_restore/window_restore_util.h"
  19. #include "ash/wm/window_state.h"
  20. #include "ash/wm/wm_event.h"
  21. #include "base/auto_reset.h"
  22. #include "base/bind.h"
  23. #include "base/check_op.h"
  24. #include "base/containers/adapters.h"
  25. #include "base/containers/contains.h"
  26. #include "base/threading/thread_task_runner_handle.h"
  27. #include "components/account_id/account_id.h"
  28. #include "components/app_restore/app_restore_info.h"
  29. #include "components/app_restore/full_restore_utils.h"
  30. #include "components/app_restore/window_properties.h"
  31. #include "ui/aura/client/aura_constants.h"
  32. #include "ui/aura/client/window_parenting_client.h"
  33. #include "ui/aura/window.h"
  34. #include "ui/display/display.h"
  35. #include "ui/display/screen.h"
  36. #include "ui/views/widget/widget.h"
  37. namespace ash {
  38. namespace {
  39. WindowRestoreController* g_instance = nullptr;
  40. // Callback for testing which is run when `SaveWindowImpl()` triggers a write to
  41. // file.
  42. WindowRestoreController::SaveWindowCallback g_save_window_callback_for_testing;
  43. // The list of possible app window parents.
  44. constexpr ShellWindowId kAppParentContainers[11] = {
  45. kShellWindowId_DefaultContainerDeprecated,
  46. kShellWindowId_DeskContainerB,
  47. kShellWindowId_DeskContainerC,
  48. kShellWindowId_DeskContainerD,
  49. kShellWindowId_DeskContainerE,
  50. kShellWindowId_DeskContainerF,
  51. kShellWindowId_DeskContainerG,
  52. kShellWindowId_DeskContainerH,
  53. kShellWindowId_AlwaysOnTopContainer,
  54. kShellWindowId_FloatContainer,
  55. kShellWindowId_UnparentedContainer,
  56. };
  57. // The types of apps currently supported by window restore.
  58. // TODO(crbug.com/1164472): Checking app type is temporary solution until we
  59. // can get windows which are allowed to window restore from the
  60. // FullRestoreService.
  61. constexpr AppType kSupportedAppTypes[4] = {
  62. AppType::BROWSER, AppType::CHROME_APP, AppType::ARC_APP, AppType::LACROS};
  63. // Delay for certain app types before activation is allowed. This is because
  64. // some apps' client request activation after creation, which can break user
  65. // flow.
  66. constexpr base::TimeDelta kAllowActivationDelay = base::Seconds(2);
  67. app_restore::WindowInfo* GetWindowInfo(aura::Window* window) {
  68. return window->GetProperty(app_restore::kWindowInfoKey);
  69. }
  70. // If `window`'s saved window info makes the `window` out-of-bounds for the
  71. // display, manually restore its bounds. Also ensures that at least 30% of the
  72. // window is visible to handle the case where the display a window is restored
  73. // to is drastically smaller than the pre-restore display.
  74. void MaybeRestoreOutOfBoundsWindows(aura::Window* window) {
  75. app_restore::WindowInfo* window_info = GetWindowInfo(window);
  76. if (!window_info)
  77. return;
  78. gfx::Rect current_bounds =
  79. window_info->current_bounds.value_or(gfx::Rect(0, 0));
  80. if (current_bounds.IsEmpty())
  81. return;
  82. const auto& closest_display =
  83. display::Screen::GetScreen()->GetDisplayNearestWindow(window);
  84. gfx::Rect display_area = closest_display.work_area();
  85. if (display_area.Contains(current_bounds))
  86. return;
  87. AdjustBoundsToEnsureMinimumWindowVisibility(display_area, &current_bounds);
  88. auto* window_state = WindowState::Get(window);
  89. if (window_state->HasRestoreBounds()) {
  90. // When a `window` is in maximized, minimized, or snapped its restore bounds
  91. // are saved in `WindowInfo.current_bounds` and its
  92. // maximized/minimized/snapped bounds are determined by the system, so apply
  93. // this adjustment to `window`'s restore bounds instead.
  94. window_state->SetRestoreBoundsInScreen(current_bounds);
  95. } else {
  96. window->SetBoundsInScreen(current_bounds, closest_display);
  97. }
  98. }
  99. // Self deleting class which watches a unparented window and deletes itself once
  100. // the window has a parent.
  101. class ParentChangeObserver : public aura::WindowObserver {
  102. public:
  103. ParentChangeObserver(aura::Window* window) {
  104. DCHECK(!window->parent());
  105. window_observation_.Observe(window);
  106. }
  107. ParentChangeObserver(const ParentChangeObserver&) = delete;
  108. ParentChangeObserver& operator=(const ParentChangeObserver&) = delete;
  109. ~ParentChangeObserver() override = default;
  110. // aura::WindowObserver:
  111. void OnWindowParentChanged(aura::Window* window,
  112. aura::Window* parent) override {
  113. if (!parent)
  114. return;
  115. WindowRestoreController::Get()->SaveAllWindows();
  116. delete this;
  117. }
  118. void OnWindowDestroying(aura::Window* window) override { delete this; }
  119. base::ScopedObservation<aura::Window, aura::WindowObserver>
  120. window_observation_{this};
  121. };
  122. } // namespace
  123. WindowRestoreController::WindowRestoreController() {
  124. DCHECK_EQ(nullptr, g_instance);
  125. g_instance = this;
  126. tablet_mode_observation_.Observe(Shell::Get()->tablet_mode_controller());
  127. app_restore_info_observation_.Observe(
  128. app_restore::AppRestoreInfo::GetInstance());
  129. }
  130. WindowRestoreController::~WindowRestoreController() {
  131. DCHECK_EQ(this, g_instance);
  132. g_instance = nullptr;
  133. }
  134. // static
  135. WindowRestoreController* WindowRestoreController::Get() {
  136. return g_instance;
  137. }
  138. // static
  139. bool WindowRestoreController::CanActivateRestoredWindow(
  140. const aura::Window* window) {
  141. if (!window->GetProperty(app_restore::kLaunchedFromAppRestoreKey))
  142. return true;
  143. // Only windows on the active desk should be activatable.
  144. if (!desks_util::BelongsToActiveDesk(const_cast<aura::Window*>(window)))
  145. return false;
  146. // Ghost windows can be activated.
  147. const AppType app_type =
  148. static_cast<AppType>(window->GetProperty(aura::client::kAppType));
  149. const bool is_real_arc_window =
  150. window->GetProperty(app_restore::kRealArcTaskWindow);
  151. if (app_type == AppType::ARC_APP && !is_real_arc_window)
  152. return true;
  153. auto* desk_container = window->parent();
  154. if (!desk_container || !desks_util::IsDeskContainer(desk_container))
  155. return true;
  156. // Only the topmost unminimize restored window can be activated.
  157. auto siblings = desk_container->children();
  158. for (auto* const sibling : base::Reversed(siblings)) {
  159. if (WindowState::Get(sibling)->IsMinimized())
  160. continue;
  161. return window == sibling;
  162. }
  163. return false;
  164. }
  165. // static
  166. bool WindowRestoreController::CanActivateAppList(const aura::Window* window) {
  167. auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller();
  168. if (!tablet_mode_controller || !tablet_mode_controller->InTabletMode())
  169. return true;
  170. auto* app_list_controller = Shell::Get()->app_list_controller();
  171. if (!app_list_controller || app_list_controller->GetWindow() != window)
  172. return true;
  173. for (auto* root_window : Shell::GetAllRootWindows()) {
  174. auto active_desk_children =
  175. desks_util::GetActiveDeskContainerForRoot(root_window)->children();
  176. // Find the topmost unminimized window.
  177. auto topmost_visible_iter = active_desk_children.rbegin();
  178. while (topmost_visible_iter != active_desk_children.rend() &&
  179. WindowState::Get(*topmost_visible_iter)->IsMinimized()) {
  180. topmost_visible_iter = std::next(topmost_visible_iter);
  181. }
  182. if (topmost_visible_iter != active_desk_children.rend() &&
  183. (*topmost_visible_iter)
  184. ->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
  185. return false;
  186. }
  187. }
  188. return true;
  189. }
  190. // static
  191. std::vector<aura::Window*>::const_iterator
  192. WindowRestoreController::GetWindowToInsertBefore(
  193. aura::Window* window,
  194. const std::vector<aura::Window*>& windows) {
  195. int32_t* activation_index =
  196. window->GetProperty(app_restore::kActivationIndexKey);
  197. DCHECK(activation_index);
  198. auto it = windows.begin();
  199. while (it != windows.end()) {
  200. int32_t* next_activation_index =
  201. (*it)->GetProperty(app_restore::kActivationIndexKey);
  202. if (!next_activation_index || *activation_index > *next_activation_index) {
  203. // Activation index is saved to match MRU order so lower means more
  204. // recent/higher in stacking order. Also restored windows should be
  205. // stacked below non-restored windows.
  206. return it;
  207. }
  208. it = std::next(it);
  209. }
  210. return it;
  211. }
  212. void WindowRestoreController::SaveWindow(WindowState* window_state) {
  213. SaveWindowImpl(window_state, /*activation_index=*/absl::nullopt);
  214. }
  215. void WindowRestoreController::SaveAllWindows() {
  216. auto mru_windows =
  217. Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
  218. for (int i = 0; i < static_cast<int>(mru_windows.size()); ++i) {
  219. // Provide the activation index here since we need to loop through `windows`
  220. // anyhow. Otherwise we need to loop again to get the same value in
  221. // `SaveWindowImpl()`.
  222. WindowState* window_state = WindowState::Get(mru_windows[i]);
  223. SaveWindowImpl(window_state, /*activation_index=*/i);
  224. }
  225. }
  226. void WindowRestoreController::OnWindowActivated(aura::Window* gained_active) {
  227. SaveAllWindows();
  228. }
  229. void WindowRestoreController::OnTabletModeStarted() {
  230. SaveAllWindows();
  231. }
  232. void WindowRestoreController::OnTabletModeEnded() {
  233. SaveAllWindows();
  234. }
  235. void WindowRestoreController::OnTabletControllerDestroyed() {
  236. tablet_mode_observation_.Reset();
  237. }
  238. void WindowRestoreController::OnRestorePrefChanged(const AccountId& account_id,
  239. bool could_restore) {
  240. if (could_restore)
  241. SaveAllWindows();
  242. }
  243. void WindowRestoreController::OnAppLaunched(aura::Window* window) {
  244. // Non ARC windows will already be saved as this point, as this is for cases
  245. // where an ARC window is created without a task.
  246. if (!IsArcWindow(window))
  247. return;
  248. // Save the window info once the app launched. If `window` does not have a
  249. // parent yet, there won't be any window state, so create an observer that
  250. // will save when `window` gets a parent. Save all windows since we need to
  251. // update the activation index of the other windows.
  252. if (window->parent()) {
  253. SaveAllWindows();
  254. return;
  255. }
  256. new ParentChangeObserver(window);
  257. }
  258. void WindowRestoreController::OnWidgetInitialized(views::Widget* widget) {
  259. DCHECK(widget);
  260. aura::Window* window = widget->GetNativeWindow();
  261. if (window->GetProperty(app_restore::kParentToHiddenContainerKey))
  262. return;
  263. UpdateAndObserveWindow(window);
  264. // If the restored bounds are out of the screen, move the window to the bounds
  265. // manually as most widget types force windows to be within the work area on
  266. // creation.
  267. // TODO(sammiequon): The Files app uses async Mojo calls to activate
  268. // and set its bounds, making this approach not work. In the future, we'll
  269. // need to address the Files app.
  270. MaybeRestoreOutOfBoundsWindows(window);
  271. }
  272. void WindowRestoreController::OnParentWindowToValidContainer(
  273. aura::Window* window) {
  274. DCHECK(window);
  275. DCHECK(window->GetProperty(app_restore::kParentToHiddenContainerKey));
  276. app_restore::WindowInfo* window_info = GetWindowInfo(window);
  277. if (window_info) {
  278. const int desk_id = window_info->desk_id
  279. ? int{*window_info->desk_id}
  280. : aura::client::kWindowWorkspaceUnassignedWorkspace;
  281. window->SetProperty(aura::client::kWindowWorkspaceKey, desk_id);
  282. }
  283. // Now that the hidden container key is cleared,
  284. // `aura::client::ParentWindowWithContext` should parent `window` to a valid
  285. // desk container.
  286. window->SetProperty(app_restore::kParentToHiddenContainerKey, false);
  287. aura::client::ParentWindowWithContext(window,
  288. /*context=*/window->GetRootWindow(),
  289. window->GetBoundsInScreen());
  290. UpdateAndObserveWindow(window);
  291. }
  292. void WindowRestoreController::OnWindowPropertyChanged(aura::Window* window,
  293. const void* key,
  294. intptr_t old) {
  295. // If the ARC ghost window becomes ARC app's window, it should be applied
  296. // the activation delay.
  297. if (key == app_restore::kRealArcTaskWindow &&
  298. window->GetProperty(app_restore::kRealArcTaskWindow)) {
  299. window->SetProperty(app_restore::kLaunchedFromAppRestoreKey, true);
  300. restore_property_clear_callbacks_.emplace(
  301. window, base::BindOnce(&WindowRestoreController::ClearLaunchedKey,
  302. weak_ptr_factory_.GetWeakPtr(), window));
  303. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  304. FROM_HERE, restore_property_clear_callbacks_[window].callback(),
  305. kAllowActivationDelay);
  306. }
  307. if (key != app_restore::kLaunchedFromAppRestoreKey ||
  308. window->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
  309. return;
  310. }
  311. // Once this property is cleared, there is no need to observe `window`
  312. // anymore.
  313. DCHECK(windows_observation_.IsObservingSource(window));
  314. windows_observation_.RemoveObservation(window);
  315. to_be_shown_windows_.erase(window);
  316. if (base::Contains(restore_property_clear_callbacks_, window))
  317. CancelAndRemoveRestorePropertyClearCallback(window);
  318. }
  319. void WindowRestoreController::OnWindowVisibilityChanged(aura::Window* window,
  320. bool visible) {
  321. // `OnWindowVisibilityChanged` fires for children of a window as well, but we
  322. // are only interested in the window we originally observed.
  323. if (!windows_observation_.IsObservingSource(window))
  324. return;
  325. if (!visible || !to_be_shown_windows_.contains(window))
  326. return;
  327. to_be_shown_windows_.erase(window);
  328. RestoreStateTypeAndClearLaunchedKey(window);
  329. // Early return if we're not in tablet mode, or the app list is null.
  330. aura::Window* app_list_window =
  331. Shell::Get()->app_list_controller()->GetWindow();
  332. if (!Shell::Get()->tablet_mode_controller()->InTabletMode() ||
  333. !app_list_window) {
  334. return;
  335. }
  336. // Because windows are shown inactive, they don't take focus/activation. This
  337. // can lead to situations in tablet mode where the app list is active and
  338. // visibly below restored windows. This causes the hotseat widget to not be
  339. // hidden, so deactivate the app list. See crbug.com/1202923.
  340. auto* app_list_widget =
  341. views::Widget::GetWidgetForNativeWindow(app_list_window);
  342. if (app_list_widget->IsActive() && WindowState::Get(window)->IsMaximized())
  343. app_list_widget->Deactivate();
  344. }
  345. void WindowRestoreController::OnWindowDestroying(aura::Window* window) {
  346. DCHECK(windows_observation_.IsObservingSource(window));
  347. windows_observation_.RemoveObservation(window);
  348. if (base::Contains(restore_property_clear_callbacks_, window))
  349. ClearLaunchedKey(window);
  350. }
  351. void WindowRestoreController::UpdateAndObserveWindow(aura::Window* window) {
  352. DCHECK(window);
  353. DCHECK(window->parent());
  354. windows_observation_.AddObservation(window);
  355. // Unless minimized, snap state and activation unblock are done when the
  356. // window is first shown, which will be async for exo apps.
  357. if (WindowState::Get(window)->IsMinimized() || window->IsVisible()) {
  358. // If the window is already visible, do not wait until it is next visible to
  359. // restore the state type and clear the launched key.
  360. RestoreStateTypeAndClearLaunchedKey(window);
  361. } else {
  362. to_be_shown_windows_.insert(window);
  363. // Clear the restore show state key in case for any reason the window
  364. // did not restore its minimized state.
  365. window->ClearProperty(aura::client::kRestoreShowStateKey);
  366. }
  367. StackWindow(window);
  368. }
  369. void WindowRestoreController::StackWindow(aura::Window* window) {
  370. int32_t* activation_index =
  371. window->GetProperty(app_restore::kActivationIndexKey);
  372. if (!activation_index)
  373. return;
  374. Shell::Get()->mru_window_tracker()->OnWindowAlteredByWindowRestore(window);
  375. // Stack the window.
  376. auto siblings = window->parent()->children();
  377. auto insertion_point = GetWindowToInsertBefore(window, siblings);
  378. if (insertion_point != siblings.end())
  379. window->parent()->StackChildBelow(window, *insertion_point);
  380. }
  381. bool WindowRestoreController::IsRestoringWindow(aura::Window* window) const {
  382. return windows_observation_.IsObservingSource(window);
  383. }
  384. void WindowRestoreController::SaveWindowImpl(
  385. WindowState* window_state,
  386. absl::optional<int> activation_index) {
  387. DCHECK(window_state);
  388. aura::Window* window = window_state->window();
  389. // Skip saving ARC PIP window.
  390. if (window_state->IsPip() && IsArcWindow(window))
  391. return;
  392. // Only apps whose parent is a certain container can be saved.
  393. if (!window->parent() ||
  394. !base::Contains(kAppParentContainers, window->parent()->GetId())) {
  395. return;
  396. }
  397. // Only some app types can be saved.
  398. if (!base::Contains(
  399. kSupportedAppTypes,
  400. static_cast<AppType>(window->GetProperty(aura::client::kAppType)))) {
  401. return;
  402. }
  403. // Do not save window data if the setting is turned off by active user.
  404. if (!app_restore::AppRestoreInfo::GetInstance()->CanPerformRestore(
  405. Shell::Get()->session_controller()->GetActiveAccountId())) {
  406. return;
  407. }
  408. aura::Window::Windows mru_windows;
  409. // We only need |mru_windows| if |activation_index| is nullopt as
  410. // |mru_windows| will be used to calculated the window's activation index when
  411. // it's not provided by |activation_index|.
  412. if (!activation_index.has_value()) {
  413. mru_windows =
  414. Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
  415. }
  416. std::unique_ptr<app_restore::WindowInfo> window_info = BuildWindowInfo(
  417. window, activation_index, /*for_saved_desks=*/false, mru_windows);
  418. full_restore::SaveWindowInfo(*window_info);
  419. if (g_save_window_callback_for_testing)
  420. g_save_window_callback_for_testing.Run(*window_info);
  421. }
  422. void WindowRestoreController::RestoreStateTypeAndClearLaunchedKey(
  423. aura::Window* window) {
  424. app_restore::WindowInfo* window_info = GetWindowInfo(window);
  425. if (window_info) {
  426. // Snap the window if necessary.
  427. auto state_type = window_info->window_state_type;
  428. if (state_type) {
  429. // Add the window to be tracked by the tablet mode window manager
  430. // manually. It is normally tracked when it becomes visible, but in snap
  431. // case we want to track it before it becomes visible. This will allow us
  432. // to snap the window before it is shown and skip first showing the window
  433. // in normal or maximized state.
  434. // TODO(crbug.com/1164472): Investigate splitview for ARC apps, which
  435. // are not managed by TabletModeWindowManager.
  436. if (Shell::Get()->tablet_mode_controller()->InTabletMode())
  437. Shell::Get()->tablet_mode_controller()->AddWindow(window);
  438. if (*state_type == chromeos::WindowStateType::kPrimarySnapped ||
  439. *state_type == chromeos::WindowStateType::kSecondarySnapped) {
  440. base::AutoReset<aura::Window*> auto_reset_to_be_snapped(
  441. &to_be_snapped_window_, window);
  442. const WindowSnapWMEvent snap_event(
  443. *state_type == chromeos::WindowStateType::kPrimarySnapped
  444. ? WM_EVENT_SNAP_PRIMARY
  445. : WM_EVENT_SNAP_SECONDARY);
  446. WindowState::Get(window)->OnWMEvent(&snap_event);
  447. }
  448. }
  449. }
  450. // Window that are launched from window restore are not activatable initially
  451. // to prevent them from taking activation when Widget::Show() is called. Make
  452. // these windows activatable once they are launched. Use a post task since it
  453. // is quite common for some widgets to explicitly call Show() after
  454. // initialized.
  455. // TODO(sammiequon): Instead of disabling activation when creating the widget
  456. // and enabling it here, use `ShowInactive()` instead of `Show()` when the
  457. // widget is created.
  458. restore_property_clear_callbacks_.emplace(
  459. window, base::BindOnce(&WindowRestoreController::ClearLaunchedKey,
  460. weak_ptr_factory_.GetWeakPtr(), window));
  461. // Also, for some ARC and chrome apps, the client can request activation after
  462. // showing. We cannot detect this, so we use a timeout to keep the window not
  463. // activatable for a while longer. Classic browser and lacros windows are
  464. // expected to call `ShowInactive()` where the browser is created.
  465. const AppType app_type =
  466. static_cast<AppType>(window->GetProperty(aura::client::kAppType));
  467. // Prevent apply activation delay on ARC ghost window. It should be only apply
  468. // on real ARC window. Only ARC ghost window use this property.
  469. const bool is_real_arc_window =
  470. window->GetProperty(app_restore::kRealArcTaskWindow);
  471. const base::TimeDelta delay =
  472. app_type == AppType::CHROME_APP ||
  473. (app_type == AppType::ARC_APP && is_real_arc_window)
  474. ? kAllowActivationDelay
  475. : base::TimeDelta();
  476. base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
  477. FROM_HERE, restore_property_clear_callbacks_[window].callback(), delay);
  478. }
  479. void WindowRestoreController::ClearLaunchedKey(aura::Window* window) {
  480. CancelAndRemoveRestorePropertyClearCallback(window);
  481. // If the window is destroying then prevent extra work by not clearing the
  482. // property.
  483. if (!window->is_destroying())
  484. window->SetProperty(app_restore::kLaunchedFromAppRestoreKey, false);
  485. }
  486. void WindowRestoreController::CancelAndRemoveRestorePropertyClearCallback(
  487. aura::Window* window) {
  488. DCHECK(window);
  489. DCHECK(base::Contains(restore_property_clear_callbacks_, window));
  490. restore_property_clear_callbacks_[window].Cancel();
  491. restore_property_clear_callbacks_.erase(window);
  492. }
  493. void WindowRestoreController::SetSaveWindowCallbackForTesting(
  494. SaveWindowCallback callback) {
  495. g_save_window_callback_for_testing = std::move(callback);
  496. }
  497. } // namespace ash