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

#include "extensions/shell/browser/shell_desktop_controller_aura.h"

#include <algorithm>
#include <memory>
#include <string>

#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "build/chromeos_buildflags.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/shell/browser/shell_app_window_client.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/cursor_shape_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/native_ui_types.h"
#include "ui/wm/core/base_focus_rules.h"
#include "ui/wm/core/compound_event_filter.h"
#include "ui/wm/core/cursor_loader.h"
#include "ui/wm/core/cursor_manager.h"
#include "ui/wm/core/focus_controller.h"
#include "ui/wm/core/native_cursor_manager.h"
#include "ui/wm/core/native_cursor_manager_delegate.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "base/command_line.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "extensions/shell/browser/shell_screen.h"
#include "extensions/shell/common/switches.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/user_activity/user_activity_detector.h"
#include "ui/chromeos/user_activity_power_manager_notifier.h"
#include "ui/display/types/display_mode.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/native_display_delegate.h"
#include "ui/ozone/public/ozone_platform.h"  // nogncheck
#else
#include "ui/views/widget/desktop_aura/desktop_screen.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace extensions {

namespace {

// A class that bridges the gap between CursorManager and Aura. It borrows
// heavily from NativeCursorManagerAsh.
class ShellNativeCursorManager : public wm::NativeCursorManager {
 public:
  explicit ShellNativeCursorManager(
      ShellDesktopControllerAura* desktop_controller)
      : desktop_controller_(desktop_controller) {
    aura::client::SetCursorShapeClient(&cursor_loader_);
  }

  ShellNativeCursorManager(const ShellNativeCursorManager&) = delete;
  ShellNativeCursorManager& operator=(const ShellNativeCursorManager&) = delete;

  ~ShellNativeCursorManager() override {
    aura::client::SetCursorShapeClient(nullptr);
  }

  // wm::NativeCursorManager overrides.
  void SetDisplay(const display::Display& display,
                  wm::NativeCursorManagerDelegate* delegate) override {
    if (cursor_loader_.SetDisplay(display)) {
      SetCursor(delegate->GetCursor(), delegate);
    }
  }

  void SetCursor(gfx::NativeCursor cursor,
                 wm::NativeCursorManagerDelegate* delegate) override {
    cursor_loader_.SetPlatformCursor(&cursor);
    delegate->CommitCursor(cursor);

    if (delegate->IsCursorVisible())
      SetCursorOnAllRootWindows(cursor);
  }

  void SetVisibility(bool visible,
                     wm::NativeCursorManagerDelegate* delegate) override {
    delegate->CommitVisibility(visible);

    if (visible) {
      SetCursor(delegate->GetCursor(), delegate);
    } else {
      gfx::NativeCursor invisible_cursor(ui::mojom::CursorType::kNone);
      cursor_loader_.SetPlatformCursor(&invisible_cursor);
      SetCursorOnAllRootWindows(invisible_cursor);
    }
  }

  void SetCursorSize(ui::CursorSize cursor_size,
                     wm::NativeCursorManagerDelegate* delegate) override {
    cursor_loader_.SetSize(cursor_size);
    delegate->CommitCursorSize(cursor_size);
    if (delegate->IsCursorVisible())
      SetCursor(delegate->GetCursor(), delegate);
  }

  void SetLargeCursorSizeInDip(
      int large_cursor_size_in_dip,
      ::wm::NativeCursorManagerDelegate* delegate) override {
    cursor_loader_.SetLargeCursorSizeInDip(large_cursor_size_in_dip);
    delegate->CommitLargeCursorSizeInDip(large_cursor_size_in_dip);
    if (delegate->IsCursorVisible()) {
      SetCursor(delegate->GetCursor(), delegate);
    }
  }

  void SetMouseEventsEnabled(
      bool enabled,
      wm::NativeCursorManagerDelegate* delegate) override {
    delegate->CommitMouseEventsEnabled(enabled);
    SetVisibility(delegate->IsCursorVisible(), delegate);
  }

