1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
ash / wm / window_restore / window_restore_controller.cc [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/window_restore/window_restore_controller.h"
#include <cstdint>
#include "ash/app_list/app_list_controller_impl.h"
#include "ash/public/cpp/app_types_util.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/wm/container_finder.h"
#include "ash/wm/desks/desk.h"
#include "ash/wm/desks/desks_util.h"
#include "ash/wm/desks/templates/saved_desk_util.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_positioning_utils.h"
#include "ash/wm/window_restore/window_restore_util.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ui/base/app_types.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/app_restore/full_restore_utils.h"
#include "components/app_restore/window_properties.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/tablet_state.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
WindowRestoreController* g_instance = nullptr;
// Callback for testing which is run when `SaveWindowImpl()` triggers a write to
// file.
WindowRestoreController::SaveWindowCallback g_save_window_callback_for_testing;
// The list of possible app window parents.
constexpr ShellWindowId kAppParentContainers[19] = {
kShellWindowId_DeskContainerA, kShellWindowId_DeskContainerB,
kShellWindowId_DeskContainerC, kShellWindowId_DeskContainerD,
kShellWindowId_DeskContainerE, kShellWindowId_DeskContainerF,
kShellWindowId_DeskContainerG, kShellWindowId_DeskContainerH,
kShellWindowId_DeskContainerI, kShellWindowId_DeskContainerJ,
kShellWindowId_DeskContainerK, kShellWindowId_DeskContainerL,
kShellWindowId_DeskContainerM, kShellWindowId_DeskContainerN,
kShellWindowId_DeskContainerO, kShellWindowId_DeskContainerP,
kShellWindowId_AlwaysOnTopContainer, kShellWindowId_FloatContainer,
kShellWindowId_UnparentedContainer,
};
// The types of apps currently supported by window restore.
// TODO(crbug.com/40163553): Checking app type is temporary solution until we
// can get windows which are allowed to window restore from the
// FullRestoreService.
constexpr chromeos::AppType kSupportedAppTypes[] = {
chromeos::AppType::BROWSER, chromeos::AppType::CHROME_APP,
chromeos::AppType::ARC_APP, chromeos::AppType::SYSTEM_APP};
// Delay for certain app types before activation is allowed. This is because
// some apps' client request activation after creation, which can break user
// flow.
constexpr base::TimeDelta kAllowActivationDelay = base::Seconds(2);
app_restore::WindowInfo* GetWindowInfo(aura::Window* window) {
return window->GetProperty(app_restore::kWindowInfoKey);
}
// 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) {
app_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);
const gfx::Rect display_area = closest_display.work_area();
if (display_area.Contains(current_bounds))
return;
AdjustBoundsToEnsureMinimumWindowVisibility(
display_area, /*client_controlled=*/false, ¤t_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);
}
}
// Self deleting class which watches a unparented window and deletes itself once
// the window has a parent.
class ParentChangeObserver : public aura::WindowObserver {
public:
ParentChangeObserver(aura::Window* window) {
DCHECK(!window->parent());
window_observation_.Observe(window);
}
ParentChangeObserver(const ParentChangeObserver&) = delete;
ParentChangeObserver& operator=(const ParentChangeObserver&) = delete;
~ParentChangeObserver() override = default;
// aura::WindowObserver:
void OnWindowParentChanged(aura::Window* window,
aura::Window* parent) override {
if (!parent)
return;
WindowRestoreController::Get()->SaveAllWindows();
delete this;
}
void OnWindowDestroying(aura::Window* window) override { delete this; }
base::ScopedObservation<aura::Window, aura::WindowObserver>
window_observation_{this};
};
} // namespace
WindowRestoreController::WindowRestoreController() {
DCHECK_EQ(nullptr, g_instance);
g_instance = this;
app_restore_info_observation_.Observe(
app_restore::AppRestoreInfo::GetInstance());
}
WindowRestoreController::~WindowRestoreController() {
DCHECK_EQ(this, g_instance);
g_instance = nullptr;
}
// static
WindowRestoreController* WindowRestoreController::Get() {
return g_instance;
}
// static
bool WindowRestoreController::CanActivateRestoredWindow(
const aura::Window* window) {
if (!window->GetProperty(app_restore::kLaunchedFromAppRestoreKey))
return true;
// Only windows on the active desk should be activatable.
if (!desks_util::BelongsToActiveDesk(const_cast<aura::Window*>(window)))
return false;
// Ghost windows can be activated.
const chromeos::AppType app_type = window->GetProperty(chromeos::kAppTypeKey);
const bool is_real_arc_window =
window->GetProperty(app_restore::kRealArcTaskWindow);
if (app_type == chromeos::AppType::ARC_APP && !is_real_arc_window) {
return true;
}
auto* desk_container = window->parent();
if (!desk_container || !desks_util::IsDeskContainer(desk_container))
return true;
// Only the topmost unminimize restored window can be activated.
auto siblings = desk_container->children();
for (aura::Window* const sibling : base::Reversed(siblings)) {
if (WindowState::Get(sibling)->IsMinimized())
continue;
return window == sibling;
}
return false;
}
// static
bool WindowRestoreController::CanActivateAppList(const aura::Window* window) {
if (!display::Screen::GetScreen()->InTabletMode()) {
return true;
}
auto* app_list_controller = Shell::Get()->app_list_controller();
if (!app_list_controller || app_list_controller->GetWindow() != window)
return true;
for (aura::Window* root_window : Shell::GetAllRootWindows()) {
auto active_desk_children =
desks_util::GetActiveDeskContainerForRoot(root_window)->children();
// Find the topmost unminimized window.
auto topmost_visible_iter = active_desk_children.rbegin();
while (topmost_visible_iter != active_desk_children.rend() &&
WindowState::Get(*topmost_visible_iter)->IsMinimized()) {
topmost_visible_iter = std::next(topmost_visible_iter);
}
if (topmost_visible_iter != active_desk_children.rend() &&
(*topmost_visible_iter)
->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
return false;
}
}
return true;
}
// static
std::vector<raw_ptr<aura::Window, VectorExperimental>>::const_iterator
WindowRestoreController::GetWindowToInsertBefore(
aura::Window* window,
const std::vector<raw_ptr<aura::Window, VectorExperimental>>& windows) {
const int32_t* activation_index =
window->GetProperty(app_restore::kActivationIndexKey);
DCHECK(activation_index);
// If this is an admin template window, it should be placed on top of existing
// windows (but relative to other desk template windows).
if (saved_desk_util::IsWindowOnTopForTemplate(window)) {
for (auto it = windows.begin(); it != windows.end(); ++it) {
const int32_t* next_activation_index =
(*it)->GetProperty(app_restore::kActivationIndexKey);
if (next_activation_index && *activation_index > *next_activation_index) {
return it;
}
}
return windows.end();
}
auto it = windows.begin();
while (it != windows.end()) {
const int32_t* next_activation_index =
(*it)->GetProperty(app_restore::kActivationIndexKey);
if (!next_activation_index || *activation_index > *next_activation_index) {
// Activation index is saved to match MRU order so lower means more
// recent/higher in stacking order. Also restored windows should be
// stacked below non-restored windows.
return it;
}
it = std::next(it);
}
return it;
}
void WindowRestoreController::SaveWindow(WindowState* window_state) {
SaveWindowImpl(window_state, /*activation_index=*/std::nullopt);
}
void WindowRestoreController::SaveAllWindows() {
auto mru_windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
for (int i = 0; i < static_cast<int>(mru_windows.size()); ++i) {
// Provide the activation index here since we need to loop through `windows`
// anyhow. Otherwise we need to loop again to get the same value in
// `SaveWindowImpl()`.
WindowState* window_state = WindowState::Get(mru_windows[i]);
SaveWindowImpl(window_state, /*activation_index=*/i);
}
}
void WindowRestoreController::OnWindowActivated(aura::Window* gained_active) {
SaveAllWindows();
}
void WindowRestoreController::OnDisplayTabletStateChanged(
display::TabletState state) {
if (display::IsTabletStateChanging(state)) {
// Do nothing if the tablet state is still in the process of transition.
return;
}
SaveAllWindows();
}
void WindowRestoreController::OnRestorePrefChanged(const AccountId& account_id,
bool could_restore) {
if (could_restore)
SaveAllWindows();
}
void WindowRestoreController::OnAppLaunched(aura::Window* window) {
// Non ARC windows will already be saved as this point, as this is for cases
// where an ARC window is created without a task.
if (!IsArcWindow(window))
return;
// Save the window info once the app launched. If `window` does not have a
// parent yet, there won't be any window state, so create an observer that
// will save when `window` gets a parent. Save all windows since we need to
// update the activation index of the other windows.
if (window->parent()) {
SaveAllWindows();
return;
}
new ParentChangeObserver(window);
}
void WindowRestoreController::OnWidgetInitialized(views::Widget* widget) {
CHECK(widget);
aura::Window* window = widget->GetNativeWindow();
if (window->GetProperty(app_restore::kParentToHiddenContainerKey)) {
return;
}
if (!window->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
return;
}
// Windows with restore window key less than -1 are launched from desk
// templates or saved desks; we want to stay in overview for these. Windows
// with restore window key more than -1 are launched from full restore and we
// want to end overview for these.
if (window->GetProperty(app_restore::kRestoreWindowIdKey) > -1) {
OverviewController::Get()->EndOverview(OverviewEndAction::kFullRestore);
}
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.
MaybeRestoreOutOfBoundsWindows(window);
}
void WindowRestoreController::OnParentWindowToValidContainer(
aura::Window* window) {
DCHECK(window);
DCHECK(window->GetProperty(app_restore::kParentToHiddenContainerKey));
app_restore::WindowInfo* window_info = GetWindowInfo(window);
if (window_info) {
int desk_id = -1;
if (window_info->desk_guid.is_valid()) {
desk_id =
DesksController::Get()->GetDeskIndexByUuid(window_info->desk_guid);
}
// Its possible that the uuid is valid but it is not the uuid of any current
// desk.
if (desk_id == -1) {
desk_id = window_info->desk_id
? static_cast<int>(*window_info->desk_id)
: aura::client::kWindowWorkspaceUnassignedWorkspace;
}
window->SetProperty(aura::client::kWindowWorkspaceKey, desk_id);
}
// Now that the hidden container key is cleared,
// `aura::client::ParentWindowWithContext` should parent `window` to a valid
// desk container.
window->SetProperty(app_restore::kParentToHiddenContainerKey, false);
aura::client::ParentWindowWithContext(window,
/*context=*/window->GetRootWindow(),
window->GetBoundsInScreen(),
display::kInvalidDisplayId);
UpdateAndObserveWindow(window);
}
void WindowRestoreController::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
// If the ARC ghost window becomes ARC app's window, it should be applied
// the activation delay.
if (key == app_restore::kRealArcTaskWindow &&
window->GetProperty(app_restore::kRealArcTaskWindow)) {
window->SetProperty(app_restore::kLaunchedFromAppRestoreKey, true);
restore_property_clear_callbacks_.emplace(
window, base::BindOnce(&WindowRestoreController::ClearLaunchedKey,
weak_ptr_factory_.GetWeakPtr(), window));
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, restore_property_clear_callbacks_[window].callback(),
kAllowActivationDelay);
}
if (key != app_restore::kLaunchedFromAppRestoreKey ||
window->GetProperty(app_restore::kLaunchedFromAppRestoreKey)) {
return;
}
// Once this property is cleared, there is no need to observe `window`
// anymore.
DCHECK(windows_observation_.IsObservingSource(window));
windows_observation_.RemoveObservation(window);
to_be_shown_windows_.erase(window);
if (base::Contains(restore_property_clear_callbacks_, window))
CancelAndRemoveRestorePropertyClearCallback(window);
}
void WindowRestoreController::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
// `OnWindowVisibilityChanged` fires for children of a window as well, but we
// are only interested in the window we originally observed.
if (!windows_observation_.IsObservingSource(window))
return;
if (!visible || !to_be_shown_windows_.contains(window))
return;
to_be_shown_windows_.erase(window);
RestoreStateTypeAndClearLaunchedKey(window);
// Early return if we're not in tablet mode, or the app list is null.
aura::Window* app_list_window =
Shell::Get()->app_list_controller()->GetWindow();
if (!Shell::Get()->IsInTabletMode() || !app_list_window) {
return;
}
// Because windows are shown inactive, they don't take focus/activation. This
// can lead to situations in tablet mode where the app list is active and
// visibly below restored windows. This causes the hotseat widget to not be
// hidden, so deactivate the app list. See crbug.com/1202923.
auto* app_list_widget =
views::Widget::GetWidgetForNativeWindow(app_list_window);
if (app_list_widget->IsActive() && WindowState::Get(window)->IsMaximized())
app_list_widget->Deactivate();
}
void WindowRestoreController::OnWindowDestroying(aura::Window* window) {
DCHECK(windows_observation_.IsObservingSource(window));
windows_observation_.RemoveObservation(window);
if (base::Contains(restore_property_clear_callbacks_, window))
ClearLaunchedKey(window);
}
void WindowRestoreController::UpdateAndObserveWindow(aura::Window* window) {
DCHECK(window);
DCHECK(window->parent());
windows_observation_.AddObservation(window);
// Unless minimized, snap state and activation unblock are done when the
// window is first shown, which will be async for exo apps.
if (WindowState::Get(window)->IsMinimized() || window->IsVisible()) {
// If the window is already visible, do not wait until it is next visible to
// restore the state type and clear the launched key.
RestoreStateTypeAndClearLaunchedKey(window);
} else {
to_be_shown_windows_.insert(window);
// Clear the restore show state key in case for any reason the window
// did not restore its minimized state.
window->ClearProperty(aura::client::kRestoreShowStateKey);
}
StackWindow(window);
}
void WindowRestoreController::StackWindow(aura::Window* window) {
int32_t* activation_index =
window->GetProperty(app_restore::kActivationIndexKey);
if (!activation_index)
return;
Shell::Get()->mru_window_tracker()->OnWindowAlteredByWindowRestore(window);
// Stack the window.
auto siblings = window->parent()->children();
auto insertion_point = GetWindowToInsertBefore(window, siblings);
if (insertion_point != siblings.end())
window->parent()->StackChildBelow(window, *insertion_point);
}
bool WindowRestoreController::IsRestoringWindow(aura::Window* window) const {
return windows_observation_.IsObservingSource(window);
}
void WindowRestoreController::SaveWindowImpl(
WindowState* window_state,
std::optional<int> activation_index) {
DCHECK(window_state);
aura::Window* window = window_state->window();
// Skip saving ARC PIP window.
if (window_state->IsPip() && IsArcWindow(window))
return;
// Only apps whose parent is a certain container can be saved.
if (!window->parent() ||
!base::Contains(kAppParentContainers, window->parent()->GetId())) {
return;
}
// Only some app types can be saved.
if (!base::Contains(kSupportedAppTypes,
window->GetProperty(chromeos::kAppTypeKey))) {
return;
}
// Do not save window data if the setting is turned off by active user.
if (!app_restore::AppRestoreInfo::GetInstance()->CanPerformRestore(
Shell::Get()->session_controller()->GetActiveAccountId())) {
return;
}
aura::Window::Windows mru_windows;
// We only need |mru_windows| if |activation_index| is nullopt as
// |mru_windows| will be used to calculated the window's activation index when
// it's not provided by |activation_index|.
if (!activation_index.has_value()) {
mru_windows =
Shell::Get()->mru_window_tracker()->BuildMruWindowList(kAllDesks);
}
std::unique_ptr<app_restore::WindowInfo> window_info =
BuildWindowInfo(window, activation_index, mru_windows);
::full_restore::SaveWindowInfo(*window_info);
if (g_save_window_callback_for_testing)
g_save_window_callback_for_testing.Run(*window_info);
}
void WindowRestoreController::RestoreStateTypeAndClearLaunchedKey(
aura::Window* window) {
app_restore::WindowInfo* window_info = GetWindowInfo(window);
if (window_info) {
// Snap the window if necessary.
auto state_type = window_info->window_state_type;
if (state_type) {
auto* window_state = WindowState::Get(window);
// Add the window to be tracked by the tablet mode window manager
// manually. It is normally tracked when it becomes visible, but in snap
// case we want to track it before it becomes visible. This will allow us
// to snap the window before it is shown and skip first showing the window
// in normal or maximized state.
// TODO(crbug.com/40163553): Investigate splitview for ARC apps, which
// are not managed by TabletModeWindowManager.
Shell::Get()->tablet_mode_controller()->AddWindow(window);
if (chromeos::IsSnappedWindowStateType(*state_type)) {
base::AutoReset<raw_ptr<aura::Window>> auto_reset_to_be_snapped(
&to_be_snapped_window_, window);
// Use the window restore info snap percentage as the target snap ratio.
const float snap_ratio = window_info->snap_percentage.value_or(
chromeos::kDefaultSnapRatio * 100) *
0.01f;
const WindowSnapWMEvent snap_event(
*state_type == chromeos::WindowStateType::kPrimarySnapped
? WM_EVENT_SNAP_PRIMARY
: WM_EVENT_SNAP_SECONDARY,
snap_ratio,
WindowSnapActionSource::
kSnapByFullRestoreOrDeskTemplateOrSavedDesk);
window_state->OnWMEvent(&snap_event);
}
if (*state_type == chromeos::WindowStateType::kFloated) {
const WindowFloatWMEvent float_event(
chromeos::FloatStartLocation::kBottomRight);
window_state->OnWMEvent(&float_event);
}
}
}
// Window that are launched from window restore are not activatable initially
// to prevent them from taking activation when Widget::Show() is called. Make
// these windows activatable once they are launched. Use a post task since it
// is quite common for some widgets to explicitly call Show() after
// initialized.
// TODO(sammiequon): Instead of disabling activation when creating the widget
// and enabling it here, use `ShowInactive()` instead of `Show()` when the
// widget is created.
restore_property_clear_callbacks_.emplace(
window, base::BindOnce(&WindowRestoreController::ClearLaunchedKey,
weak_ptr_factory_.GetWeakPtr(), window));
// Also, for some ARC and chrome apps, the client can request activation after
// showing. We cannot detect this, so we use a timeout to keep the window not
// activatable for a while longer. Classic browser and lacros windows are
// expected to call `ShowInactive()` where the browser is created.
const chromeos::AppType app_type = window->GetProperty(chromeos::kAppTypeKey);
// Prevent apply activation delay on ARC ghost window. It should be only apply
// on real ARC window. Only ARC ghost window use this property.
const bool is_real_arc_window =
window->GetProperty(app_restore::kRealArcTaskWindow);
const base::TimeDelta delay =
app_type == chromeos::AppType::CHROME_APP ||
(app_type == chromeos::AppType::ARC_APP && is_real_arc_window)
? kAllowActivationDelay
: base::TimeDelta();
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, restore_property_clear_callbacks_[window].callback(), delay);
}
void WindowRestoreController::ClearLaunchedKey(aura::Window* window) {
CancelAndRemoveRestorePropertyClearCallback(window);
// If the window is destroying then prevent extra work by not clearing the
// property.
if (!window->is_destroying())
window->SetProperty(app_restore::kLaunchedFromAppRestoreKey, false);
}
void WindowRestoreController::CancelAndRemoveRestorePropertyClearCallback(
aura::Window* window) {
DCHECK(window);
DCHECK(base::Contains(restore_property_clear_callbacks_, window));
restore_property_clear_callbacks_[window].Cancel();
restore_property_clear_callbacks_.erase(window);
}
void WindowRestoreController::SetSaveWindowCallbackForTesting(
SaveWindowCallback callback) {
g_save_window_callback_for_testing = std::move(callback);
}
} // namespace ash