chromium/chrome/browser/ui/ash/shelf/browser_app_shelf_controller.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/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 "base/strings/string_util.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance.h"
#include "chrome/browser/apps/browser_instance/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_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);
  }
}

std::string BrowserAppIdForWindow(aura::Window* window) {
  return crosapi::browser_util::IsLacrosWindow(window)
             ? app_constants::kLacrosAppId
             : app_constants::kChromeAppId;
}

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

  std::unique_ptr<ash::ShelfItemDelegate> delegate =
      shelf_item_factory_->CreateShelfItemDelegateForAppId(id.app_id);
  std::unique_ptr<ash::ShelfItem> new_item =
      shelf_item_factory_->CreateShelfItemForApp(id, status, ash::TYPE_APP,
                                                 /*title=*/std::u16string());
  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) {
  // App ID of a window is set to the ID of the app active in this window:
  // 1) for app windows, it's the ID of the app running in this window,
  // 2) for regular tabbed browser windows, it's the ID of the app running in
  //    the active tab of this window. If there is no app in the active tab of a
  //    browser window, the window's app ID is set to the ID of the browser
  //    itself (Ash or Lacros).
  //
  // Shelf ID of a window is set to the ID of the shelf item the app instance
  // running in this window maps to. This is usually the same as window's app
  // ID, except for the cases where the active instance has no shelf item (apps
  // configured to open in a tab that don't have a pinned shelf item): in this
  // case, the window's shelf ID is set to the ID of the browser itself (Ash or
  // Lacros).

  std::string app_id;
  ash::ShelfID shelf_id;

  const apps::BrowserAppInstance* active_instance =
      browser_app_instance_registry_->FindAppInstanceIf(
          [window](const apps::BrowserAppInstance& instance) {
            return instance.window == window && instance.is_web_contents_active;
          });
  if (active_instance) {
    app_id = active_instance->app_id;
    if (const ash::ShelfItem* item = model_->ItemByID(ash::ShelfID(app_id))) {
      shelf_id = item->id;
    }
  } else {
    app_id = BrowserAppIdForWindow(window);
  }
  if (shelf_id.IsNull()) {
    shelf_id = ash::ShelfID(BrowserAppIdForWindow(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);
  }
}