  void SetCursorColor(SkColor color,
                      wm::NativeCursorManagerDelegate* delegate) override {
    cursor_loader_.SetColor(color);
    delegate->CommitCursorColor(color);
    if (delegate->IsCursorVisible()) {
      SetCursor(delegate->GetCursor(), delegate);
    }
  }

 private:
  // Sets |cursor| as the active cursor within Aura.
  void SetCursorOnAllRootWindows(gfx::NativeCursor cursor) {
    for (aura::Window* window : desktop_controller_->GetAllRootWindows()) {
      window->GetHost()->SetCursor(cursor);
    }
  }

  raw_ptr<ShellDesktopControllerAura> desktop_controller_;  // Not owned.

  wm::CursorLoader cursor_loader_{/*use_platform_cursors=*/false};
};

class AppsFocusRules : public wm::BaseFocusRules {
 public:
  AppsFocusRules() {}

  AppsFocusRules(const AppsFocusRules&) = delete;
  AppsFocusRules& operator=(const AppsFocusRules&) = delete;

  ~AppsFocusRules() override {}

  bool SupportsChildActivation(const aura::Window* window) const override {
    return true;
  }
};

}  // namespace

ShellDesktopControllerAura::ShellDesktopControllerAura(
    content::BrowserContext* browser_context)
    : browser_context_(browser_context),
      app_window_client_(new ShellAppWindowClient) {
  extensions::AppWindowClient::Set(app_window_client_.get());

#if BUILDFLAG(IS_CHROMEOS)
  chromeos::PowerManagerClient::Get()->AddObserver(this);
  display_configurator_ = std::make_unique<display::DisplayConfigurator>();
  display_configurator_->Init(
      ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate(), false);
  display_configurator_->ForceInitialConfigure();
  display_configurator_->AddObserver(this);
#endif

  InitWindowManager();
}

ShellDesktopControllerAura::~ShellDesktopControllerAura() {
  TearDownWindowManager();
#if BUILDFLAG(IS_CHROMEOS)
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
#endif
  extensions::AppWindowClient::Set(nullptr);
}

void ShellDesktopControllerAura::PreMainMessageLoopRun() {
  KeepAliveRegistry::GetInstance()->AddObserver(this);
}

void ShellDesktopControllerAura::WillRunMainMessageLoop(
    std::unique_ptr<base::RunLoop>& run_loop) {
  quit_when_idle_closure_ = run_loop->QuitWhenIdleClosure();
}

void ShellDesktopControllerAura::PostMainMessageLoopRun() {
  KeepAliveRegistry::GetInstance()->SetIsShuttingDown(true);
  KeepAliveRegistry::GetInstance()->RemoveObserver(this);
}

void ShellDesktopControllerAura::AddAppWindow(AppWindow* app_window,
                                              gfx::NativeWindow window) {
  // Find the closest display to the specified bounds.
  const display::Display& display =
      display::Screen::Get()->GetDisplayMatching(window->GetBoundsInScreen());

  // Create a RootWindowController for the display if necessary.
  if (root_window_controllers_.count(display.id()) == 0) {
    root_window_controllers_[display.id()] =
        CreateRootWindowControllerForDisplay(display);
  }
  root_window_controllers_[display.id()]->AddAppWindow(app_window, window);
}

void ShellDesktopControllerAura::CloseAppWindows() {
  for (auto& pair : root_window_controllers_)
    pair.second->CloseAppWindows();
}

void ShellDesktopControllerAura::CloseRootWindowController(
    RootWindowController* root_window_controller) {
  const auto it = std::ranges::find(
      root_window_controllers_, root_window_controller,
      [](const auto& candidate_pair) { return candidate_pair.second.get(); });
  CHECK(it != root_window_controllers_.end());
  TearDownRootWindowController(it->second.get());
  root_window_controllers_.erase(it);

  MaybeQuit();
}

#if BUILDFLAG(IS_CHROMEOS)
void ShellDesktopControllerAura::PowerButtonEventReceived(
    bool down,
    base::TimeTicks timestamp) {
  if (down) {
    chromeos::PowerManagerClient::Get()->RequestShutdown(
        power_manager::REQUEST_SHUTDOWN_FOR_USER, "AppShell power button");
  }
}

