// Copyright 2013 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/overview/scoped_overview_transform_window.h"
#include <algorithm>
#include <utility>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/style/system_shadow.h"
#include "ash/wm/float/float_controller.h"
#include "ash/wm/overview/delayed_animation_observer_impl.h"
#include "ash/wm/overview/overview_constants.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_item_view.h"
#include "ash/wm/overview/overview_utils.h"
#include "ash/wm/overview/scoped_overview_animation_settings.h"
#include "ash/wm/scoped_layer_tree_synchronizer.h"
#include "ash/wm/snap_group/snap_group.h"
#include "ash/wm/snap_group/snap_group_controller.h"
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/splitview/split_view_utils.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_transient_descendant_iterator.h"
#include "ash/wm/window_util.h"
#include "ash/wm/wm_constants.h"
#include "base/auto_reset.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/ui/base/window_properties.h"
#include "chromeos/ui/base/window_state_type.h"
#include "chromeos/ui/frame/frame_utils.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/transient_window_client.h"
#include "ui/aura/scoped_window_event_targeting_blocker.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_observer.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d_f.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"
namespace ash {
namespace {
// When set to true by tests makes closing the widget synchronous.
bool immediate_close_for_tests = false;
// Delay closing window to allow it to shrink and fade out.
constexpr int kCloseWindowDelayInMilliseconds = 150;
void ClearWindowProperties(aura::Window* window) {
window->ClearProperty(chromeos::kIsShowingInOverviewKey);
window->ClearProperty(kHideInOverviewKey);
}
// Layer animation observer that is attached to a clip and/or rounded corners
// animation. We need this for the exit animation, where we want to animate
// properties but the overview session has been destroyed. We want to use this
// observer for animations that require an intermediate step. For example, when
// removing a clip, we want to first animate to the size of the window, and then
// set the clip rect to be empty after the animation has completed.
class UndoPropertyObserver : public ui::ImplicitAnimationObserver,
public aura::WindowObserver {
public:
explicit UndoPropertyObserver(aura::Window* window) : window_(window) {
window_->AddObserver(this);
}
UndoPropertyObserver(const UndoPropertyObserver&) = delete;
UndoPropertyObserver& operator=(const UndoPropertyObserver&) = delete;
~UndoPropertyObserver() override {
StopObservingImplicitAnimations();
window_->RemoveObserver(this);
window_ = nullptr;
}
private:
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
window_->layer()->SetClipRect(gfx::Rect());
delete this;
}
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override {
CHECK_EQ(window_, window);
delete this;
}
// Guaranteed to be not null for the duration of `this`.
raw_ptr<aura::Window> window_;
};
} // namespace
class ScopedOverviewTransformWindow::LayerCachingAndFilteringObserver
: public ui::LayerObserver {
public:
explicit LayerCachingAndFilteringObserver(ui::Layer* layer) : layer_(layer) {
layer_->AddObserver(this);
layer_->AddCacheRenderSurfaceRequest();
layer_->AddTrilinearFilteringRequest();
}
LayerCachingAndFilteringObserver(const LayerCachingAndFilteringObserver&) =
delete;
LayerCachingAndFilteringObserver& operator=(
const LayerCachingAndFilteringObserver&) = delete;
~LayerCachingAndFilteringObserver() override {
if (layer_) {
layer_->RemoveTrilinearFilteringRequest();
layer_->RemoveCacheRenderSurfaceRequest();
layer_->RemoveObserver(this);
}
}
// ui::LayerObserver overrides:
void LayerDestroyed(ui::Layer* layer) override {
layer_->RemoveObserver(this);
layer_ = nullptr;
}
private:
raw_ptr<ui::Layer> layer_;
};
ScopedOverviewTransformWindow::ScopedOverviewTransformWindow(
OverviewItem* overview_item,
aura::Window* window)
: overview_item_(overview_item),
window_(window),
original_opacity_(window->layer()->GetTargetOpacity()),
original_clip_rect_(window_->layer()->GetTargetClipRect()) {
raster_scale_observer_lock_.emplace(
(new RasterScaleLayerObserver(window_, window_->layer(), window_))
->Lock());
fill_mode_ = GetOverviewItemFillModeForWindow(window);
std::vector<raw_ptr<aura::Window, VectorExperimental>>
transient_children_to_hide;
for (auto* transient : GetTransientTreeIterator(window)) {
event_targeting_blocker_map_[transient] =
std::make_unique<aura::ScopedWindowEventTargetingBlocker>(transient);
if (window_util::AsBubbleDialogDelegate(transient)) {
transient->SetProperty(kHideInOverviewKey, true);
} else {
transient->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// Add this as `aura::WindowObserver` for observing `kHideInOverviewKey`
// property changes.
window_observations_.AddObservation(transient);
}
// Hide transient children which have been specified to be hidden in
// overview mode.
if (transient != window && transient->GetProperty(kHideInOverviewKey)) {
transient_children_to_hide.push_back(transient);
}
}
if (!transient_children_to_hide.empty())
AddHiddenTransientWindows(std::move(transient_children_to_hide));
aura::client::GetTransientWindowClient()->AddObserver(this);
// Tablet mode grid layout has scrolling, so all windows must be stacked under
// the current split view window if they share the same parent so that during
// scrolls, they get scrolled underneath the split view window. The window
// will be returned to its proper z-order on exiting overview if it is
// activated.
// TODO(sammiequon): This does not handle the case if either the snapped
// window or this window is an always on top window.
if (auto* split_view_controller =
SplitViewController::Get(Shell::GetPrimaryRootWindow());
ShouldUseTabletModeGridLayout() &&
split_view_controller->InSplitViewMode()) {
aura::Window* snapped_window =
split_view_controller->GetDefaultSnappedWindow();
if (window->parent() == snapped_window->parent()) {
// Helper to get the z order of a window in its parent.
auto get_z_order = [](aura::Window* window) -> size_t {
for (size_t i = 0u; i < window->parent()->children().size(); ++i) {
if (window == window->parent()->children()[i])
return i;
}
NOTREACHED();
};
if (get_z_order(window_) > get_z_order(snapped_window))
window_->parent()->StackChildBelow(window_, snapped_window);
}
}
// In overview, each window, along with its transient window, has the
// display's root window as a common ancestor.
// Note: windows in the overview belong to different containers. For instance,
// normal windows belong to a desk container, floated windows to a float
// container, and always-on-top windows to their respective container.
window_tree_synchronizer_ = std::make_unique<ScopedWindowTreeSynchronizer>(
window_->GetRootWindow(), /*restore_tree=*/true);
}
ScopedOverviewTransformWindow::~ScopedOverviewTransformWindow() {
// Reset clipping in the case `RestoreWindow()` is not called, such as when
// `this` is dragged to another display. Without this check, `SetClipping`
// would override the one we called in `RestoreWindow()` which would result in
// the same final clip but may remove the animation. See crbug.com/1140639.
if (reset_clip_on_shutdown_) {
SetClipping(gfx::Rect(original_clip_rect_.size()));
}
for (auto* transient : GetTransientTreeIterator(window_)) {
ClearWindowProperties(transient);
DCHECK(event_targeting_blocker_map_.contains(transient));
event_targeting_blocker_map_.erase(transient);
}
UpdateRoundedCorners(/*show=*/false);
aura::client::GetTransientWindowClient()->RemoveObserver(this);
}
// static
float ScopedOverviewTransformWindow::GetItemScale(int source_height,
int target_height,
int top_view_inset,
int title_height) {
return std::min(2.0f, static_cast<float>(target_height - title_height) /
(source_height - top_view_inset));
}
void ScopedOverviewTransformWindow::RestoreWindow(bool reset_transform,
bool animate) {
base::AutoReset<bool> restoring(&is_restoring_, true);
// Shadow controller may be null on shutdown.
if (auto* shadow_controller = Shell::Get()->shadow_controller()) {
shadow_controller->UpdateShadowForWindow(window_);
}
// We will handle clipping here, no need to do anything in the destructor.
reset_clip_on_shutdown_ = false;
if (!animate || IsMinimizedOrTucked()) {
// Minimized windows may have had their transforms altered by swiping up
// from the shelf.
ScopedOverviewAnimationSettings animation_settings(OVERVIEW_ANIMATION_NONE,
window_);
window_util::SetTransform(window_, gfx::Transform());
SetClipping(gfx::Rect(original_clip_rect_.size()));
return;
}
if (reset_transform) {
ScopedAnimationSettings animation_settings_list;
BeginScopedAnimation(overview_item_->GetExitTransformAnimationType(),
&animation_settings_list);
for (auto& settings : animation_settings_list) {
auto exit_observer = std::make_unique<ExitAnimationObserver>();
settings->AddObserver(exit_observer.get());
if (window_->layer()->GetAnimator() == settings->GetAnimator())
settings->AddObserver(new WindowTransformAnimationObserver(window_));
OverviewController::Get()->AddExitAnimationObserver(
std::move(exit_observer));
}
// Use identity transform directly to reset window's transform when exiting
// overview.
window_util::SetTransform(window_, gfx::Transform());
// Add requests to cache render surface and perform trilinear filtering for
// the exit animation of overview mode. The requests will be removed when
// the exit animation finishes.
if (features::IsTrilinearFilteringEnabled()) {
for (auto& settings : animation_settings_list) {
settings->CacheRenderSurface();
settings->TrilinearFiltering();
}
}
}
ScopedOverviewAnimationSettings animation_settings(
overview_item_->GetExitOverviewAnimationType(), window_);
SetOpacity(original_opacity_);
if (original_clip_rect_.IsEmpty()) {
animation_settings.AddObserver(new UndoPropertyObserver(window_));
SetClipping(gfx::Rect(window_->bounds().size()));
} else {
SetClipping(gfx::Rect(original_clip_rect_.size()));
}
window_tree_synchronizer_->Restore();
}
void ScopedOverviewTransformWindow::BeginScopedAnimation(
OverviewAnimationType animation_type,
ScopedAnimationSettings* animation_settings) {
if (animation_type == OVERVIEW_ANIMATION_NONE)
return;
for (auto* window : window_util::GetVisibleTransientTreeIterator(window_)) {
auto settings = std::make_unique<ScopedOverviewAnimationSettings>(
animation_type, window);
settings->DeferPaint();
// Create an EnterAnimationObserver if this is an enter overview layout
// animation.
if (animation_type == OVERVIEW_ANIMATION_LAYOUT_OVERVIEW_ITEMS_ON_ENTER) {
auto enter_observer = std::make_unique<EnterAnimationObserver>();
settings->AddObserver(enter_observer.get());
OverviewController::Get()->AddEnterAnimationObserver(
std::move(enter_observer));
}
animation_settings->push_back(std::move(settings));
}
}
bool ScopedOverviewTransformWindow::Contains(const aura::Window* target) const {
for (auto* window : GetTransientTreeIterator(window_)) {
if (window->Contains(target))
return true;
}
if (!IsMinimizedOrTucked()) {
return false;
}
// A minimized window's item_widget_ may have already been destroyed.
const auto* item_widget = overview_item_->item_widget();
if (!item_widget)
return false;
return item_widget->GetNativeWindow()->Contains(target);
}
gfx::RectF ScopedOverviewTransformWindow::GetTransformedBounds() const {
return window_util::GetTransformedBounds(window_, GetTopInset());
}
int ScopedOverviewTransformWindow::GetTopInset() const {
// Mirror window doesn't have insets.
if (IsMinimizedOrTucked()) {
return 0;
}
for (auto* window : window_util::GetVisibleTransientTreeIterator(window_)) {
// If there are regular windows in the transient ancestor tree, all those
// windows are shown in the same overview item and the header is not masked.
if (window != window_ &&
window->GetType() == aura::client::WINDOW_TYPE_NORMAL) {
return 0;
}
}
return window_->GetProperty(aura::client::kTopViewInset);
}
void ScopedOverviewTransformWindow::SetOpacity(float opacity) {
for (auto* window :
window_util::GetVisibleTransientTreeIterator(GetOverviewWindow()))
window->layer()->SetOpacity(opacity);
}
void ScopedOverviewTransformWindow::SetClipping(const gfx::Rect& clip_rect) {
// No need to clip `window_` if it is about to be destroyed.
if (window_->is_destroying()) {
return;
}
ui::Layer* layer = window_->layer();
// TODO(sammiequon): Investigate why we cannot use
// `ui::Layer::GetTargetClipRect()` here.
if (layer->GetAnimator()->GetTargetClipRect() == clip_rect) {
return;
}
layer->SetClipRect(clip_rect);
}
gfx::RectF ScopedOverviewTransformWindow::ShrinkRectToFitPreservingAspectRatio(
const gfx::RectF& rect,
const gfx::RectF& bounds,
int top_view_inset,
int title_height) const {
DCHECK(!rect.IsEmpty());
DCHECK_LE(top_view_inset, rect.height());
const float scale = GetItemScale(rect.height(), bounds.height(),
top_view_inset, title_height);
const float horizontal_offset = 0.5 * (bounds.width() - scale * rect.width());
const float width = bounds.width() - 2.f * horizontal_offset;
const float vertical_offset = title_height - scale * top_view_inset;
const float height =
std::min(scale * rect.height(), bounds.height() - vertical_offset);
gfx::RectF new_bounds(bounds.x() + horizontal_offset,
bounds.y() + vertical_offset, width, height);
switch (fill_mode_) {
case OverviewItemFillMode::kLetterBoxed:
case OverviewItemFillMode::kPillarBoxed: {
// Attempt to scale |rect| to fit |bounds|. Maintain the aspect ratio of
// |rect|. Letter boxed windows' width will match |bounds|'s width and
// pillar boxed windows' height will match |bounds|'s height.
const bool is_pillar = fill_mode_ == OverviewItemFillMode::kPillarBoxed;
const gfx::Rect window_bounds =
::wm::GetTransientRoot(window_)->GetBoundsInScreen();
const float window_ratio =
static_cast<float>(window_bounds.width()) / window_bounds.height();
if (is_pillar) {
const float new_width = height * window_ratio;
new_bounds.set_width(new_width);
} else {
// For some use cases, the `new_height` is larger than the maximum
// height should be applied to the window within the `bounds` even it's
// letter box window type. In this case we should use maximum height
// directly.
float new_height = std::min(height, bounds.width() / window_ratio);
new_bounds = bounds;
new_bounds.Inset(gfx::InsetsF::TLBR(title_height, 0, 0, 0));
if (top_view_inset) {
new_bounds.set_height(new_height);
// Calculate `scaled_top_view_inset` without considering
// `title_height` because we have already inset the top of
// `new_bounds` by that value. We also do not consider
// `top_view_inset` in our calculation of `new_scale` because we want
// to find out the height of the inset when the whole window,
// including the inset, is scaled down to `new_bounds`.
const float new_scale =
GetItemScale(rect.height(), new_bounds.height(), 0, 0);
const float scaled_top_view_inset = top_view_inset * new_scale;
// Offset `new_bounds` to be at a point in the overview item frame
// where it will be centered when we clip the `top_view_inset`.
new_bounds.Offset(0, (bounds.height() - title_height) / 2 -
(new_height - scaled_top_view_inset) / 2 -
scaled_top_view_inset);
} else {
new_bounds.ClampToCenteredSize(
gfx::SizeF(bounds.width(), new_height));
}
}
break;
}
default:
break;
}
// If we do not use whole numbers, there may be some artifacts drawn (i.e.
// shadows, notches). This may be an effect of subpixel rendering. It's ok to
// round it here since this is the last calculation (we don't have to worry
// about roundoff error).
return gfx::RectF(gfx::ToRoundedRect(new_bounds));
}
aura::Window* ScopedOverviewTransformWindow::GetOverviewWindow() {
if (IsMinimizedOrTucked()) {
return overview_item_->item_widget()->GetNativeWindow();
}
return window_;
}
void ScopedOverviewTransformWindow::Close() {
if (immediate_close_for_tests) {
CloseWidget();
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ScopedOverviewTransformWindow::CloseWidget,
weak_ptr_factory_.GetWeakPtr()),
base::Milliseconds(kCloseWindowDelayInMilliseconds));
}
bool ScopedOverviewTransformWindow::IsMinimizedOrTucked() const {
return window_util::IsMinimizedOrTucked(window_);
}
void ScopedOverviewTransformWindow::PrepareForOverview() {
Shell::Get()->shadow_controller()->UpdateShadowForWindow(window_);
// Add requests to cache render surface and perform trilinear filtering. The
// requests will be removed in dtor. So the requests will be valid during the
// enter animation and the whole time during overview mode. For the exit
// animation of overview mode, we need to add those requests again.
if (features::IsTrilinearFilteringEnabled()) {
for (auto* window :
window_util::GetVisibleTransientTreeIterator(GetOverviewWindow())) {
cached_and_filtered_layer_observers_.push_back(
std::make_unique<LayerCachingAndFilteringObserver>(window->layer()));
}
}
}
void ScopedOverviewTransformWindow::EnsureVisible() {
original_opacity_ = 1.f;
}
void ScopedOverviewTransformWindow::UpdateOverviewItemFillMode() {
fill_mode_ = GetOverviewItemFillModeForWindow(window_);
}
void ScopedOverviewTransformWindow::UpdateRoundedCorners(bool show) {
// TODO(b/274470528): Keep track of the corner radius animations.
// Hide the corners if minimized, OverviewItemView will handle showing the
// rounded corners on the UI.
if (IsMinimizedOrTucked()) {
DCHECK(!show);
}
ui::Layer* layer = window_->layer();
layer->SetIsFastRoundedCorner(true);
if (!show) {
layer->SetRoundedCornerRadius(gfx::RoundedCornersF());
return;
}
const gfx::RectF contents_bounds_in_screen = GetTransformedBounds();
// Depending on the size of `backdrop_view`, we might not want to round the
// window associated with `layer`.
const bool has_rounding = window_util::ShouldRoundThumbnailWindow(
overview_item_->GetBackDropView(), contents_bounds_in_screen);
const float scale = layer->transform().To2dScale().x();
layer->SetRoundedCornerRadius(
has_rounding ? window_util::GetMiniWindowRoundedCorners(
window(), /*include_header_rounding=*/false, scale)
: gfx::RoundedCornersF(0));
if (!chromeos::features::IsRoundedWindowsEnabled()) {
return;
}
gfx::RectF contents_bounds_in_root(contents_bounds_in_screen);
wm::TranslateRectFromScreen(window_->GetRootWindow(),
&contents_bounds_in_root);
const gfx::RRectF rounded_contents_bounds(
contents_bounds_in_root,
window_util::GetMiniWindowRoundedCorners(
window(), /*include_header_rounding=*/false));
// Synchronizing the rounded corners of a window and its transient hierarchy
// against `rounded_contents_bounds` yields two outcomes:
// * We can apply the specified rounding without the need for a render
// surface.
// * It ensures that the transient windows' corners are correctly rounded,
// ensuring that all four corners of the WindowMiniView appear rounded.
// See b/325635179.
window_tree_synchronizer_->SynchronizeRoundedCorners(
window(), /*consider_curvature=*/false, rounded_contents_bounds,
/*ignore_predicate=*/base::BindRepeating([](aura::Window* window) {
return window->GetProperty(kHideInOverviewKey) ||
window->GetProperty(kExcludeFromTransientTreeTransformKey);
}));
}
void ScopedOverviewTransformWindow::OnTransientChildWindowAdded(
aura::Window* parent,
aura::Window* transient_child) {
if (parent != window_ && !::wm::HasTransientAncestor(parent, window_))
return;
DCHECK(!event_targeting_blocker_map_.contains(transient_child));
event_targeting_blocker_map_[transient_child] =
std::make_unique<aura::ScopedWindowEventTargetingBlocker>(
transient_child);
transient_child->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// Hide transient children which have been specified to be hidden in
// overview mode.
if (transient_child != window_ &&
transient_child->GetProperty(kHideInOverviewKey)) {
AddHiddenTransientWindows({transient_child});
}
// Add this as |aura::WindowObserver| for observing |kHideInOverviewKey|
// property changes.
window_observations_.AddObservation(transient_child);
}
void ScopedOverviewTransformWindow::OnTransientChildWindowRemoved(
aura::Window* parent,
aura::Window* transient_child) {
if (parent != window_ && !::wm::HasTransientAncestor(parent, window_))
return;
ClearWindowProperties(transient_child);
DCHECK(event_targeting_blocker_map_.contains(transient_child));
event_targeting_blocker_map_.erase(transient_child);
if (window_observations_.IsObservingSource(transient_child))
window_observations_.RemoveObservation(transient_child);
}
void ScopedOverviewTransformWindow::OnWindowPropertyChanged(
aura::Window* window,
const void* key,
intptr_t old) {
if (window == window_ && key == chromeos::kWindowStateTypeKey) {
const auto old_window_state = static_cast<chromeos::WindowStateType>(old);
// During the restore process, the synchronizer attempts to restore the
// rounded corners of the window's layer tree to the state it was in just
// before entering overview.
// However, this is not always be desirable. For instance, if an overview
// item is dragged into a snapped state, the synchronizer may hold an
// outdated original state. While the original state was for a
// rounded window, the window is now square in the snapped state.
if (chromeos::ShouldWindowHaveRoundedCorners(window) !=
chromeos::ShouldWindowStateHaveRoundedCorners(old_window_state)) {
window_tree_synchronizer_->ResetCachedLayerInfo();
}
return;
}
if (key != kHideInOverviewKey)
return;
const auto current_value = window->GetProperty(kHideInOverviewKey);
if (current_value == old)
return;
if (current_value) {
AddHiddenTransientWindows({window});
} else {
hidden_transient_children_->RemoveWindow(window, /*show_window=*/true);
}
}
void ScopedOverviewTransformWindow::OnWindowBoundsChanged(
aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
if (window == window_ || is_restoring_) {
return;
}
// Transient window is repositioned. The new position within the
// overview item needs to be recomputed. No need to recompute if the
// transient is invisible. It will get placed properly when it reshows on
// overview end.
if (!window->IsVisible())
return;
overview_item_->SetBounds(overview_item_->target_bounds(),
OVERVIEW_ANIMATION_NONE);
}
void ScopedOverviewTransformWindow::OnWindowDestroying(aura::Window* window) {
DCHECK(window_observations_.IsObservingSource(window));
window_observations_.RemoveObservation(window);
}
// static
void ScopedOverviewTransformWindow::SetImmediateCloseForTests(bool immediate) {
immediate_close_for_tests = immediate;
}
void ScopedOverviewTransformWindow::CloseWidget() {
if (aura::Window* parent_window = wm::GetTransientRoot(window_)) {
window_util::CloseWidgetForWindow(parent_window);
}
}
void ScopedOverviewTransformWindow::AddHiddenTransientWindows(
const std::vector<raw_ptr<aura::Window, VectorExperimental>>&
transient_windows) {
if (!hidden_transient_children_) {
hidden_transient_children_ = std::make_unique<ScopedOverviewHideWindows>(
std::move(transient_windows), /*forced_hidden=*/true);
} else {
for (aura::Window* window : transient_windows) {
hidden_transient_children_->AddWindow(window);
}
}
}
} // namespace ash