blob: 9983ffdb1e5b6ae827f022294167a7fa85ec1928 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/shelf/browser_app_shelf_controller.h"
#include <memory>
#include "ash/public/cpp/window_properties.h"
#include "base/debug/dump_without_crashing.h"
#include "chrome/browser/apps/app_service/browser_app_instance.h"
#include "chrome/browser/apps/app_service/browser_app_instance_registry.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ui/ash/shelf/browser_app_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_item_factory.h"
#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_controller.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/app_constants/constants.h"
#include "ui/aura/window.h"
namespace {
void MaybeUpdateStringProperty(aura::Window* window,
const ui::ClassProperty<std::string*>* property,
const std::string& value) {
std::string* old_value = window->GetProperty(property);
if (!old_value || *old_value != value) {
window->SetProperty(property, value);
}
}
// TODO(crbug.com/1267769): sometimes browser window instance lookup fails when
// it should be present. Remove when we know the root cause.
void BrowserWindowMustBeValid(
const apps::BrowserWindowInstance* browser_window) {
if (!browser_window) {
base::debug::DumpWithoutCrashing();
} else if (!browser_window->window) {
base::debug::DumpWithoutCrashing();
}
}
} // namespace
BrowserAppShelfController::BrowserAppShelfController(
Profile* profile,
apps::BrowserAppInstanceRegistry& browser_app_instance_registry,
ash::ShelfModel& model,
ChromeShelfItemFactory& shelf_item_factory,
ShelfSpinnerController& shelf_spinner_controller)
: profile_(profile),
model_(model),
shelf_item_factory_(shelf_item_factory),
shelf_spinner_controller_(shelf_spinner_controller),
browser_app_instance_registry_(browser_app_instance_registry) {
CHECK(web_app::IsWebAppsCrosapiEnabled());
registry_observation_.Observe(&browser_app_instance_registry_);
shelf_model_observation_.Observe(&model);
}
BrowserAppShelfController::~BrowserAppShelfController() = default;
void BrowserAppShelfController::OnBrowserWindowAdded(
const apps::BrowserWindowInstance& instance) {
ash::ShelfID id(instance.GetAppId());
CreateOrUpdateShelfItem(id, ash::STATUS_RUNNING);
MaybeUpdateWindowProperties(instance.window);
}
void BrowserAppShelfController::OnBrowserWindowRemoved(
const apps::BrowserWindowInstance& instance) {
bool is_running =
crosapi::browser_util::IsLacrosWindow(instance.window)
? browser_app_instance_registry_.IsLacrosBrowserRunning()
: browser_app_instance_registry_.IsAshBrowserRunning();
if (!is_running) {
ash::ShelfID id(instance.GetAppId());
SetShelfItemClosed(id);
}
}
void BrowserAppShelfController::OnBrowserAppAdded(
const apps::BrowserAppInstance& instance) {
ash::ShelfID id(instance.app_id);
switch (instance.type) {
case apps::BrowserAppInstance::Type::kAppWindow: {
shelf_spinner_controller_.CloseSpinner(instance.app_id);
CreateOrUpdateShelfItem(id, ash::STATUS_RUNNING);
break;
}
case apps::BrowserAppInstance::Type::kAppTab:
// New shelf item is not automatically created for unpinned tabbed apps.
if (const ash::ShelfItem* item = model_.ItemByID(id)) {
UpdateShelfItemStatus(*item, ash::STATUS_RUNNING);
}
break;
}
MaybeUpdateWindowProperties(instance.window);
}
void BrowserAppShelfController::OnBrowserAppUpdated(
const apps::BrowserAppInstance& instance) {
// Active tab may have changed.
MaybeUpdateWindowProperties(instance.window);
}
void BrowserAppShelfController::OnBrowserAppRemoved(
const apps::BrowserAppInstance& instance) {
if (instance.type == apps::BrowserAppInstance::Type::kAppTab) {
// If a tab is closed, browser window may still remain, so it needs its
// properties updated.
MaybeUpdateWindowProperties(instance.window);
}
if (!browser_app_instance_registry_.IsAppRunning(instance.app_id)) {
ash::ShelfID id(instance.app_id);
SetShelfItemClosed(id);
}
}
void BrowserAppShelfController::ShelfItemAdded(int index) {
const ash::ShelfItem& item = model_.items()[index];
const std::string& app_id = item.id.app_id;
if (!BrowserAppShelfControllerShouldHandleApp(app_id, profile_)) {
return;
}
bool running = (app_id == app_constants::kLacrosAppId)
? browser_app_instance_registry_.IsLacrosBrowserRunning()
: browser_app_instance_registry_.IsAppRunning(app_id);
UpdateShelfItemStatus(item,
running ? ash::STATUS_RUNNING : ash::STATUS_CLOSED);
MaybeUpdateWindowPropertiesForApp(app_id);
}
void BrowserAppShelfController::UpdateShelfItemStatus(
const ash::ShelfItem& item,
ash::ShelfItemStatus status) {
auto new_item = item;
new_item.status = status;
model_.Set(model_.ItemIndexByID(item.id), new_item);
}
void BrowserAppShelfController::CreateOrUpdateShelfItem(
const ash::ShelfID& id,
ash::ShelfItemStatus status) {
const ash::ShelfItem* item = model_.ItemByID(id);
if (item) {
UpdateShelfItemStatus(*item, status);
return;
}
ash::ShelfItem new_item;
std::unique_ptr<ash::ShelfItemDelegate> delegate;
shelf_item_factory_.CreateShelfItemForAppId(id.app_id, &new_item, &delegate);
new_item.type = ash::TYPE_APP;
new_item.status = status;
new_item.app_status =
ShelfControllerHelper::GetAppStatus(profile_, id.app_id);
model_.AddAt(model_.item_count(), new_item, std::move(delegate));
}
void BrowserAppShelfController::SetShelfItemClosed(const ash::ShelfID& id) {
const ash::ShelfItem* item = model_.ItemByID(id);
if (!item) {
// There is no shelf item for unpinned apps running in a browser tab.
return;
}
if (ash::IsPinnedShelfItemType(item->type)) {
UpdateShelfItemStatus(*item, ash::STATUS_CLOSED);
} else {
int index = model_.ItemIndexByID(id);
model_.RemoveItemAt(index);
}
}
void BrowserAppShelfController::MaybeUpdateWindowProperties(
aura::Window* window) {
const apps::BrowserAppInstance* active_instance =
browser_app_instance_registry_.FindAppInstanceIf(
[window](const apps::BrowserAppInstance& instance) {
return instance.window == window && instance.is_web_contents_active;
});
const apps::BrowserWindowInstance* browser_window =
browser_app_instance_registry_.FindWindowInstanceIf(
[window](const apps::BrowserWindowInstance& instance) {
return instance.window == window;
});
const char* browser_app_id = crosapi::browser_util::IsLacrosWindow(window)
? app_constants::kLacrosAppId
: app_constants::kChromeAppId;
// App ID of the window is set to the app ID of the active tab. If the active
// tab has no app, app ID of the window is set to the browser's ID.
// Shelf ID of the window is set to the app's item on the shelf, if the item
// exists, otherwise it's set to the browser's ID (this happens for apps in
// tabs that aren't pinned).
std::string app_id;
ash::ShelfID shelf_id;
if (active_instance) {
app_id = active_instance->app_id;
const ash::ShelfItem* item = model_.ItemByID(ash::ShelfID(app_id));
if (item) {
shelf_id = item->id;
} else {
// There is no shelf item for unpinned apps running in a browser tab, so
// they get mapped to the browser's shelf item (app ID and shelf ID are
// different at this point).
shelf_id = ash::ShelfID(browser_app_id);
DCHECK(model_.ItemByID(shelf_id));
BrowserWindowMustBeValid(browser_window);
}
} else {
// No active app for that window: it's mapped to the browser's shelf item,
// which must be present.
app_id = browser_app_id;
shelf_id = ash::ShelfID(browser_app_id);
DCHECK(model_.ItemByID(shelf_id));
BrowserWindowMustBeValid(browser_window);
}
MaybeUpdateStringProperty(window, ash::kAppIDKey, app_id);
MaybeUpdateStringProperty(window, ash::kShelfIDKey, shelf_id.Serialize());
}
void BrowserAppShelfController::MaybeUpdateWindowPropertiesForApp(
const std::string& app_id) {
std::set<const apps::BrowserAppInstance*> instances =
browser_app_instance_registry_.SelectAppInstances(
[&app_id](const apps::BrowserAppInstance& instance) {
return instance.app_id == app_id;
});
std::set<aura::Window*> windows;
for (const auto* instance : instances) {
windows.insert(instance->window);
}
for (auto* window : windows) {
MaybeUpdateWindowProperties(window);
}
}