// 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 <string>
#include <tuple>
#include <vector>
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/window_properties.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/test/test_future.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_observer.h"
#include "chrome/browser/apps/browser_instance/browser_app_instance_registry.h"
#include "chrome/browser/ash/crosapi/ash_requires_lacros_browsertestbase.h"
#include "chrome/browser/ash/crosapi/browser_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "components/app_constants/constants.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/events/base_event_utils.h"
#include "ui/wm/core/window_util.h"
using ::app_constants::kChromeAppId;
using ::app_constants::kLacrosAppId;
void PinApp(const std::string& app_id) {
auto* shelf_model = ash::ShelfModel::Get();
if (shelf_model->ItemIndexByAppID(app_id) >= 0) {
shelf_model->PinExistingItemWithID(app_id);
} else {
shelf_model->AddAndPinAppWithFactoryConstructedDelegate(app_id);
}
}
void UnpinApp(const std::string& app_id) {
ash::ShelfModel::Get()->UnpinAppWithID(app_id);
}
std::optional<ash::ShelfItemStatus> ShelfStatus(const std::string& app_id) {
ash::ShelfModel* model = ChromeShelfController::instance()->shelf_model();
for (const ash::ShelfItem& item : model->items()) {
if (item.id.app_id == app_id) {
return item.status;
}
}
return std::nullopt;
}
std::string WindowAppId(aura::Window* window) {
std::string* id = window->GetProperty(ash::kAppIDKey);
return id ? *id : "";
}
ash::ShelfID WindowShelfId(aura::Window* window) {
return ash::ShelfID::Deserialize(window->GetProperty(ash::kShelfIDKey));
}
class TestConditionWaiter : public apps::BrowserAppInstanceObserver,
public apps::AppRegistryCache::Observer {
public:
using Condition = base::RepeatingCallback<bool()>;
TestConditionWaiter(
apps::BrowserAppInstanceRegistry& browser_app_instance_registry,
apps::AppRegistryCache& app_registry_cache,
Condition condition)
: condition_(condition) {
browser_app_instance_registry_observation_.Observe(
&browser_app_instance_registry);
app_registry_cache_observation_.Observe(&app_registry_cache);
}
// apps::BrowserAppInstanceObserver overrides:
void OnBrowserWindowAdded(
const apps::BrowserWindowInstance& instance) override {
OnAnyEvent();
}
void OnBrowserWindowUpdated(
const apps::BrowserWindowInstance& instance) override {
OnAnyEvent();
}
void OnBrowserWindowRemoved(
const apps::BrowserWindowInstance& instance) override {
OnAnyEvent();
}
void OnBrowserAppAdded(const apps::BrowserAppInstance& instance) override {
OnAnyEvent();
}
void OnBrowserAppUpdated(const apps::BrowserAppInstance& instance) override {
OnAnyEvent();
}
void OnBrowserAppRemoved(const apps::BrowserAppInstance& instance) override {
OnAnyEvent();
}
// apps::AppRegistryCache::Observer overrides:
void OnAppUpdate(const apps::AppUpdate& update) override { OnAnyEvent(); }
void OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) override {
OnAnyEvent();
}
void Wait(const base::Location& from_here, const std::string& message) {
if (!condition_.Run()) {
base::test::ScopedRunLoopTimeout timeout(
from_here, TestTimeouts::action_timeout(),
base::BindLambdaForTesting(
[&]() { return "Waiting for: " + message; }));
run_loop_.Run();
}
}
private:
void OnAnyEvent() {
if (condition_.Run()) {
run_loop_.Quit();
}
}
base::RunLoop run_loop_;
base::ScopedObservation<apps::BrowserAppInstanceRegistry,
apps::BrowserAppInstanceObserver>
browser_app_instance_registry_observation_{this};
base::ScopedObservation<apps::AppRegistryCache,
apps::AppRegistryCache::Observer>
app_registry_cache_observation_{this};
Condition condition_;
};
#define WAIT_FOR(condition) \
WaitForCondition(FROM_HERE, \
base::BindLambdaForTesting([&]() { return (condition); }), \
#condition)
struct ExpectedAppMenuItem {
int command_id;
std::string title;
bool operator==(const ExpectedAppMenuItem& other) const {
return std::tie(command_id, title) ==
std::tie(other.command_id, other.title);
}
};
std::ostream& operator<<(std::ostream& os, const ExpectedAppMenuItem& i) {
return os << i.command_id << ", " << i.title;
}
class BrowserAppShelfControllerBrowserTest
: public crosapi::AshRequiresLacrosBrowserTestBase {
protected:
static constexpr char kURL_A[] = "https://a.example.org";
static constexpr char kURL_B[] = "https://b.example.org";
static constexpr char kURL_C[] = "https://c.example.org";
static constexpr char kURL_D[] = "https://d.example.org";
// Constants are defined here rather than globally because GURL objects cannot
// be created in a static initialiser.
const webapps::AppId kAppId_A = UrlToAppId(kURL_A);
const webapps::AppId kAppId_B = UrlToAppId(kURL_B);
const webapps::AppId kAppId_C = UrlToAppId(kURL_C);
const webapps::AppId kAppId_D = UrlToAppId(kURL_D);
webapps::AppId UrlToAppId(const std::string& start_url) {
return web_app::GenerateAppId(/*manifest_id=*/std::nullopt,
GURL(start_url));
}
void SetUpOnMainThread() override {
crosapi::AshRequiresLacrosBrowserTestBase::SetUpOnMainThread();
if (!HasLacrosArgument()) {
return;
}
apps::AppTypeInitializationWaiter(GetAshProfile(), apps::AppType::kWeb)
.Await();
auto* registry = AppServiceProxy()->BrowserAppInstanceRegistry();
ASSERT_NE(registry, nullptr);
registry_ = registry;
}
void TearDownOnMainThread() override {
crosapi::AshRequiresLacrosBrowserTestBase::TearDownOnMainThread();
if (!HasLacrosArgument()) {
return;
}
std::vector<std::string> app_ids;
AppServiceProxy()->AppRegistryCache().ForEachApp(
[&app_ids](const apps::AppUpdate& update) {
if (update.AppType() == apps::AppType::kWeb) {
app_ids.push_back(update.AppId());
}
});
for (const std::string& app_id : app_ids) {
AppServiceProxy()->UninstallSilently(app_id,
apps::UninstallSource::kShelf);
apps::AppReadinessWaiter(GetAshProfile(), app_id,
apps::Readiness::kUninstalledByUser)
.Await();
}
}
apps::AppServiceProxy* AppServiceProxy() {
return apps::AppServiceProxyFactory::GetForProfile(GetAshProfile());
}
void WaitForCondition(const base::Location& from_here,
TestConditionWaiter::Condition condition,
const std::string& message) {
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(GetAshProfile());
TestConditionWaiter(*registry_, proxy->AppRegistryCache(),
std::move(condition))
.Wait(from_here, message);
}
std::string InstallWebApp(const std::string& start_url,
apps::WindowMode mode) {
base::test::TestFuture<const std::string&> app_id_future;
GetStandaloneBrowserTestController()->InstallWebApp(
start_url, mode, app_id_future.GetCallback());
std::string app_id = app_id_future.Take();
// Wait until the app is installed: app service publisher updates may arrive
// out of order with the web app installation reply, so we wait until the
// state of the app service is consistent.
apps::AppReadinessWaiter(GetAshProfile(), app_id).Await();
AppServiceProxy()->AppRegistryCache().ForOneApp(
app_id, [mode](const apps::AppUpdate& update) {
EXPECT_EQ(update.AppType(), apps::AppType::kWeb);
EXPECT_EQ(update.WindowMode(), mode);
});
return app_id;
}
// Launch directly when there is no shelf item.
void Launch(const std::string& app_id) {
ChromeShelfController::instance()->LaunchApp(ash::ShelfID(app_id),
ash::LAUNCH_FROM_UNKNOWN, 0,
display::kInvalidDisplayId);
WAIT_FOR(registry_->IsAppRunning(app_id));
}
void Stop(const std::string& app_id) {
AppServiceProxy()->StopApp(app_id);
WAIT_FOR(!registry_->IsAppRunning(app_id));
}
bool IsAppActive(const std::string& app_id) const {
auto* app = registry_->FindAppInstanceIf(
[&app_id](const apps::BrowserAppInstance& instance) {
return instance.app_id == app_id;
});
return app && wm::IsActiveWindow(app->window) &&
app->is_web_contents_active;
}
// Get unique titles of all app instances.
std::set<std::string> GetAppTitles(const std::string& app_id) const {
std::set<std::string> result;
for (const auto* instance : registry_->SelectAppInstances(
[&app_id](const apps::BrowserAppInstance& instance) {
return instance.app_id == app_id;
})) {
result.insert(instance->title);
}
return result;
}
int AppInstanceCount(const std::string& app_id) const {
return registry_
->SelectAppInstances(
[&app_id](const apps::BrowserAppInstance& instance) {
return instance.app_id == app_id;
})
.size();
}
using SelectResult =
std::pair<ash::ShelfAction, std::vector<ExpectedAppMenuItem>>;
SelectResult SelectShelfItem(
const std::string& app_id,
ash::ShelfLaunchSource source = ash::LAUNCH_FROM_UNKNOWN) {
auto event = std::make_unique<ui::MouseEvent>(
ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, 0);
base::RunLoop run_loop;
ash::ShelfModel* model = ash::ShelfModel::Get();
ash::ShelfItemDelegate* delegate =
model->GetShelfItemDelegate(ash::ShelfID(app_id));
ash::ShelfAction action_taken = ash::SHELF_ACTION_NONE;
std::vector<ExpectedAppMenuItem> app_menu_items;
delegate->ItemSelected(
std::move(event), display::kInvalidDisplayId, source,
base::BindLambdaForTesting(
[&](ash::ShelfAction action,
ash::ShelfItemDelegate::AppMenuItems items) {
action_taken = action;
for (const auto& item : items) {
app_menu_items.push_back(
{item.command_id, base::UTF16ToUTF8(item.title)});
}
run_loop.Quit();
}),
base::NullCallback());
run_loop.Run();
WAIT_FOR(registry_->IsAppRunning(app_id));
return SelectResult{action_taken, std::move(app_menu_items)};
}
raw_ptr<apps::BrowserAppInstanceRegistry, DanglingUntriaged> registry_{
nullptr};
};
IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest, TabbedApps) {
if (!HasLacrosArgument()) {
return;
}
{
SCOPED_TRACE("initial state");
// StartLacros opens one Ash and one Lacros window.
WAIT_FOR(registry_->IsLacrosBrowserRunning());
EXPECT_EQ(ShelfStatus(kLacrosAppId), ash::STATUS_RUNNING);
}
const apps::BrowserWindowInstance* lacros =
*std::begin(registry_->GetLacrosBrowserWindowInstances());
ASSERT_TRUE(lacros);
EXPECT_EQ(WindowAppId(lacros->window), kLacrosAppId);
// Shelf ID property tells us which shelf item will be marked as active by
// ShelfWindowWatcher, so we just watch that.
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
{
SCOPED_TRACE("launch unpinned, stop");
ASSERT_EQ(kAppId_A, InstallWebApp(kURL_A, apps::WindowMode::kBrowser));
Launch(kAppId_A);
// App A is unpinned, so no new item.
EXPECT_EQ(ShelfStatus(kAppId_A), std::nullopt);
// App ID of the window is now set to app A, but shelf ID maps to the
// browser shelf item because there is no pinned item for app A.
EXPECT_EQ(WindowAppId(lacros->window), kAppId_A);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
// Close just the app tab.
Stop(kAppId_A);
EXPECT_EQ(WindowAppId(lacros->window), kLacrosAppId);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
}
{
SCOPED_TRACE("launch, pin, stop");
ASSERT_EQ(kAppId_B, InstallWebApp(kURL_B, apps::WindowMode::kBrowser));
Launch(kAppId_B);
PinApp(kAppId_B);
// App B has a pinned item, so it's marked running.
EXPECT_EQ(ShelfStatus(kAppId_B), ash::STATUS_RUNNING);
// Both app/shelf ID of the browser window now point to the shelf item for
// app B.
EXPECT_EQ(WindowAppId(lacros->window), kAppId_B);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
// Close just the app tab.
Stop(kAppId_B);
// Pinned item remains.
EXPECT_EQ(ShelfStatus(kAppId_B), ash::STATUS_CLOSED);
EXPECT_EQ(WindowAppId(lacros->window), kLacrosAppId);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
}
{
SCOPED_TRACE("pin, launch, stop");
ASSERT_EQ(kAppId_C, InstallWebApp(kURL_C, apps::WindowMode::kBrowser));
PinApp(kAppId_C);
EXPECT_EQ(SelectShelfItem(kAppId_C),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
// App C has a pinned item, so it's marked running and active.
EXPECT_EQ(ShelfStatus(kAppId_C), ash::STATUS_RUNNING);
// Both app/shelf ID of the browser window now point to the shelf item for
// app C.
EXPECT_EQ(WindowAppId(lacros->window), kAppId_C);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_C));
// Close just the app tab.
Stop(kAppId_C);
// Pinned item remains.
EXPECT_EQ(ShelfStatus(kAppId_C), ash::STATUS_CLOSED);
EXPECT_EQ(WindowAppId(lacros->window), kLacrosAppId);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
}
{
SCOPED_TRACE("pin, launch, unpin, stop");
ASSERT_EQ(kAppId_D, InstallWebApp(kURL_D, apps::WindowMode::kBrowser));
PinApp(kAppId_D);
EXPECT_EQ(SelectShelfItem(kAppId_D),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
UnpinApp(kAppId_D);
// Unpinned app tabs don't get a shelf item, but if the app is still
// running, it stays.
EXPECT_EQ(ShelfStatus(kAppId_D), ash::STATUS_RUNNING);
EXPECT_EQ(WindowAppId(lacros->window), kAppId_D);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_D));
// Close just the app tab.
Stop(kAppId_D);
EXPECT_EQ(ShelfStatus(kAppId_D), std::nullopt);
EXPECT_EQ(WindowAppId(lacros->window), kLacrosAppId);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kLacrosAppId));
}
}
IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest, WindowedApps) {
if (!HasLacrosArgument()) {
return;
}
{
SCOPED_TRACE("initial state");
// StartLacros opens one Ash and one Lacros window.
WAIT_FOR(registry_->IsLacrosBrowserRunning());
EXPECT_EQ(ShelfStatus(kLacrosAppId), ash::STATUS_RUNNING);
}
{
SCOPED_TRACE("launch unpinned, stop");
ASSERT_EQ(kAppId_A, InstallWebApp(kURL_A, apps::WindowMode::kWindow));
Launch(kAppId_A);
const apps::BrowserAppInstance* appA = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_A; });
EXPECT_EQ(ShelfStatus(kAppId_A), ash::STATUS_RUNNING);
ASSERT_TRUE(appA);
EXPECT_EQ(WindowAppId(appA->window), kAppId_A);
EXPECT_EQ(WindowShelfId(appA->window), ash::ShelfID(kAppId_A));
// Close the app window.
Stop(kAppId_A);
EXPECT_EQ(ShelfStatus(kAppId_A), std::nullopt);
}
{
SCOPED_TRACE("launch, pin, stop");
ASSERT_EQ(kAppId_B, InstallWebApp(kURL_B, apps::WindowMode::kWindow));
Launch(kAppId_B);
PinApp(kAppId_B);
const apps::BrowserAppInstance* appB = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_B; });
EXPECT_EQ(ShelfStatus(kAppId_B), ash::STATUS_RUNNING);
ASSERT_TRUE(appB);
EXPECT_EQ(WindowAppId(appB->window), kAppId_B);
EXPECT_EQ(WindowShelfId(appB->window), ash::ShelfID(kAppId_B));
// Close the app window.
Stop(kAppId_B);
EXPECT_EQ(ShelfStatus(kAppId_B), ash::STATUS_CLOSED);
}
{
SCOPED_TRACE("pin, launch, stop");
ASSERT_EQ(kAppId_C, InstallWebApp(kURL_C, apps::WindowMode::kWindow));
PinApp(kAppId_C);
EXPECT_EQ(SelectShelfItem(kAppId_C),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
const apps::BrowserAppInstance* appC = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_C; });
EXPECT_EQ(ShelfStatus(kAppId_C), ash::STATUS_RUNNING);
ASSERT_TRUE(appC);
EXPECT_EQ(WindowAppId(appC->window), kAppId_C);
EXPECT_EQ(WindowShelfId(appC->window), ash::ShelfID(kAppId_C));
// Close the app window.
Stop(kAppId_C);
EXPECT_EQ(ShelfStatus(kAppId_C), ash::STATUS_CLOSED);
}
{
SCOPED_TRACE("pin, launch, unpin, stop");
ASSERT_EQ(kAppId_D, InstallWebApp(kURL_D, apps::WindowMode::kWindow));
PinApp(kAppId_D);
EXPECT_EQ(SelectShelfItem(kAppId_D),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
UnpinApp(kAppId_D);
const apps::BrowserAppInstance* appD = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_D; });
EXPECT_EQ(ShelfStatus(kAppId_D), ash::STATUS_RUNNING);
ASSERT_TRUE(appD);
EXPECT_EQ(WindowAppId(appD->window), kAppId_D);
EXPECT_EQ(WindowShelfId(appD->window), ash::ShelfID(kAppId_D));
// Close the app window.
Stop(kAppId_D);
EXPECT_EQ(ShelfStatus(kAppId_D), std::nullopt);
}
}
// Flakily fails: https://crbug.com/1373054
IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
DISABLED_ActivateAndMinimizeTabs) {
if (!HasLacrosArgument()) {
return;
}
{
SCOPED_TRACE("initial state");
// StartLacros opens one Ash and one Lacros window.
WAIT_FOR(registry_->IsAshBrowserRunning() &&
registry_->IsLacrosBrowserRunning());
EXPECT_EQ(ShelfStatus(kLacrosAppId), ash::STATUS_RUNNING);
EXPECT_EQ(ShelfStatus(kChromeAppId), ash::STATUS_RUNNING);
}
const apps::BrowserWindowInstance* lacros =
*std::begin(registry_->GetLacrosBrowserWindowInstances());
{
// Install, pin, and launch two apps (A and B) in the same order. Both apps
// will be running in two tabs in one window, app B is active.
ASSERT_EQ(kAppId_A, InstallWebApp(kURL_A, apps::WindowMode::kBrowser));
ASSERT_EQ(kAppId_B, InstallWebApp(kURL_B, apps::WindowMode::kBrowser));
PinApp(kAppId_A);
PinApp(kAppId_B);
ASSERT_EQ(SelectShelfItem(kAppId_A),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
ASSERT_EQ(SelectShelfItem(kAppId_B),
(SelectResult{ash::SHELF_ACTION_NEW_WINDOW_CREATED, {}}));
ASSERT_EQ(registry_->GetLacrosBrowserWindowInstances().size(), 1u);
ASSERT_EQ(ShelfStatus(kAppId_A), ash::STATUS_RUNNING);
ASSERT_EQ(ShelfStatus(kAppId_B), ash::STATUS_RUNNING);
ASSERT_EQ(WindowAppId(lacros->window), kAppId_B);
ASSERT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
// Activate the inactive app A tab.
ASSERT_EQ(SelectShelfItem(kAppId_A),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(IsAppActive(kAppId_A) && !IsAppActive(kAppId_B));
EXPECT_EQ(WindowAppId(lacros->window), kAppId_A);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_A));
// Re-activate app B tab again.
ASSERT_EQ(SelectShelfItem(kAppId_B),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && IsAppActive(kAppId_B));
EXPECT_EQ(WindowAppId(lacros->window), kAppId_B);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
// Selecting app B again minimises it.
ASSERT_EQ(SelectShelfItem(kAppId_B),
(SelectResult{ash::SHELF_ACTION_WINDOW_MINIMIZED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && !IsAppActive(kAppId_B));
// Window properties should not change for the minimised window.
EXPECT_FALSE(lacros->window->IsVisible());
EXPECT_EQ(WindowAppId(lacros->window), kAppId_B);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
// Selecting app B again restores and activates it.
ASSERT_EQ(SelectShelfItem(kAppId_B),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && IsAppActive(kAppId_B));
// Window properties should not change for the window.
EXPECT_TRUE(lacros->window->IsVisible());
EXPECT_EQ(WindowAppId(lacros->window), kAppId_B);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
// Selecting app B again from the app list should do nothing.
ASSERT_EQ(SelectShelfItem(kAppId_B, ash::LAUNCH_FROM_APP_LIST),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && IsAppActive(kAppId_B));
// Window properties should not change for the window.
EXPECT_TRUE(lacros->window->IsVisible());
EXPECT_EQ(WindowAppId(lacros->window), kAppId_B);
EXPECT_EQ(WindowShelfId(lacros->window), ash::ShelfID(kAppId_B));
}
}
// Flakily fails: https://crbug.com/1373054
IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
DISABLED_ActivateAndMinimizeWindows) {
if (!HasLacrosArgument()) {
return;
}
{
SCOPED_TRACE("initial state");
// StartLacros opens one Ash and one Lacros window.
WAIT_FOR(registry_->IsAshBrowserRunning() &&
registry_->IsLacrosBrowserRunning());
EXPECT_EQ(ShelfStatus(kLacrosAppId), ash::STATUS_RUNNING);
EXPECT_EQ(ShelfStatus(kChromeAppId), ash::STATUS_RUNNING);
}
ASSERT_EQ(kAppId_A, InstallWebApp(kURL_A, apps::WindowMode::kWindow));
ASSERT_EQ(kAppId_B, InstallWebApp(kURL_B, apps::WindowMode::kWindow));
Launch(kAppId_A);
Launch(kAppId_B);
const apps::BrowserAppInstance* appA = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_A; });
const apps::BrowserAppInstance* appB = registry_->FindAppInstanceIf(
[&](const auto& instance) { return instance.app_id == kAppId_B; });
// Both are pinned.
EXPECT_EQ(ShelfStatus(kAppId_A), ash::STATUS_RUNNING);
EXPECT_EQ(ShelfStatus(kAppId_B), ash::STATUS_RUNNING);
// App B window is activated.
ASSERT_EQ(SelectShelfItem(kAppId_B),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && IsAppActive(kAppId_B));
// App A window is activated.
ASSERT_EQ(SelectShelfItem(kAppId_A),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(IsAppActive(kAppId_A) && !IsAppActive(kAppId_B));
EXPECT_TRUE(appA->window->IsVisible());
EXPECT_TRUE(appB->window->IsVisible());
// App A window is minimised.
ASSERT_EQ(SelectShelfItem(kAppId_A),
(SelectResult{ash::SHELF_ACTION_WINDOW_MINIMIZED, {}}));
WAIT_FOR(!IsAppActive(kAppId_A) && IsAppActive(kAppId_B));
EXPECT_FALSE(appA->window->IsVisible());
EXPECT_TRUE(appB->window->IsVisible());
// App A window is restored.
ASSERT_EQ(SelectShelfItem(kAppId_A),
(SelectResult{ash::SHELF_ACTION_WINDOW_ACTIVATED, {}}));
WAIT_FOR(IsAppActive(kAppId_A) && !IsAppActive(kAppId_B));
EXPECT_TRUE(appA->window->IsVisible());
EXPECT_TRUE(appB->window->IsVisible());
}
// Flakily fails: https://crbug.com/1373054
IN_PROC_BROWSER_TEST_F(BrowserAppShelfControllerBrowserTest,
DISABLED_MultipleInstancesShowMenu) {
if (!HasLacrosArgument()) {
return;
}
{
SCOPED_TRACE("initial state");
// StartLacros opens one Ash and one Lacros window.
WAIT_FOR(registry_->IsAshBrowserRunning() &&
registry_->IsLacrosBrowserRunning());
EXPECT_EQ(ShelfStatus(kLacrosAppId), ash::STATUS_RUNNING);
EXPECT_EQ(ShelfStatus(kChromeAppId), ash::STATUS_RUNNING);
}
ASSERT_EQ(kAppId_A, InstallWebApp(kURL_A, apps::WindowMode::kBrowser));
PinApp(kAppId_A);
Launch(kAppId_A);
Launch(kAppId_A);
WAIT_FOR(AppInstanceCount(kAppId_A) == 2 &&
GetAppTitles(kAppId_A) == std::set<std::string>{"a.example.org"});
EXPECT_EQ(SelectShelfItem(kAppId_A), (SelectResult{ash::SHELF_ACTION_NONE,
{
{1, "a.example.org"},
{2, "a.example.org"},
}}));
ASSERT_EQ(kAppId_B, InstallWebApp(kURL_B, apps::WindowMode::kWindow));
Launch(kAppId_B);
Launch(kAppId_B);
WAIT_FOR(AppInstanceCount(kAppId_B) == 2 &&
GetAppTitles(kAppId_B) == std::set<std::string>{"b.example.org"});
EXPECT_EQ(SelectShelfItem(kAppId_B), (SelectResult{ash::SHELF_ACTION_NONE,
{
{1, "b.example.org"},
{2, "b.example.org"},
}}));
}