void ShellDesktopControllerAura::OnDisplayConfigurationChanged(
    const display::DisplayConfigurator::DisplayStateList& displays) {
  for (const display::DisplaySnapshot* display_mode : displays) {
    if (!display_mode->current_mode())
      continue;
    auto it = root_window_controllers_.find(display_mode->display_id());
    if (it != root_window_controllers_.end())
      it->second->UpdateSize(display_mode->current_mode()->size());
  }
}
#endif

ui::EventDispatchDetails ShellDesktopControllerAura::DispatchKeyEventPostIME(
    ui::KeyEvent* key_event) {
  if (key_event->target()) {
    aura::WindowTreeHost* host = static_cast<aura::Window*>(key_event->target())
                                     ->GetRootWindow()
                                     ->GetHost();
    return host->DispatchKeyEventPostIME(key_event);
  }

  // Send the key event to the focused window.
  aura::Window* active_window =
      const_cast<aura::Window*>(focus_controller_->GetActiveWindow());
  if (active_window) {
    return active_window->GetRootWindow()->GetHost()->DispatchKeyEventPostIME(
        key_event);
  }

  return GetPrimaryHost()->DispatchKeyEventPostIME(key_event);
}

void ShellDesktopControllerAura::OnKeepAliveStateChanged(
    bool is_keeping_alive) {
  if (!is_keeping_alive)
    MaybeQuit();
}

void ShellDesktopControllerAura::OnKeepAliveRestartStateChanged(
    bool can_restart) {}

aura::WindowTreeHost* ShellDesktopControllerAura::GetPrimaryHost() {
  if (root_window_controllers_.empty())
    return nullptr;

  const display::Display& display = display::Screen::Get()->GetPrimaryDisplay();
  if (root_window_controllers_.count(display.id()) == 1)
    return root_window_controllers_[display.id()]->host();

  // Fall back to an existing host.
  return root_window_controllers_.begin()->second->host();
}

aura::Window::Windows ShellDesktopControllerAura::GetAllRootWindows() {
  aura::Window::Windows windows;
  for (auto& pair : root_window_controllers_)
    windows.push_back(pair.second->host()->window());
  return windows;
}

void ShellDesktopControllerAura::SetWindowBoundsInScreen(
    AppWindow* app_window,
    const gfx::Rect& bounds) {
  display::Display display = display::Screen::Get()->GetDisplayMatching(bounds);

  // Create a RootWindowController for the display if necessary.
  if (root_window_controllers_.count(display.id()) == 0) {
    root_window_controllers_[display.id()] =
        CreateRootWindowControllerForDisplay(display);
  }

  // Check if the window is parented to a different RootWindowController.
  if (app_window->GetNativeWindow()->GetRootWindow() !=
      root_window_controllers_[display.id()]->host()->window()) {
    // Move the window to the appropriate RootWindowController for the display.
    for (const auto& it : root_window_controllers_) {
      if (it.second->host()->window() ==
          app_window->GetNativeWindow()->GetRootWindow()) {
        it.second->RemoveAppWindow(app_window);
        break;
      }
    }
    root_window_controllers_[display.id()]->AddAppWindow(
        app_window, app_window->GetNativeWindow());
  }

  app_window->GetNativeWindow()->SetBoundsInScreen(bounds, display);
}

void ShellDesktopControllerAura::InitWindowManager() {
  root_window_event_filter_ = std::make_unique<wm::CompoundEventFilter>();

  // Screen may be initialized in tests.
  if (!display::Screen::Get()) {
#if BUILDFLAG(IS_CHROMEOS)
    screen_ = std::make_unique<ShellScreen>(this, GetStartingWindowSize());
    // TODO(pkasting): Make ShellScreen() call SetScreenInstance() as the
    // classes in CreateDesktopScreen() do, and remove this.
    display::Screen::SetScreenInstance(screen_.get());
#else
    // TODO(crbug.com/40535820): Refactor DesktopScreen out of views.
    screen_ = views::CreateDesktopScreen();
#endif
  }

  focus_controller_ =
      std::make_unique<wm::FocusController>(new AppsFocusRules());
  cursor_manager_ = std::make_unique<wm::CursorManager>(
      std::make_unique<ShellNativeCursorManager>(this));
  cursor_manager_->SetDisplay(display::Screen::Get()->GetPrimaryDisplay());
  cursor_manager_->SetCursor(ui::mojom::CursorType::kPointer);

#if BUILDFLAG(IS_CHROMEOS)
  user_activity_notifier_ =
      std::make_unique<ui::UserActivityPowerManagerNotifier>(
          ui::UserActivityDetector::Get(), /*fingerprint=*/mojo::NullRemote());
#endif
}

