blob: a0e7d67bba85f5db7c2a9a8f2285bdbaad87aa33 [file] [log] [blame]
// 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/app_service/browser_app_instance_registry.h"
#include <utility>
#include "base/debug/dump_without_crashing.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/apps/app_service/browser_app_instance.h"
#include "chrome/browser/apps/app_service/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, ExperimentalAsh> 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;
});
}
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)) {
if (aura::Window* window = GetWindowByInstanceId(id)) {
views::Widget* widget = views::Widget::GetWidgetForNativeView(window);
if (widget->IsActive() != instance->is_active) {
// TODO: Replace log with DCHECK once we know better about
// crbug.com/1284930 and b/256952679.
static bool reported = false;
if (!reported) {
reported = true;
LOG(ERROR) << "Browser window activation is inconsistent. Registry "
"is "
<< instance->is_active << " while widget is "
<< widget->IsActive() << ".";
base::debug::DumpWithoutCrashing();
}
}
}
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::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) {
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) {
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/1174246): 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::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++;
}
}
}
// 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