#include "ui/wm/core/focus_controller.h"
#include <string_view>
#include "base/auto_reset.h"
#include "base/observer_list.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/focus_change_observer.h"
#include "ui/aura/env.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event.h"
#include "ui/wm/core/focus_rules.h"
#include "ui/wm/core/window_util.h"
#include "ui/wm/public/activation_change_observer.h"
namespace wm {
namespace {
void StackTransientParentsBelowModalWindow(aura::Window* window) {
if (window->GetProperty(aura::client::kModalKey) !=
ui::mojom::ModalType::kWindow) {
return;
}
aura::Window* transient_parent = wm::GetTransientParent(window);
while (transient_parent) {
transient_parent->parent()->StackChildAtTop(transient_parent);
transient_parent = wm::GetTransientParent(transient_parent);
}
}
}
FocusController::FocusController(FocusRules* rules)
: rules_(rules),
focus_follows_cursor_(
base::FeatureList::IsEnabled(features::kFocusFollowsCursor)) {
DCHECK(rules);
}
FocusController::~FocusController() = default;
void FocusController::SetFocusRules(std::unique_ptr<FocusRules> new_rules) {
CHECK(!pending_activation_);
rules_ = std::move(new_rules);
}
void FocusController::AddObserver(ActivationChangeObserver* observer) {
activation_observers_.AddObserver(observer);
}
void FocusController::RemoveObserver(ActivationChangeObserver* observer) {
activation_observers_.RemoveObserver(observer);
}
void FocusController::ActivateWindow(aura::Window* window) {
FocusWindow(window);
}
void FocusController::DeactivateWindow(aura::Window* window) {
if (window) {
if (window != GetActiveWindow())
return;
FocusWindow(rules_->GetNextActivatableWindow(window));
}
}
const aura::Window* FocusController::GetActiveWindow() const {
return active_window_;
}
aura::Window* FocusController::GetActivatableWindow(
aura::Window* window) const {
return rules_->GetActivatableWindow(window);
}
const aura::Window* FocusController::GetToplevelWindow(
const aura::Window* window) const {
return rules_->GetToplevelWindow(window);
}
bool FocusController::CanActivateWindow(const aura::Window* window) const {
return rules_->CanActivateWindow(window);
}
void FocusController::AddObserver(aura::client::FocusChangeObserver* observer) {
focus_observers_.AddObserver(observer);
}
void FocusController::RemoveObserver(
aura::client::FocusChangeObserver* observer) {
focus_observers_.RemoveObserver(observer);
}
void FocusController::FocusWindow(aura::Window* window) {
FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT, window,
false);
}
void FocusController::ResetFocusWithinActiveWindow(aura::Window* window) {
DCHECK(window);
if (!active_window_) {
return;
}
if (!active_window_->Contains(window)) {
return;
}
if (!rules_->CanFocusWindow(window, nullptr)) {
return;
}
SetFocusedWindow(window);
}
aura::Window* FocusController::GetFocusedWindow() {
return focused_window_;
}
void FocusController::OnKeyEvent(ui::KeyEvent* event) {}
void FocusController::OnMouseEvent(ui::MouseEvent* event) {
if ((event->type() == ui::EventType::kMousePressed ||
(event->type() == ui::EventType::kMouseEntered &&
focus_follows_cursor_)) &&
!event->handled()) {
WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()),
event);
}
}
void FocusController::OnScrollEvent(ui::ScrollEvent* event) {}
void FocusController::OnTouchEvent(ui::TouchEvent* event) {}
void FocusController::OnGestureEvent(ui::GestureEvent* event) {
if (event->type() == ui::EventType::kGestureBegin &&
event->details().touch_points() == 1 && !event->handled()) {
WindowFocusedFromInputEvent(static_cast<aura::Window*>(event->target()),
event);
}
}
std::string_view FocusController::GetLogContext() const {
return "FocusController";
}
void FocusController::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
if (!visible)
WindowLostFocusFromDispositionChange(window, window->parent());
}
void FocusController::OnWindowDestroying(aura::Window* window) {
window->ClearProperty(aura::client::kModalKey);
WindowLostFocusFromDispositionChange(window, window->parent());
if (observation_manager_.IsObservingSource(window))
observation_manager_.RemoveObservation(window);
}
void FocusController::OnWindowHierarchyChanging(
const HierarchyChangeParams& params) {
if (params.receiver == active_window_ &&
params.target->Contains(params.receiver) &&
(!params.new_parent ||
aura::client::GetFocusClient(params.new_parent) !=
aura::client::GetFocusClient(params.receiver))) {
WindowLostFocusFromDispositionChange(params.receiver, params.old_parent);
}
}
void FocusController::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
if (params.receiver == focused_window_ &&
params.target->Contains(params.receiver) &&
(!params.new_parent ||
aura::client::GetFocusClient(params.new_parent) !=
aura::client::GetFocusClient(params.receiver))) {
WindowLostFocusFromDispositionChange(params.receiver, params.old_parent);
}
}
void FocusController::FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason reason,
aura::Window* window,
bool no_stacking) {
if (window &&
(window->Contains(focused_window_) || window->Contains(active_window_))) {
if (!no_stacking) {
StackActiveWindow();
}
return;
}
aura::Window* focusable = rules_->GetFocusableWindow(window);
aura::Window* activatable =
focusable ? rules_->GetActivatableWindow(focusable) : nullptr;
if (window && (!focusable || !activatable))
return;
DCHECK((focusable && activatable) || !window);
aura::Window* last_focused_window = focused_window_;
if (!pending_activation_.has_value()) {
aura::WindowTracker focusable_window_tracker;
if (focusable) {
focusable_window_tracker.Add(focusable);
focusable = nullptr;
}
if (!SetActiveWindow(reason, window, activatable, no_stacking))
return;
if (!focusable_window_tracker.windows().empty())
focusable = focusable_window_tracker.Pop();
} else {
DCHECK(!activatable || activatable == pending_activation_.value());
}
if (!updating_focus_) {
aura::Window* const new_active_window = pending_activation_.has_value()
? pending_activation_.value()
: active_window_.get();
const bool activation_changed_focus =
last_focused_window != focused_window_;
if (!activation_changed_focus || !focused_window_) {
if (new_active_window && focusable)
DCHECK(new_active_window->Contains(focusable));
SetFocusedWindow(focusable);
}
if (new_active_window && focused_window_)
DCHECK(new_active_window->Contains(focused_window_));
}
}
void FocusController::SetFocusedWindow(aura::Window* window) {
if (updating_focus_ || window == focused_window_)
return;
DCHECK(rules_->CanFocusWindow(window, nullptr));
if (window) {
DCHECK_EQ(window, rules_->GetFocusableWindow(window));
}
base::AutoReset<bool> updating_focus(&updating_focus_, true);
aura::Window* lost_focus = focused_window_;
aura::WindowTracker window_tracker;
if (lost_focus)
window_tracker.Add(lost_focus);
if (focused_window_ &&
observation_manager_.IsObservingSource(focused_window_.get()) &&
focused_window_ != active_window_) {
observation_manager_.RemoveObservation(focused_window_.get());
}
focused_window_ = window;
if (focused_window_ &&
!observation_manager_.IsObservingSource(focused_window_.get()))
observation_manager_.AddObservation(focused_window_.get());
for (auto& observer : focus_observers_) {
observer.OnWindowFocused(
focused_window_,
window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
}
if (window_tracker.Contains(lost_focus)) {
aura::client::FocusChangeObserver* observer =
aura::client::GetFocusChangeObserver(lost_focus);
if (observer)
observer->OnWindowFocused(focused_window_, lost_focus);
}
aura::client::FocusChangeObserver* observer =
aura::client::GetFocusChangeObserver(focused_window_);
if (observer) {
observer->OnWindowFocused(
focused_window_,
window_tracker.Contains(lost_focus) ? lost_focus : nullptr);
}
}
#define MAYBE_ACTIVATION_INTERRUPTED() \
if (!pending_activation_) \
return false
bool FocusController::SetActiveWindow(
ActivationChangeObserver::ActivationReason reason,
aura::Window* requested_window,
aura::Window* window,
bool no_stacking) {
if (pending_activation_)
return false;
if (window == active_window_) {
if (requested_window) {
for (auto& observer : activation_observers_)
observer.OnAttemptToReactivateWindow(requested_window, active_window_);
}
return true;
}
DCHECK(rules_->CanActivateWindow(window));
if (window)
DCHECK_EQ(window, rules_->GetActivatableWindow(window));
base::AutoReset<std::optional<aura::Window*>> updating_activation(
&pending_activation_, std::make_optional(window));
aura::Window* lost_activation = active_window_;
aura::WindowTracker window_tracker;
if (lost_activation)
window_tracker.Add(lost_activation);
if (window && !observation_manager_.IsObservingSource(window))
observation_manager_.AddObservation(window);
for (auto& observer : activation_observers_) {
observer.OnWindowActivating(reason, window, active_window_);
MAYBE_ACTIVATION_INTERRUPTED();
}
if (active_window_ &&
observation_manager_.IsObservingSource(active_window_.get()) &&
focused_window_ != active_window_) {
observation_manager_.RemoveObservation(active_window_.get());
}
active_window_ = window;
if (active_window_ && !no_stacking)
StackActiveWindow();
MAYBE_ACTIVATION_INTERRUPTED();
ActivationChangeObserver* observer = nullptr;
if (window_tracker.Contains(lost_activation)) {
observer = GetActivationChangeObserver(lost_activation);
if (observer)
observer->OnWindowActivated(reason, active_window_, lost_activation);
}
MAYBE_ACTIVATION_INTERRUPTED();
observer = GetActivationChangeObserver(active_window_);
if (observer) {
observer->OnWindowActivated(
reason, active_window_,
window_tracker.Contains(lost_activation) ? lost_activation : nullptr);
}
MAYBE_ACTIVATION_INTERRUPTED();
for (auto& activation_observer : activation_observers_) {
activation_observer.OnWindowActivated(
reason, active_window_,
window_tracker.Contains(lost_activation) ? lost_activation : nullptr);
MAYBE_ACTIVATION_INTERRUPTED();
}
return true;
}
void FocusController::StackActiveWindow() {
if (!active_window_) {
return;
}
StackTransientParentsBelowModalWindow(active_window_);
bool needs_restack = false;
bool has_found_window = false;
for (const auto child_window : active_window_->parent()->children()) {
if (child_window == active_window_) {
has_found_window = true;
continue;
}
if (has_found_window !=
HasTransientAncestor(child_window, active_window_)) {
needs_restack = true;
break;
}
}
if (needs_restack) {
active_window_->parent()->StackChildAtTop(active_window_);
}
}
void FocusController::WindowLostFocusFromDispositionChange(aura::Window* window,
aura::Window* next) {
const bool is_active_window_losing_focus = window == active_window_;
const bool is_pending_window_losing_focus =
pending_activation_ && (window == pending_activation_.value());
if (is_active_window_losing_focus || is_pending_window_losing_focus) {
if (pending_activation_) {
if (is_pending_window_losing_focus) {
pending_activation_.reset();
} else if (is_active_window_losing_focus) {
active_window_ = nullptr;
SetFocusedWindow(nullptr);
return;
}
}
aura::Window* next_activatable = rules_->GetNextActivatableWindow(window);
if (!SetActiveWindow(ActivationChangeObserver::ActivationReason::
WINDOW_DISPOSITION_CHANGED,
nullptr, next_activatable,
false)) {
return;
}
if (window == focused_window_ || !active_window_ ||
!active_window_->Contains(focused_window_)) {
SetFocusedWindow(next_activatable);
}
} else if (window->Contains(focused_window_)) {
if (pending_activation_) {
SetFocusedWindow(nullptr);
} else {
SetFocusedWindow(rules_->GetFocusableWindow(next));
}
}
}
void FocusController::WindowFocusedFromInputEvent(aura::Window* window,
const ui::Event* event) {
bool is_mouse_entered_event = event->type() == ui::EventType::kMouseEntered;
if (is_mouse_entered_event && active_window_ &&
active_window_->Contains(window)) {
return;
}
if (rules_->CanFocusWindow(GetToplevelWindow(window), event)) {
FocusAndActivateWindow(
ActivationChangeObserver::ActivationReason::INPUT_EVENT, window,
is_mouse_entered_event);
}
}
}