#include "ui/wm/core/transient_window_manager.h"
#include <algorithm>
#include <functional>
#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/observer_list.h"
#include "ui/aura/client/transient_window_client.h"
#include "ui/aura/client/transient_window_client_observer.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/class_property.h"
#include "ui/wm/core/transient_window_controller.h"
#include "ui/wm/core/transient_window_observer.h"
#include "ui/wm/core/transient_window_stacking_client.h"
#include "ui/wm/core/window_util.h"
using aura::Window;
DEFINE_UI_CLASS_PROPERTY_TYPE(::wm::TransientWindowManager*)
namespace wm {
namespace {
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(TransientWindowManager, kPropertyKey)
bool HasTransientCycles(const aura::Window* window) {
std::set<const aura::Window*> visited;
while (window) {
if (!visited.emplace(window).second) {
return true;
}
auto* transient_window_manager =
TransientWindowManager::GetIfExists(window);
window = transient_window_manager
? transient_window_manager->transient_parent()
: nullptr;
}
return false;
}
}
TransientWindowManager::~TransientWindowManager() = default;
TransientWindowManager* TransientWindowManager::GetOrCreate(Window* window) {
if (auto* manager = window->GetProperty(kPropertyKey))
return manager;
return window->SetProperty(
kPropertyKey, base::WrapUnique(new TransientWindowManager(window)));
}
const TransientWindowManager* TransientWindowManager::GetIfExists(
const Window* window) {
return window->GetProperty(kPropertyKey);
}
void TransientWindowManager::AddObserver(TransientWindowObserver* observer) {
observers_.AddObserver(observer);
}
void TransientWindowManager::RemoveObserver(TransientWindowObserver* observer) {
observers_.RemoveObserver(observer);
}
void TransientWindowManager::AddTransientChild(Window* child) {
DCHECK(TransientWindowStackingClient::instance_);
DCHECK_NE(this->window_, child);
TransientWindowManager* child_manager = GetOrCreate(child);
if (child_manager->transient_parent_)
GetOrCreate(child_manager->transient_parent_)->RemoveTransientChild(child);
DCHECK(!base::Contains(transient_children_, child));
transient_children_.push_back(child);
child_manager->transient_parent_ = window_;
CHECK(!HasTransientCycles(window_));
TransientWindowController::Get()->observers_.Notify(
&aura::client::TransientWindowClientObserver::OnTransientChildWindowAdded,
window_, child);
if (child->parent() == window_->parent())
RestackTransientDescendants();
observers_.Notify(&TransientWindowObserver::OnTransientChildAdded, window_,
child);
child_manager->observers_.Notify(
&TransientWindowObserver::OnTransientParentChanged, window_);
}
void TransientWindowManager::RemoveTransientChild(Window* child) {
auto i = std::ranges::find(transient_children_, child);
CHECK(i != transient_children_.end());
transient_children_.erase(i);
TransientWindowManager* child_manager = GetOrCreate(child);
DCHECK_EQ(window_, child_manager->transient_parent_);
child_manager->transient_parent_ = nullptr;
TransientWindowController::Get()->observers_.Notify(
&aura::client::TransientWindowClientObserver::
OnTransientChildWindowRemoved,
window_, child);
if (window_->parent() == child->parent())
RestackTransientDescendants();
observers_.Notify(&TransientWindowObserver::OnTransientChildRemoved, window_,
child);
child_manager->observers_.Notify(
&TransientWindowObserver::OnTransientParentChanged, nullptr);
}
bool TransientWindowManager::IsStackingTransient(
const aura::Window* target) const {
return stacking_target_ == target;
}
TransientWindowManager::TransientWindowManager(Window* window)
: window_(window),
transient_parent_(nullptr),
stacking_target_(nullptr),
parent_controls_visibility_(false),
parent_controls_lifetime_(true),
show_on_parent_visible_(false),
ignore_visibility_changed_event_(false) {
window_->AddObserver(this);
}
void TransientWindowManager::RestackTransientDescendants() {
if (pause_transient_descendants_restacking_)
return;
Window* parent = window_->parent();
if (!parent)
return;
aura::WindowTracker tracker(
Window::Windows(parent->children().rbegin(), parent->children().rend()));
while (!tracker.windows().empty()) {
auto* child_window = tracker.Pop();
if (child_window != window_ &&
HasTransientAncestor(child_window, window_)) {
TransientWindowManager* descendant_manager = GetOrCreate(child_window);
base::AutoReset<raw_ptr<Window>> resetter(
&descendant_manager->stacking_target_, window_);
parent->StackChildAbove(child_window, window_);
}
}
}
void TransientWindowManager::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
if (params.target != window_)
return;
bool should_restack = false;
aura::Window* new_parent = params.new_parent;
aura::Window* old_parent = params.old_parent;
if (new_parent) {
base::AutoReset<bool> reset(&pause_transient_descendants_restacking_, true);
for (aura::Window* transient_child : transient_children_) {
if (transient_child->parent() == old_parent) {
new_parent->AddChild(transient_child);
should_restack = true;
}
}
}
if (should_restack)
RestackTransientDescendants();
if (transient_parent_ && transient_parent_->parent() == new_parent) {
TransientWindowManager* transient_parent_manager =
GetOrCreate(transient_parent_);
transient_parent_manager->RestackTransientDescendants();
}
}
void TransientWindowManager::UpdateTransientChildVisibility(
bool parent_visible) {
base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
if (!parent_visible) {
show_on_parent_visible_ = window_->TargetVisibility();
window_->Hide();
} else {
if (show_on_parent_visible_ && parent_controls_visibility_)
window_->Show();
show_on_parent_visible_ = false;
}
}
void TransientWindowManager::OnWindowVisibilityChanged(Window* window,
bool visible) {
if (window_ != window)
return;
aura::WindowTracker tracker(transient_children_);
while (!tracker.windows().empty())
GetOrCreate(tracker.Pop())->UpdateTransientChildVisibility(visible);
if (ignore_visibility_changed_event_ ||
!transient_parent_ || !parent_controls_visibility_) {
return;
}
if (!transient_parent_->TargetVisibility() && visible) {
base::AutoReset<bool> reset(&ignore_visibility_changed_event_, true);
show_on_parent_visible_ = true;
window_->Hide();
} else if (!visible) {
DCHECK(!show_on_parent_visible_);
}
}
void TransientWindowManager::OnWindowStackingChanged(Window* window) {
DCHECK_EQ(window_, window);
const TransientWindowManager* transient_manager = GetIfExists(window);
if (transient_manager && transient_manager->stacking_target_) {
auto window_i = std::ranges::find(window->parent()->children(), window);
DCHECK(window_i != window->parent()->children().end());
if (window_i != window->parent()->children().begin() &&
(*(window_i - 1) == transient_manager->stacking_target_))
return;
}
RestackTransientDescendants();
}
void TransientWindowManager::OnWindowDestroying(Window* window) {
if (transient_parent_) {
TransientWindowManager::GetOrCreate(transient_parent_)
->RemoveTransientChild(window_);
}
aura::WindowTracker tracker(
Window::Windows(transient_children_.begin(), transient_children_.end()));
while (!tracker.windows().empty()) {
aura::Window* child = tracker.Pop();
auto* child_transient_manager = TransientWindowManager::GetIfExists(child);
CHECK(child_transient_manager);
if (child_transient_manager->parent_controls_lifetime_) {
delete child;
} else {
RemoveTransientChild(child);
}
}
CHECK(tracker.windows().empty());
}
}