#include "chromeos/ui/frame/immersive/immersive_focus_watcher.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
#include "ui/aura/client/transient_window_client.h"
#include "ui/aura/window.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_client.h"
namespace chromeos {
namespace {
views::BubbleDialogDelegate* AsBubbleDialogDelegate(
aura::Window* maybe_bubble) {
if (!maybe_bubble)
return nullptr;
views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble);
if (!widget)
return nullptr;
return widget->widget_delegate()->AsBubbleDialogDelegate();
}
views::View* GetAnchorView(aura::Window* maybe_bubble) {
views::BubbleDialogDelegate* bubble_dialog =
AsBubbleDialogDelegate(maybe_bubble);
return bubble_dialog ? bubble_dialog->GetAnchorView() : nullptr;
}
bool IsWindowTransientChildOf(aura::Window* maybe_transient,
aura::Window* toplevel) {
if (!maybe_transient || !toplevel)
return false;
aura::client::TransientWindowClient* transient_window_client =
aura::client::GetTransientWindowClient();
for (aura::Window* window = maybe_transient; window;
window = transient_window_client->GetTransientParent(window)) {
if (window == toplevel)
return true;
}
return false;
}
}
class ImmersiveFocusWatcher::BubbleObserver : public aura::WindowObserver {
public:
explicit BubbleObserver(ImmersiveFullscreenController* controller);
BubbleObserver(const BubbleObserver&) = delete;
BubbleObserver& operator=(const BubbleObserver&) = delete;
~BubbleObserver() override;
void StartObserving(aura::Window* bubble);
void StopObserving(aura::Window* bubble);
private:
void UpdateRevealedLock();
void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
void OnWindowDestroying(aura::Window* window) override;
raw_ptr<ImmersiveFullscreenController> controller_;
std::set<raw_ptr<aura::Window, SetExperimental>> bubbles_;
std::unique_ptr<ImmersiveRevealedLock> revealed_lock_;
};
ImmersiveFocusWatcher::BubbleObserver::BubbleObserver(
ImmersiveFullscreenController* controller)
: controller_(controller) {}
ImmersiveFocusWatcher::BubbleObserver::~BubbleObserver() {
for (aura::Window* bubble : bubbles_)
bubble->RemoveObserver(this);
}
void ImmersiveFocusWatcher::BubbleObserver::StartObserving(
aura::Window* bubble) {
if (bubbles_.insert(bubble).second) {
bubble->AddObserver(this);
UpdateRevealedLock();
}
}
void ImmersiveFocusWatcher::BubbleObserver::StopObserving(
aura::Window* bubble) {
if (bubbles_.erase(bubble)) {
bubble->RemoveObserver(this);
UpdateRevealedLock();
}
}
void ImmersiveFocusWatcher::BubbleObserver::UpdateRevealedLock() {
bool has_visible_bubble = false;
for (aura::Window* bubble : bubbles_) {
if (bubble->IsVisible()) {
has_visible_bubble = true;
break;
}
}
bool was_revealed = controller_->IsRevealed();
if (has_visible_bubble) {
if (!revealed_lock_.get()) {
revealed_lock_.reset(controller_->GetRevealedLock(
ImmersiveFullscreenController::ANIMATE_REVEAL_NO));
}
} else {
revealed_lock_.reset();
}
if (!was_revealed && revealed_lock_.get()) {
for (aura::Window* bubble : bubbles_)
AsBubbleDialogDelegate(bubble)->OnAnchorBoundsChanged();
}
}
void ImmersiveFocusWatcher::BubbleObserver::OnWindowVisibilityChanged(
aura::Window*,
bool visible) {
UpdateRevealedLock();
}
void ImmersiveFocusWatcher::BubbleObserver::OnWindowDestroying(
aura::Window* window) {
StopObserving(window);
}
ImmersiveFocusWatcher::ImmersiveFocusWatcher(
ImmersiveFullscreenController* controller)
: immersive_fullscreen_controller_(controller) {
GetWidget()->GetFocusManager()->AddFocusChangeListener(this);
aura::client::GetTransientWindowClient()->AddObserver(this);
::wm::GetActivationClient(GetWidgetWindow()->GetRootWindow())
->AddObserver(this);
RecreateBubbleObserver();
}
ImmersiveFocusWatcher::~ImmersiveFocusWatcher() {
aura::client::GetTransientWindowClient()->RemoveObserver(this);
GetWidget()->GetFocusManager()->RemoveFocusChangeListener(this);
auto* activation_client =
::wm::GetActivationClient(GetWidgetWindow()->GetRootWindow());
if (activation_client)
activation_client->RemoveObserver(this);
}
void ImmersiveFocusWatcher::UpdateFocusRevealedLock() {
views::Widget* widget = GetWidget();
views::View* top_container =
immersive_fullscreen_controller_->top_container();
bool hold_lock = false;
if (widget->IsActive()) {
views::View* focused_view = widget->GetFocusManager()->GetFocusedView();
if (top_container->Contains(focused_view))
hold_lock = true;
} else {
aura::Window* native_window = GetWidgetWindow();
aura::Window* active_window =
::wm::GetActivationClient(native_window->GetRootWindow())
->GetActiveWindow();
if (GetAnchorView(active_window)) {
} else {
if (immersive_fullscreen_controller_->IsRevealed() &&
IsWindowTransientChildOf(active_window, native_window)) {
hold_lock = true;
}
}
}
if (hold_lock) {
if (!lock_.get()) {
lock_.reset(immersive_fullscreen_controller_->GetRevealedLock(
ImmersiveFullscreenController::ANIMATE_REVEAL_YES));
}
} else {
lock_.reset();
}
}
void ImmersiveFocusWatcher::ReleaseLock() {
lock_.reset();
}
views::Widget* ImmersiveFocusWatcher::GetWidget() {
return immersive_fullscreen_controller_->widget();
}
aura::Window* ImmersiveFocusWatcher::GetWidgetWindow() {
return GetWidget()->GetNativeWindow();
}
void ImmersiveFocusWatcher::RecreateBubbleObserver() {
bubble_observer_ =
std::make_unique<BubbleObserver>(immersive_fullscreen_controller_);
const std::vector<raw_ptr<aura::Window, VectorExperimental>>
transient_children =
aura::client::GetTransientWindowClient()->GetTransientChildren(
GetWidgetWindow());
for (const auto& transient_child : transient_children) {
views::View* anchor_view = GetAnchorView(transient_child);
if (anchor_view &&
immersive_fullscreen_controller_->top_container()->Contains(
anchor_view)) {
bubble_observer_->StartObserving(transient_child);
}
}
}
void ImmersiveFocusWatcher::OnDidChangeFocus(views::View* focused_before,
views::View* focused_now) {
UpdateFocusRevealedLock();
}
void ImmersiveFocusWatcher::OnWindowActivated(
::wm::ActivationChangeObserver::ActivationReason reason,
aura::Window* gaining_active,
aura::Window* losing_active) {
UpdateFocusRevealedLock();
}
void ImmersiveFocusWatcher::OnTransientChildWindowAdded(
aura::Window* window,
aura::Window* transient) {
views::View* anchor = GetAnchorView(transient);
if (anchor &&
immersive_fullscreen_controller_->top_container()->Contains(anchor)) {
bubble_observer_->StartObserving(transient);
}
}
void ImmersiveFocusWatcher::OnTransientChildWindowRemoved(
aura::Window* window,
aura::Window* transient) {
bubble_observer_->StopObserving(transient);
}
}