void ShellDesktopControllerAura::TearDownWindowManager() {
  for (auto& pair : root_window_controllers_)
    TearDownRootWindowController(pair.second.get());
  root_window_controllers_.clear();

#if BUILDFLAG(IS_CHROMEOS)
  user_activity_notifier_.reset();
#endif
  cursor_manager_.reset();
  focus_controller_.reset();
  if (screen_) {
#if BUILDFLAG(IS_CHROMEOS)
    display::Screen::SetScreenInstance(nullptr);
#endif
    screen_.reset();
  }
  root_window_event_filter_.reset();
}

std::unique_ptr<RootWindowController>
ShellDesktopControllerAura::CreateRootWindowControllerForDisplay(
    const display::Display& display) {
  // Convert display's bounds from DIP to physical pixels for WindowTreeHost.
  gfx::Rect bounds(gfx::ScaleToFlooredPoint(display.bounds().origin(),
                                            display.device_scale_factor()),
                   display.GetSizeInPixel());
  auto root_window_controller =
      std::make_unique<RootWindowController>(this, bounds, browser_context_);

  // Initialize the root window with our clients.
  aura::Window* root_window = root_window_controller->host()->window();
  root_window->AddPreTargetHandler(root_window_event_filter_.get());
  aura::client::SetFocusClient(root_window, focus_controller_.get());
  root_window->AddPreTargetHandler(focus_controller_.get());
  wm::SetActivationClient(root_window, focus_controller_.get());
  aura::client::SetCursorClient(root_window, cursor_manager_.get());

  if (!input_method_) {
    // Create an input method and become its key event dispatcher.
    input_method_ = ui::CreateInputMethod(
        this, root_window_controller->host()->GetAcceleratedWidget());
    root_window_controller->host()->SetSharedInputMethod(input_method_.get());
  }

  return root_window_controller;
}

void ShellDesktopControllerAura::TearDownRootWindowController(
    RootWindowController* root) {
  root->host()->window()->RemovePreTargetHandler(
      root_window_event_filter_.get());
  root->host()->window()->RemovePreTargetHandler(focus_controller_.get());
}

void ShellDesktopControllerAura::MaybeQuit() {
  // Quit if there are no app windows open and no keep-alives waiting for apps
  // to relaunch.  |run_loop_| may be null in tests.
  if (quit_when_idle_closure_ && root_window_controllers_.empty() &&
      !KeepAliveRegistry::GetInstance()->IsKeepingAlive()) {
    std::move(quit_when_idle_closure_).Run();
  }
}

#if BUILDFLAG(IS_CHROMEOS)
gfx::Size ShellDesktopControllerAura::GetStartingWindowSize() {
  gfx::Size size = GetPrimaryDisplaySize();
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kAppShellHostWindowSize)) {
    const std::string size_str =
        command_line->GetSwitchValueASCII(switches::kAppShellHostWindowSize);
    int width, height;
    CHECK_EQ(2,
             UNSAFE_TODO(sscanf(size_str.c_str(), "%dx%d", &width, &height)));
    size = gfx::Size(width, height);
  }
  return size.IsEmpty() ? gfx::Size(1920, 1080) : size;
}

gfx::Size ShellDesktopControllerAura::GetPrimaryDisplaySize() {
  const display::DisplayConfigurator::DisplayStateList& displays =
      display_configurator_->cached_displays();
  const display::DisplayMode* mode =
      displays.empty() ? nullptr : displays[0]->current_mode();
  return mode ? mode->size() : gfx::Size();
}
#endif

}  // namespace extensions