910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#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 {

// When a modal window is activated, we bring its entire transient parent chain
// to the front. This function must be called before the modal transient is
// stacked at the top to ensure correct stacking order.
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);
  }
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// FocusController, public:

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);
}

////////////////////////////////////////////////////////////////////////////////
// FocusController, ActivationClient implementation:

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) {
    // FocusController implements deactivation by way of activating another
    // window. Don't deactivate |window| if it's not active to avoid attempting
    // to activate another 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);
}

////////////////////////////////////////////////////////////////////////////////
// FocusController, aura::client::FocusClient implementation:

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,
      /*no_stacking=*/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_;
}

////////////////////////////////////////////////////////////////////////////////
// FocusController, ui::EventHandler implementation:
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";
}

////////////////////////////////////////////////////////////////////////////////
// FocusController, aura::WindowObserver implementation:

void FocusController::OnWindowVisibilityChanged(aura::Window* window,
                                                bool visible) {
  if (!visible)
    WindowLostFocusFromDispositionChange(window, window->parent());
}

void FocusController::OnWindowDestroying(aura::Window* window) {
  // A window's modality state will interfere with focus restoration during its
  // destruction.
  window->ClearProperty(aura::client::kModalKey);
  WindowLostFocusFromDispositionChange(window, window->parent());

  // We may have already stopped observing |window| if `SetActiveWindow()` was
  // called inside `WindowLostFocusFromDispositionChange()`.
  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);
  }
}

////////////////////////////////////////////////////////////////////////////////
// FocusController, private:

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;
  }

  // Focusing a window also activates its containing activatable window. Note
  // that the rules could redirect activation and/or focus.
  aura::Window* focusable = rules_->GetFocusableWindow(window);
  aura::Window* activatable =
      focusable ? rules_->GetActivatableWindow(focusable) : nullptr;

  // We need valid focusable/activatable windows in the event we're not clearing
  // focus. "Clearing focus" is inferred by whether or not |window| passed to
  // this function is non-NULL.
  if (window && (!focusable || !activatable))
    return;
  DCHECK((focusable && activatable) || !window);

  // Activation change observers may change the focused window. If this happens
  // we must not adjust the focus below since this will clobber that change.
  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 {
    // Only allow the focused window to change, *not* the active window if
    // called reentrantly.
    DCHECK(!activatable || activatable == pending_activation_.value());
  }

  // If the window's ActivationChangeObserver shifted focus to a valid window,
  // we don't want to focus the window we thought would be focused by default.
  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_;

  // Allow for the window losing focus to be deleted during dispatch. If it is
  // deleted pass NULL to observers instead of a deleted 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);
  }
}

// Defines a macro that is meant to be called from SetActiveWindow(), which
// checks whether the activation was interrupted by checking whether
// |pending_activation_| has a value or not. In this case, it early-outs from
// the SetActiveWindow() stack.
// clang-format off
#define MAYBE_ACTIVATION_INTERRUPTED() \
  if (!pending_activation_)            \
    return false
// clang-format on

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_;
  // Allow for the window losing activation to be deleted during dispatch. If
  // it is deleted pass NULL to observers instead of a deleted window.
  aura::WindowTracker window_tracker;
  if (lost_activation)
    window_tracker.Add(lost_activation);

  // Start observing the window gaining activation at this point since it maybe
  // destroyed at an early stage, e.g. the activating phase.
  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_);

  // |active_window_| must be stacked below its transient siblings and above its
  // non-transient siblings. Restacking is only needed if that is not already
  // true.
  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) {
  // TODO(beng): See if this function can be replaced by a call to
  //             FocusWindow().
  // Activation adjustments are handled first in the event of a disposition
  // changed. If an activation change is necessary, focus is reset as part of
  // that process so there's no point in updating focus independently.
  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_) {
      // We're in the middle of an on-going activation. We need to determine
      // whether we need to abort this activation. This happens when the window
      // gaining activation is destroyed at any point of the activation process.
      if (is_pending_window_losing_focus) {
        // Abort this on-going activation. The below call to SetActiveWindow()
        // will attempt activating the next activatable window.
        pending_activation_.reset();
      } else if (is_active_window_losing_focus) {
        // The window losing activation may have been destroyed before the
        // window gaining active is set as the active window. We need to clear
        // the active and focused windows temporarily, since querying the active
        // window now should not return a dangling pointer.
        active_window_ = nullptr;
        SetFocusedWindow(nullptr);

        // We should continue the on-going activation and leave
        // |pending_activation_| unchanged.
        return;
      }
    }

    aura::Window* next_activatable = rules_->GetNextActivatableWindow(window);
    if (!SetActiveWindow(ActivationChangeObserver::ActivationReason::
                             WINDOW_DISPOSITION_CHANGED,
                         nullptr, next_activatable,
                         /*no_stacking=*/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_) {
      // We're in the process of updating activation, most likely
      // ActivationChangeObserver::OnWindowActivated() is changing something
      // about the focused window (visibility perhaps). Temporarily set the
      // focus to null, we'll set it to something better when activation
      // completes.
      SetFocusedWindow(nullptr);
    } else {
      // Active window isn't changing, but focused window might be.
      SetFocusedWindow(rules_->GetFocusableWindow(next));
    }
  }
}

void FocusController::WindowFocusedFromInputEvent(aura::Window* window,
                                                  const ui::Event* event) {
  // For focus follows cursor: avoid activating when `window` is a child of the
  // currently active window.
  bool is_mouse_entered_event = event->type() == ui::EventType::kMouseEntered;
  if (is_mouse_entered_event && active_window_ &&
      active_window_->Contains(window)) {
    return;
  }

  // Only focus |window| if it or any of its parents can be focused. Otherwise
  // FocusWindow() will focus the topmost window, which may not be the
  // currently focused one.
  if (rules_->CanFocusWindow(GetToplevelWindow(window), event)) {
    FocusAndActivateWindow(
        ActivationChangeObserver::ActivationReason::INPUT_EVENT, window,
        /*no_stacking=*/is_mouse_entered_event);
  }
}

}  // namespace wm