chromium/chrome/browser/apps/browser_instance/browser_app_instance_registry.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/apps/browser_instance/browser_app_instance_registry.h"

#include <utility>

#include "base/memory/raw_ptr.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_map.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ui/browser_window.h"
#include "components/exo/shell_surface_util.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/activation_change_observer.h"
#include "ui/wm/public/activation_client.h"

namespace apps {

namespace {

void ActivateWindow(aura::Window* window) {
  wm::GetActivationClient(window->GetRootWindow())->ActivateWindow(window);
}

void MinimizeWindow(aura::Window* window) {
  views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
  widget->Minimize();
}

}  // namespace

struct BrowserAppInstanceRegistry::WindowEventList {
  raw_ptr<aura::Window> window{nullptr};
  std::vector<base::OnceCallback<void(aura::Window*)>> events;
};

BrowserAppInstanceRegistry::BrowserAppInstanceRegistry(
    BrowserAppInstanceTracker& ash_instance_tracker)
    : ash_instance_tracker_(ash_instance_tracker) {
  tracker_observation_.Observe(&*ash_instance_tracker_);
  aura_env_observation_.Observe(aura::Env::GetInstance());
}

BrowserAppInstanceRegistry::~BrowserAppInstanceRegistry() = default;

const BrowserAppInstance* BrowserAppInstanceRegistry::GetAppInstanceById(
    base::UnguessableToken id) const {
  return FindAppInstanceIf(
      [&id](const BrowserAppInstance& instance) { return instance.id == id; });
}

const BrowserWindowInstance*
BrowserAppInstanceRegistry::GetBrowserWindowInstanceById(
    base::UnguessableToken id) const {
  return FindWindowInstanceIf([&id](const BrowserWindowInstance& instance) {
    return instance.id == id;
  });
}

const std::set<const BrowserAppInstance*>
BrowserAppInstanceRegistry::GetBrowserAppInstancesByWindow(
    const aura::Window* window) const {
  return SelectAppInstances([&window](const BrowserAppInstance& instance) {
    return instance.window == window;
  });
}

const BrowserWindowInstance*
BrowserAppInstanceRegistry::GetBrowserWindowInstanceByWindow(
    const aura::Window* window) const {
  return FindWindowInstanceIf([&window](const BrowserWindowInstance& instance) {
    return instance.window == window;
  });
}

aura::Window* BrowserAppInstanceRegistry::GetWindowByInstanceId(
    const base::UnguessableToken& id) const {
  if (const BrowserAppInstance* instance = GetAppInstanceById(id)) {
    return instance->window;
  }

  if (const BrowserWindowInstance* instance =
          GetBrowserWindowInstanceById(id)) {
    return instance->window;
  }

  return nullptr;
}

std::set<const BrowserWindowInstance*>
BrowserAppInstanceRegistry::GetLacrosBrowserWindowInstances() const {
  std::set<const BrowserWindowInstance*> result;
  for (const auto& pair : lacros_window_instances_) {
    result.insert(pair.second.get());
  }
  return result;
}

bool BrowserAppInstanceRegistry::IsAppRunning(const std::string& app_id) const {
  return FindAppInstanceIf([&app_id](const BrowserAppInstance& instance) {
           return instance.app_id == app_id;
         }) != nullptr;
}

bool BrowserAppInstanceRegistry::IsAshBrowserRunning() const {
  return ash_instance_tracker_->window_instances_.size() > 0;
}

bool BrowserAppInstanceRegistry::IsLacrosBrowserRunning() const {
  return lacros_window_instances_.size() > 0;
}

void BrowserAppInstanceRegistry::ActivateTabInstance(
    const base::UnguessableToken& id) {
  if (lacros_app_instances_.find(id) != lacros_app_instances_.end()) {
    if (controller_.is_bound()) {
      controller_->ActivateTabInstance(id);
    }
  } else {
    ash_instance_tracker_->ActivateTabInstance(id);
  }
}

void BrowserAppInstanceRegistry::ActivateInstance(
    const base::UnguessableToken& id) {
  if (const BrowserAppInstance* instance = GetAppInstanceById(id)) {
    ActivateWindow(instance->window);
    ActivateTabInstance(id);
    return;
  }

  if (const BrowserWindowInstance* instance =
          GetBrowserWindowInstanceById(id)) {
    ActivateWindow(instance->window);
  }
}

void BrowserAppInstanceRegistry::MinimizeInstance(
    const base::UnguessableToken& id) {
  if (aura::Window* window = GetWindowByInstanceId(id)) {
    MinimizeWindow(window);
  }
}

bool BrowserAppInstanceRegistry::IsInstanceActive(
    const base::UnguessableToken& id) const {
  if (const BrowserAppInstance* instance = GetAppInstanceById(id)) {
    return instance->is_browser_active() && instance->is_web_contents_active;
  }

  if (const BrowserWindowInstance* instance =
          GetBrowserWindowInstanceById(id)) {
    return instance->is_active();
  }
  return false;
}

void BrowserAppInstanceRegistry::NotifyExistingInstances(
    BrowserAppInstanceObserver* observer) {
  for (const auto& pair : ash_instance_tracker_->window_instances_) {
    observer->OnBrowserWindowAdded(*pair.second);
  }
  for (const auto& pair : ash_instance_tracker_->app_tab_instances_) {
    observer->OnBrowserAppAdded(*pair.second);
  }
  for (const auto& pair : ash_instance_tracker_->app_window_instances_) {
    observer->OnBrowserAppAdded(*pair.second);
  }
  for (const auto& pair : lacros_window_instances_) {
    observer->OnBrowserWindowAdded(*pair.second);
  }
  for (const auto& pair : lacros_app_instances_) {
    observer->OnBrowserAppAdded(*pair.second);
  }
}

void BrowserAppInstanceRegistry::MaybeStartActivationObservation(
    aura::Window* window) {
  if (is_activation_observed_) {
    return;
  }

  is_activation_observed_ = true;
  // On Ash Chrome, there is only one `ActivationClient` so as long as `window`
  // is attached to the tree, it serves the purpose of getting the
  // `ActivationClient`.
  // Since `ActivationClient` is destroyed before `BrowserAppInstanceRegistry`,
  // `BrowserAppInstanceRegistry` does not need to remove itself upon
  // destruction.
  wm::ActivationClient* activation_client =
      wm::GetActivationClient(window->GetRootWindow());
  CHECK(activation_client);
  activation_client->AddObserver(this);
}

void BrowserAppInstanceRegistry::BindReceiver(
    crosapi::CrosapiId id,
    mojo::PendingReceiver<crosapi::mojom::BrowserAppInstanceRegistry>
        receiver) {
  receiver_set_.Add(this, std::move(receiver), id);
}

void BrowserAppInstanceRegistry::OnBrowserWindowAdded(
    const apps::BrowserWindowInstance& instance) {
  MaybeStartActivationObservation(instance.window);
  for (auto& observer : observers_) {
    observer.OnBrowserWindowAdded(instance);
  }
}

void BrowserAppInstanceRegistry::OnBrowserWindowUpdated(
    const apps::BrowserWindowInstance& instance) {
  for (auto& observer : observers_) {
    observer.OnBrowserWindowUpdated(instance);
  }
}

void BrowserAppInstanceRegistry::OnBrowserWindowRemoved(
    const apps::BrowserWindowInstance& instance) {
  for (auto& observer : observers_) {
    observer.OnBrowserWindowRemoved(instance);
  }
}

void BrowserAppInstanceRegistry::OnBrowserAppAdded(
    const BrowserAppInstance& instance) {
  MaybeStartActivationObservation(instance.window);
  for (auto& observer : observers_) {
    observer.OnBrowserAppAdded(instance);
  }
}

void BrowserAppInstanceRegistry::OnBrowserAppUpdated(
    const BrowserAppInstance& instance) {
  for (auto& observer : observers_) {
    observer.OnBrowserAppUpdated(instance);
  }
}

void BrowserAppInstanceRegistry::OnBrowserAppRemoved(
    const BrowserAppInstance& instance) {
  for (auto& observer : observers_) {
    observer.OnBrowserAppRemoved(instance);
  }
}

void BrowserAppInstanceRegistry::RegisterController(
    mojo::PendingRemote<crosapi::mojom::BrowserAppInstanceController>
        controller) {
  // At the moment only a single controller is supported.
  // TODO(crbug.com/40167449): Support SxS lacros.
  if (controller_.is_bound()) {
    return;
  }
  controller_.Bind(std::move(controller));
  controller_.set_disconnect_handler(
      base::BindOnce(&BrowserAppInstanceRegistry::OnControllerDisconnected,
                     base::Unretained(this)));
}

void BrowserAppInstanceRegistry::OnBrowserWindowAdded(
    apps::BrowserWindowInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(&BrowserAppInstanceRegistry::LacrosWindowInstanceAdded,
                     weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnBrowserWindowUpdated(
    apps::BrowserWindowInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(&BrowserAppInstanceRegistry::LacrosWindowInstanceUpdated,
                     weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnBrowserWindowRemoved(
    apps::BrowserWindowInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(&BrowserAppInstanceRegistry::LacrosWindowInstanceRemoved,
                     weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnBrowserAppAdded(
    apps::BrowserAppInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(
          &BrowserAppInstanceRegistry::LacrosAppInstanceAddedOrUpdated,
          weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnBrowserAppUpdated(
    apps::BrowserAppInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(
          &BrowserAppInstanceRegistry::LacrosAppInstanceAddedOrUpdated,
          weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnBrowserAppRemoved(
    apps::BrowserAppInstanceUpdate update) {
  auto window_id = update.window_id;
  RunOrEnqueueEventForWindow(
      window_id,
      base::BindOnce(&BrowserAppInstanceRegistry::LacrosAppInstanceRemoved,
                     weak_ptr_factory_.GetWeakPtr(), std::move(update)));
}

void BrowserAppInstanceRegistry::OnWindowInitialized(aura::Window* window) {
  if (!crosapi::browser_util::IsLacrosWindow(window)) {
    return;
  }
  lacros_window_observations_.AddObservation(window);
  const std::string* id = exo::GetShellApplicationId(window);
  DCHECK(id);
  auto& event_list = window_id_to_event_list_[*id];
  event_list.window = window;
  // Flush any pending events for the new window.
  for (auto& callback : event_list.events) {
    std::move(callback).Run(window);
  }
  event_list.events.clear();
}

void BrowserAppInstanceRegistry::OnWindowVisibilityChanged(aura::Window* window,
                                                           bool visible) {
  if (visible) {
    // When `LacrosWindowInstanceAdded()` or
    // `LacrosAppInstanceAddedOrUpdated()` is called, the `window` is not
    // attached to the tree yet and will not be able to get `ActivationClient`
    // from it. For this reason, for Lacros window, we delay the start of
    // activation to when it becomes visible.
    MaybeStartActivationObservation(window);
  }
}

void BrowserAppInstanceRegistry::OnWindowDestroying(aura::Window* window) {
  lacros_window_observations_.RemoveObservation(window);
  const std::string* id = exo::GetShellApplicationId(window);
  DCHECK(id);
  DCHECK(base::Contains(window_id_to_event_list_, *id));
  window_id_to_event_list_.erase(*id);

  for (auto it = std::begin(lacros_app_instances_);
       it != std::end(lacros_app_instances_);) {
    if (it->second->window == window) {
      auto instance = std::move(it->second);
      it = lacros_app_instances_.erase(it);
      for (auto& observer : observers_) {
        observer.OnBrowserAppRemoved(*instance);
      }
    } else {
      it++;
    }
  }
  for (auto it = std::begin(lacros_window_instances_);
       it != std::end(lacros_window_instances_);) {
    if (it->second->window == window) {
      auto instance = std::move(it->second);
      it = lacros_window_instances_.erase(it);
      for (auto& observer : observers_) {
        observer.OnBrowserWindowRemoved(*instance);
      }
    } else {
      it++;
    }
  }
}

void BrowserAppInstanceRegistry::OnWindowActivated(ActivationReason reason,
                                                   aura::Window* gained_active,
                                                   aura::Window* lost_active) {
  std::set<const BrowserAppInstance*> instances =
      GetBrowserAppInstancesByWindow(gained_active);
  for (const auto* instance : instances) {
    OnBrowserAppUpdated(*instance);
  }

  instances = GetBrowserAppInstancesByWindow(lost_active);
  for (const auto* instance : instances) {
    OnBrowserAppUpdated(*instance);
  }

  if (const BrowserWindowInstance* instance =
          GetBrowserWindowInstanceByWindow(gained_active)) {
    OnBrowserWindowUpdated(*instance);
  }

  if (const BrowserWindowInstance* instance =
          GetBrowserWindowInstanceByWindow(lost_active)) {
    OnBrowserWindowUpdated(*instance);
  }
}

// Run the action immediately if the window matching |window_id| is
// available, otherwise buffer the event until it is.
void BrowserAppInstanceRegistry::RunOrEnqueueEventForWindow(
    const std::string& window_id,
    base::OnceCallback<void(aura::Window*)> event) {
  auto& event_list = window_id_to_event_list_[window_id];
  if (event_list.window) {
    std::move(event).Run(event_list.window.get());
  } else {
    event_list.events.push_back(std::move(event));
  }
}

void BrowserAppInstanceRegistry::LacrosWindowInstanceAdded(
    apps::BrowserWindowInstanceUpdate update,
    aura::Window* window) {
  DCHECK(window);
  auto instance_id = update.id;
  auto& instance = AddInstance(
      lacros_window_instances_, instance_id,
      std::make_unique<BrowserWindowInstance>(std::move(update), window));
  for (auto& observer : observers_) {
    observer.OnBrowserWindowAdded(instance);
  }
}

void BrowserAppInstanceRegistry::LacrosWindowInstanceUpdated(
    apps::BrowserWindowInstanceUpdate update,
    aura::Window* window) {
  DCHECK(window);
  auto* instance = GetInstance(lacros_window_instances_, update.id);
  if (instance && instance->MaybeUpdate(update.is_active)) {
    for (auto& observer : observers_) {
      observer.OnBrowserWindowUpdated(*instance);
    }
  }
}

void BrowserAppInstanceRegistry::LacrosWindowInstanceRemoved(
    apps::BrowserWindowInstanceUpdate update,
    aura::Window* window) {
  DCHECK(window);
  auto instance = PopInstanceIfExists(lacros_window_instances_, update.id);
  if (instance) {
    for (auto& observer : observers_) {
      observer.OnBrowserWindowRemoved(*instance);
    }
  }
}

void BrowserAppInstanceRegistry::LacrosAppInstanceAddedOrUpdated(
    apps::BrowserAppInstanceUpdate update,
    aura::Window* window) {
  DCHECK(window);
  // Create instance if it does not already eixsts, update it if exists.
  //
  // In some cases this may result in the removal of an instance and then
  // immediate recreation of it with the same ID, but it's necessary to maintain
  // app instances with a valid window.
  //
  // For example, if the last tab is dragged from browser A into browser B, the
  // tab will get reparented into a different window and browser A's window is
  // destroyed. However app instance messages and window destruction events may
  // arrive out of order because they originate from different sources now
  // (crosapi and wayland). If a window is destroyed first, it leaves the app
  // instance with an invalid window for a fraction of time. Rather than making
  // the window pointer nullable, we remove the instance and then re-add it when
  // an instance update message reparenting the instance into a new window
  // arrives.
  BrowserAppInstance* instance = GetInstance(lacros_app_instances_, update.id);
  if (instance) {
    if (instance->MaybeUpdate(window, update.title, update.is_browser_active,
                              update.is_web_contents_active,
                              update.browser_session_id,
                              update.restored_browser_session_id)) {
      for (auto& observer : observers_) {
        observer.OnBrowserAppUpdated(*instance);
      }
    }
  } else {
    auto id = update.id;
    auto& new_instance = AddInstance(
        lacros_app_instances_, id,
        std::make_unique<BrowserAppInstance>(std::move(update), window));
    for (auto& observer : observers_) {
      observer.OnBrowserAppAdded(new_instance);
    }
  }
}

void BrowserAppInstanceRegistry::LacrosAppInstanceRemoved(
    apps::BrowserAppInstanceUpdate update,
    aura::Window* window) {
  DCHECK(window);
  auto instance = PopInstanceIfExists(lacros_app_instances_, update.id);
  if (instance) {
    for (auto& observer : observers_) {
      observer.OnBrowserAppRemoved(*instance);
    }
  }
}

void BrowserAppInstanceRegistry::OnControllerDisconnected() {
  controller_.reset();
}

}  // namespace apps