blob: 14bc6b768c5bced69d5f86d231d97272ae04960c [file] [log] [blame]
// Copyright 2013 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/chrome_shelf_controller.h"
#include <stddef.h>
#include <algorithm>
#include <initializer_list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/metrics/arc_metrics_constants.h"
#include "ash/components/arc/mojom/app.mojom.h"
#include "ash/components/arc/mojom/compatibility_mode.mojom.h"
#include "ash/components/arc/test/arc_util_test_support.h"
#include "ash/components/arc/test/fake_app_instance.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/display/display_configuration_controller.h"
#include "ash/multi_user/multi_user_window_manager_impl.h"
#include "ash/public/cpp/app_list/internal_app_id_constants.h"
#include "ash/public/cpp/multi_user_window_manager.h"
#include "ash/public/cpp/shelf_item.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/public/cpp/shelf_prefs.h"
#include "ash/public/cpp/shelf_types.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shelf/shelf_application_menu_model.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "build/build_config.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/app_service/app_service_test.h"
#include "chrome/browser/apps/app_service/policy_util.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/app_service/app_service_app_icon_loader.h"
#include "chrome/browser/ash/app_list/arc/arc_app_icon.h"
#include "chrome/browser/ash/app_list/arc/arc_app_list_prefs.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
#include "chrome/browser/ash/app_list/arc/arc_default_app_list.h"
#include "chrome/browser/ash/app_list/internal_app/internal_app_metadata.h"
#include "chrome/browser/ash/apps/apk_web_app_service.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/crostini_util.h"
#include "chrome/browser/ash/eche_app/app_id.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_test_helper.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate_map.h"
#include "chrome/browser/ash/web_applications/camera_app/camera_system_web_app_info.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/media/router/media_router_feature.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/app_icon_loader.h"
#include "chrome/browser/ui/apps/chrome_app_delegate.h"
#include "chrome/browser/ui/ash/multi_user/multi_profile_support.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_util.h"
#include "chrome/browser/ui/ash/multi_user/multi_user_window_manager_helper.h"
#include "chrome/browser/ui/ash/session_controller_client_impl.h"
#include "chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/app_window_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/app_window_shelf_item_controller.h"
#include "chrome/browser/ui/ash/shelf/arc_app_window.h"
#include "chrome/browser/ui/ash/shelf/browser_status_monitor.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_test_util.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/chrome_shelf_prefs.h"
#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_controller.h"
#include "chrome/browser/ui/ash/shelf/shelf_spinner_item_controller.h"
#include "chrome/browser/ui/ash/shelf/standalone_browser_extension_app_shelf_item_controller.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/preinstalled_web_app_manager.h"
#include "chrome/browser/web_applications/test/app_registry_cache_waiter.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/test_browser_window_aura.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "components/account_id/account_id.h"
#include "components/app_constants/constants.h"
#include "components/exo/shell_surface_util.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/instance.h"
#include "components/services/app_service/public/cpp/instance_registry.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "components/sync/base/model_type.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/model/sync_change.h"
#include "components/sync/protocol/app_list_specifics.pb.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/test/fake_sync_change_processor.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_preferences/pref_model_associator.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/viz/test/test_gpu_service_holder.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/app_window/app_window_contents.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/app_window/native_app_window.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/grit/extensions_browser_resources.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/window_parenting_client.h"
#include "ui/aura/window.h"
#include "ui/base/models/menu_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/display.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event_constants.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/widget/widget.h"
#include "url/gurl.h"
namespace content {
class BrowserContext;
} // namespace content
using base::ASCIIToUTF16;
using extensions::Extension;
using extensions::UnloadedExtensionReason;
using extensions::mojom::ManifestLocation;
namespace {
constexpr char kGmailUrl[] = "https://mail.google.com/mail/u";
constexpr char kGmailLaunchURL[] = "https://mail.google.com/mail/ca";
constexpr char kLaunchURL[] = "https://foo.example/";
// An extension prefix.
constexpr char kCrxAppPrefix[] = "_crx_";
// Dummy app id is used to put at least one pin record to prevent initializing
// pin model with preinstalled apps that can affect some tests.
constexpr char kDummyAppId[] = "dummyappid_dummyappid_dummyappid";
constexpr int kSizeInDip = extension_misc::EXTENSION_ICON_MEDIUM;
std::unique_ptr<KeyedService> BuildTestSyncService(
content::BrowserContext* context) {
return std::make_unique<syncer::TestSyncService>();
}
std::vector<arc::mojom::AppInfoPtr> GetArcSettingsAppInfo() {
std::vector<arc::mojom::AppInfoPtr> apps;
arc::mojom::AppInfoPtr app(arc::mojom::AppInfo::New());
app->name = "settings";
app->package_name = "com.android.settings";
app->activity = "com.android.settings.Settings";
app->sticky = true;
apps.push_back(std::move(app));
return apps;
}
int GetPrimaryDisplayId() {
return display::Screen::GetScreen()->GetPrimaryDisplay().id();
}
bool ValidateImageIsFullyLoaded(const gfx::ImageSkia& image) {
if (kSizeInDip != image.width() || kSizeInDip != image.height())
return false;
const std::vector<ui::ResourceScaleFactor>& scale_factors =
ui::GetSupportedResourceScaleFactors();
for (auto& scale_factor : scale_factors) {
const float scale = ui::GetScaleForResourceScaleFactor(scale_factor);
if (!image.HasRepresentation(scale))
return false;
const gfx::ImageSkiaRep& representation = image.GetRepresentation(scale);
if (representation.is_null() ||
representation.pixel_width() != base::ClampCeil(kSizeInDip * scale) ||
representation.pixel_height() != base::ClampCeil(kSizeInDip * scale)) {
return false;
}
}
return true;
}
// Test implementation of AppIconLoader.
class TestAppIconLoaderImpl : public AppIconLoader {
public:
TestAppIconLoaderImpl() = default;
TestAppIconLoaderImpl(const TestAppIconLoaderImpl&) = delete;
TestAppIconLoaderImpl& operator=(const TestAppIconLoaderImpl&) = delete;
~TestAppIconLoaderImpl() override = default;
void AddSupportedApp(const std::string& id) { supported_apps_.insert(id); }
// AppIconLoader implementation:
bool CanLoadImageForApp(const std::string& id) override {
return supported_apps_.find(id) != supported_apps_.end();
}
void FetchImage(const std::string& id) override { ++fetch_count_; }
void ClearImage(const std::string& id) override { ++clear_count_; }
void UpdateImage(const std::string& id) override {}
int fetch_count() const { return fetch_count_; }
int clear_count() const { return clear_count_; }
private:
int fetch_count_ = 0;
int clear_count_ = 0;
std::set<std::string> supported_apps_;
};
// Fake AppServiceAppIconLoader to wait for icons loaded.
class FakeAppServiceAppIconLoader : public AppServiceAppIconLoader {
public:
FakeAppServiceAppIconLoader(Profile* profile,
int resource_size_in_dip,
AppIconLoaderDelegate* delegate)
: AppServiceAppIconLoader(profile, resource_size_in_dip, delegate) {}
void WaitForIconLoadded(
const std::vector<std::string>& expected_icon_loaded_app_ids) {
bool icon_loaded = true;
for (const auto& app_id : expected_icon_loaded_app_ids) {
if (!base::Contains(icon_loaded_app_ids_, app_id)) {
icon_loaded = false;
break;
}
}
if (icon_loaded) {
return;
}
expected_icon_loaded_app_ids_ = expected_icon_loaded_app_ids;
base::RunLoop run_loop;
icon_loaded_callback_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
// Callback invoked when the icon is loaded.
void OnLoadIcon(const std::string& app_id,
apps::IconValuePtr icon_value) override {
AppServiceAppIconLoader::OnLoadIcon(app_id, std::move(icon_value));
icon_loaded_app_ids_.insert(app_id);
bool icon_loaded = true;
for (const auto& id : expected_icon_loaded_app_ids_) {
if (!base::Contains(icon_loaded_app_ids_, id)) {
icon_loaded = false;
break;
}
}
if (icon_loaded && !icon_loaded_callback_.is_null()) {
std::move(icon_loaded_callback_).Run();
}
}
base::OnceClosure icon_loaded_callback_;
std::set<std::string> icon_loaded_app_ids_;
std::vector<std::string> expected_icon_loaded_app_ids_;
};
// Test implementation of ShelfControllerHelper.
class TestShelfControllerHelper : public ShelfControllerHelper {
public:
TestShelfControllerHelper() : ShelfControllerHelper(nullptr) {}
explicit TestShelfControllerHelper(Profile* profile)
: ShelfControllerHelper(profile) {}
TestShelfControllerHelper(const TestShelfControllerHelper&) = delete;
TestShelfControllerHelper& operator=(const TestShelfControllerHelper&) =
delete;
~TestShelfControllerHelper() override = default;
// Sets the id for the specified tab.
void SetAppID(content::WebContents* tab, const std::string& id) {
tab_id_map_[tab] = id;
}
// Returns true if there is an id registered for |tab|.
bool HasAppID(content::WebContents* tab) const {
return tab_id_map_.find(tab) != tab_id_map_.end();
}
// ShelfControllerHelper:
std::string GetAppID(content::WebContents* tab) override {
return tab_id_map_.find(tab) != tab_id_map_.end() ? tab_id_map_[tab]
: std::string();
}
bool IsValidIDForCurrentUser(const std::string& id) const override {
for (TabToStringMap::const_iterator i = tab_id_map_.begin();
i != tab_id_map_.end(); ++i) {
if (i->second == id)
return true;
}
return false;
}
ArcAppListPrefs* GetArcAppListPrefs() const override { return nullptr; }
private:
typedef std::map<content::WebContents*, std::string> TabToStringMap;
TabToStringMap tab_id_map_;
};
// Test implementation of a V2 app shelf item controller.
class TestV2AppShelfItemController : public ash::ShelfItemDelegate {
public:
explicit TestV2AppShelfItemController(const std::string& app_id)
: ash::ShelfItemDelegate(ash::ShelfID(app_id)) {}
TestV2AppShelfItemController(const TestV2AppShelfItemController&) = delete;
TestV2AppShelfItemController& operator=(const TestV2AppShelfItemController&) =
delete;
~TestV2AppShelfItemController() override = default;
// Override for ash::ShelfItemDelegate:
void ItemSelected(std::unique_ptr<ui::Event> event,
int64_t display_id,
ash::ShelfLaunchSource source,
ItemSelectedCallback callback,
const ItemFilterPredicate& filter_predicate) override {
std::move(callback).Run(ash::SHELF_ACTION_WINDOW_ACTIVATED, {});
}
void ExecuteCommand(bool, int64_t, int32_t, int64_t) override {}
void Close() override {}
};
// A fake that uses a testing profile.
class FakeChromeShelfItemFactory : public ChromeShelfItemFactory {
public:
explicit FakeChromeShelfItemFactory(Profile* profile)
: ChromeShelfItemFactory(), profile_(profile) {}
FakeChromeShelfItemFactory(const FakeChromeShelfItemFactory&) = delete;
FakeChromeShelfItemFactory& operator=(const FakeChromeShelfItemFactory&) =
delete;
~FakeChromeShelfItemFactory() override = default;
private:
Profile* GetPrimaryProfile() override { return profile_; }
const raw_ptr<Profile, ExperimentalAsh> profile_;
};
// Simulates selection of the shelf item.
void SelectItem(ash::ShelfItemDelegate* delegate) {
std::unique_ptr<ui::Event> event = std::make_unique<ui::MouseEvent>(
ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(),
ui::EF_NONE, 0);
delegate->ItemSelected(std::move(event), display::kInvalidDisplayId,
ash::LAUNCH_FROM_UNKNOWN, base::DoNothing(),
base::NullCallback());
}
bool IsWindowOnDesktopOfUser(aura::Window* window,
const AccountId& account_id) {
return MultiUserWindowManagerHelper::GetInstance()->IsWindowOnDesktopOfUser(
window, account_id);
}
void UpdateAppRegistryCache(Profile* profile,
const std::string& app_id,
bool block,
bool pause,
absl::optional<bool> show_in_shelf) {
std::vector<apps::AppPtr> apps;
apps::AppPtr app =
std::make_unique<apps::App>(apps::AppType::kChromeApp, app_id);
app->app_id = app_id;
if (block)
app->readiness = apps::Readiness::kDisabledByPolicy;
else
app->readiness = apps::Readiness::kReady;
if (pause)
app->paused = true;
else
app->paused = false;
if (show_in_shelf.has_value())
app->show_in_shelf = show_in_shelf;
apps.push_back(std::move(app));
apps::AppServiceProxyFactory::GetForProfile(profile)
->AppRegistryCache()
.OnApps(std::move(apps), apps::AppType::kChromeApp,
false /* should_notify_initialized */);
}
} // namespace
class ChromeShelfControllerTestBase : public BrowserWithTestWindowTest,
public apps::AppRegistryCache::Observer {
protected:
ChromeShelfControllerTestBase()
: BrowserWithTestWindowTest(Browser::TYPE_NORMAL) {}
void SetUp() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
// Prevent preinstalled apps from installing so these tests can control when
// they are installed.
command_line->AppendSwitch(switches::kDisableDefaultApps);
ash::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr);
web_app::PreinstalledWebAppManager::SkipStartupForTesting();
app_list::AppListSyncableServiceFactory::SetUseInTesting(true);
BrowserWithTestWindowTest::SetUp();
model_ = std::make_unique<ash::ShelfModel>();
base::Value::Dict manifest;
manifest.SetByDottedPath(extensions::manifest_keys::kName,
"launcher controller test extension");
manifest.SetByDottedPath(extensions::manifest_keys::kVersion, "1");
manifest.SetByDottedPath(extensions::manifest_keys::kManifestVersion, 2);
manifest.SetByDottedPath(extensions::manifest_keys::kDescription,
"for testing pinned apps");
// AppService checks the app's type. So set the
// manifest_keys::kLaunchWebURL, so that the extension can get the type
// from manifest value, and then AppService can get the extension's type.
manifest.SetByDottedPath(extensions::manifest_keys::kLaunchWebURL,
kLaunchURL);
base::Value::Dict manifest_platform_app;
manifest_platform_app.SetByDottedPath(
extensions::manifest_keys::kName,
"launcher controller test platform app");
manifest_platform_app.SetByDottedPath(extensions::manifest_keys::kVersion,
"1");
manifest_platform_app.SetByDottedPath(
extensions::manifest_keys::kDescription,
"for testing pinned platform apps");
base::Value::List scripts;
scripts.Append("main.js");
manifest_platform_app.SetByDottedPath(
extensions::manifest_keys::kPlatformAppBackgroundScripts,
std::move(scripts));
SyncServiceFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating(&BuildTestSyncService));
extensions::TestExtensionSystem* extension_system(
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile())));
extension_service_ = extension_system->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
extension_service_->Init();
DCHECK(profile());
extension_registry_ = extensions::ExtensionRegistry::Get(profile());
app_service_test_.SetUp(profile());
Observe(&(apps::AppServiceProxyFactory::GetForProfile(profile())
->AppRegistryCache()));
if (auto_start_arc_test_)
arc_test_.SetUp(profile());
// Wait until |extension_system| is signaled as started.
base::RunLoop run_loop;
extension_system->ready().Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
app_list_syncable_service_ =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
StartAppSyncService(app_list_syncable_service_->GetAllSyncDataForTesting());
std::string error;
extension_chrome_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, app_constants::kChromeAppId, &error);
extension1_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", &error);
extension2_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", &error);
extension5_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "cccccccccccccccccccccccccccccccc", &error);
extension6_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "dddddddddddddddddddddddddddddddd", &error);
extension7_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", &error);
extension8_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, "ffffffffffffffffffffffffffffffff", &error);
extension_platform_app_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest_platform_app,
Extension::NO_FLAGS, "gggggggggggggggggggggggggggggggg", &error);
arc_support_host_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, arc::kPlayStoreAppId, &error);
extension_service_->AddExtension(extension_chrome_.get());
// Fake File Manager app.
extension_files_app_ = Extension::Create(
base::FilePath(), ManifestLocation::kUnpacked, manifest,
Extension::NO_FLAGS, extension_misc::kFilesManagerAppId, &error);
if (StartWebAppProviderForMainProfile())
StartWebAppProvider(profile());
}
virtual bool StartWebAppProviderForMainProfile() const { return true; }
void StartWebAppProvider(Profile* profile) {
auto* provider = web_app::FakeWebAppProvider::Get(profile);
auto* system_web_app_manager = ash::TestSystemWebAppManager::Get(profile);
provider->SetRunSubsystemStartupTasks(true);
provider->Start();
system_web_app_manager->ScheduleStart();
base::RunLoop run_loop;
provider->on_external_managers_synchronized().Post(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
}
// Note that this resets previously installed SWAs.
void InstallSystemWebApp(
std::unique_ptr<ash::SystemWebAppDelegate> delegate) {
auto* system_web_app_manager =
ash::SystemWebAppManager::GetForTest(profile());
ash::SystemWebAppDelegateMap swa_map;
swa_map.emplace(delegate->GetType(), std::move(delegate));
system_web_app_manager->SetSystemAppsForTesting(std::move(swa_map));
system_web_app_manager->InstallSystemAppsForTesting();
}
ui::BaseWindow* GetLastActiveWindowForItemController(
AppWindowShelfItemController* item_controller) {
return item_controller->last_active_window_;
}
// Creates a running platform V2 app (not pinned) of type |app_id|.
virtual void CreateRunningV2App(const std::string& app_id) {
DCHECK(!test_controller_);
// Change the created shelf item controller into a V2 app controller.
std::unique_ptr<TestV2AppShelfItemController> controller =
std::make_unique<TestV2AppShelfItemController>(app_id);
test_controller_ = controller.get();
ash::ShelfID id = shelf_controller_->InsertAppItem(
std::move(controller), ash::STATUS_RUNNING, model_->item_count(),
ash::TYPE_APP);
DCHECK(shelf_controller_->IsPlatformApp(id));
}
// Sets the stage for a multi user test.
virtual void SetUpMultiUserScenario(syncer::SyncChangeList* user_a,
syncer::SyncChangeList* user_b) {
InitShelfController();
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Set an empty pinned pref to begin with.
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, app_constants::kChromeAppId);
SendPinChanges(sync_list, true);
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Assume all applications have been added already.
AddWebApp(web_app::kGoogleDocsAppId);
AddWebApp(web_app::kGmailAppId);
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
extension_service_->AddExtension(extension5_.get());
extension_service_->AddExtension(extension6_.get());
extension_service_->AddExtension(extension7_.get());
extension_service_->AddExtension(extension8_.get());
extension_service_->AddExtension(extension_platform_app_.get());
// There should be nothing in the list by now.
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Set user a preferences.
InsertAddPinChange(user_a, 0, extension1_->id());
InsertAddPinChange(user_a, 1, extension2_->id());
InsertAddPinChange(user_a, 2, web_app::kGmailAppId);
InsertAddPinChange(user_a, 3, extension_platform_app_->id());
InsertAddPinChange(user_a, 4, web_app::kGoogleDocsAppId);
InsertAddPinChange(user_a, 5, extension5_->id());
InsertAddPinChange(user_a, 6, app_constants::kChromeAppId);
// Set user b preferences.
InsertAddPinChange(user_b, 0, extension6_->id());
InsertAddPinChange(user_b, 1, extension7_->id());
InsertAddPinChange(user_b, 2, extension8_->id());
InsertAddPinChange(user_b, 3, app_constants::kChromeAppId);
}
void TearDown() override {
Observe(nullptr);
arc_test_.TearDown();
shelf_controller_ = nullptr;
shelf_item_factory_.reset();
BrowserWithTestWindowTest::TearDown();
ash::ConciergeClient::Shutdown();
app_list::AppListSyncableServiceFactory::SetUseInTesting(false);
}
std::unique_ptr<BrowserWindow> CreateBrowserWindow() override {
return std::unique_ptr<TestBrowserWindow>(CreateTestBrowserWindowAura());
}
std::unique_ptr<Browser> CreateBrowserWithTestWindowForProfile(
Profile* profile) {
TestBrowserWindow* browser_window = CreateTestBrowserWindowAura();
new TestBrowserWindowOwner(browser_window);
return CreateBrowser(profile, Browser::TYPE_NORMAL, false, browser_window);
}
// Create an uninitialized controller instance.
ChromeShelfController* CreateShelfController() {
shelf_item_factory_ =
std::make_unique<FakeChromeShelfItemFactory>(profile());
model_->SetShelfItemFactory(shelf_item_factory_.get());
shelf_controller_ = std::make_unique<ChromeShelfController>(
profile(), model_.get(), shelf_item_factory_.get());
shelf_controller_->SetProfileForTest(profile());
shelf_controller_->SetShelfControllerHelperForTest(
std::make_unique<ShelfControllerHelper>(profile()));
return shelf_controller_.get();
}
// Create and initialize the controller, owned by the test shell delegate.
void InitShelfController() { CreateShelfController()->Init(); }
// Create and initialize the controller; create a tab and show the browser.
void InitShelfControllerWithBrowser() {
InitShelfController();
chrome::NewTab(browser());
browser()->window()->Show();
}
// Destroy the controller instance and clear the local pointer.
void ResetShelfController() {
shelf_controller_.reset();
shelf_item_factory_.reset();
}
// Destroy and recreate the controller; clear and reinitialize the ShelfModel.
// Returns a pointer to the uninitialized controller, owned by shell delegate.
// TODO(msw): This does not accurately represent ChromeShelfController
// lifetime or usage in production, and does not accurately simulate restarts.
ChromeShelfController* RecreateShelfController() {
// Destroy any existing controller first; only one may exist at a time.
ResetShelfController();
model_ = std::make_unique<ash::ShelfModel>();
return CreateShelfController();
}
void StartAppSyncService(const syncer::SyncDataList& init_sync_list) {
app_list_syncable_service_->MergeDataAndStartSyncing(
syncer::APP_LIST, init_sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
EXPECT_EQ(init_sync_list.size(),
app_list_syncable_service_->sync_items().size());
}
void StopAppSyncService() {
app_list_syncable_service_->StopSyncing(syncer::APP_LIST);
}
sync_preferences::PrefModelAssociator* GetPrefSyncService() {
sync_preferences::PrefServiceSyncable* pref_sync =
profile()->GetTestingPrefService();
sync_preferences::PrefModelAssociator* pref_sync_service =
static_cast<sync_preferences::PrefModelAssociator*>(
pref_sync->GetSyncableService(syncer::OS_PREFERENCES));
return pref_sync_service;
}
void StartPrefSyncService(const syncer::SyncDataList& init_sync_list) {
absl::optional<syncer::ModelError> error =
GetPrefSyncService()->MergeDataAndStartSyncing(
syncer::OS_PREFERENCES, init_sync_list,
std::make_unique<syncer::FakeSyncChangeProcessor>());
EXPECT_FALSE(error.has_value());
}
syncer::SyncData CreateStringPrefsSyncData(const std::string& pref_name,
const std::string& pref_value) {
sync_pb::EntitySpecifics specifics;
specifics.mutable_os_preference()->mutable_preference()->set_name(
pref_name);
specifics.mutable_os_preference()->mutable_preference()->set_value(
base::StringPrintf("\"%s\"", pref_value.c_str()));
return syncer::SyncData::CreateRemoteData(
specifics, syncer::ClientTagHash::FromHashed("unused"));
}
void SetAppIconLoader(std::unique_ptr<AppIconLoader> loader) {
std::vector<std::unique_ptr<AppIconLoader>> loaders;
loaders.push_back(std::move(loader));
shelf_controller_->SetAppIconLoadersForTest(loaders);
}
void SetAppIconLoaders(std::unique_ptr<AppIconLoader> loader1,
std::unique_ptr<AppIconLoader> loader2) {
std::vector<std::unique_ptr<AppIconLoader>> loaders;
loaders.push_back(std::move(loader1));
loaders.push_back(std::move(loader2));
shelf_controller_->SetAppIconLoadersForTest(loaders);
}
void SetShelfControllerHelper(ShelfControllerHelper* helper) {
shelf_controller_->SetShelfControllerHelperForTest(
base::WrapUnique<ShelfControllerHelper>(helper));
}
void AppendPrefValue(base::Value::List& pref_values,
const std::string& policy_id) {
base::Value::Dict entry;
entry.Set(ChromeShelfPrefs::kPinnedAppsPrefAppIDKey, policy_id);
pref_values.Append(std::move(entry));
}
void RemovePrefValue(base::Value::List& pref_values,
const std::string& policy_id) {
pref_values.EraseIf([&policy_id](const auto& entry) {
return *entry.GetDict().FindString(
ChromeShelfPrefs::kPinnedAppsPrefAppIDKey) == policy_id;
});
}
void InsertRemoveAllPinsChange(syncer::SyncChangeList* list) {
for (const auto& sync_peer : app_list_syncable_service_->sync_items()) {
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(sync_peer.first);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
syncer::SyncData sync_data =
syncer::SyncData::CreateLocalData(sync_peer.first, "Test", specifics);
list->push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_DELETE, sync_data));
}
}
syncer::StringOrdinal GeneratePinPosition(int position) {
syncer::StringOrdinal ordinal_position =
syncer::StringOrdinal::CreateInitialOrdinal();
for (int i = 0; i < position; ++i)
ordinal_position = ordinal_position.CreateAfter();
return ordinal_position;
}
void InsertPinChange(syncer::SyncChangeList* list,
int position,
bool add_pin_change,
const std::string& app_id,
syncer::SyncChange::SyncChangeType type) {
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(app_id);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
if (add_pin_change) {
if (position >= 0) {
app_list_specifics->set_item_pin_ordinal(
GeneratePinPosition(position).ToInternalValue());
} else {
app_list_specifics->set_item_pin_ordinal(std::string());
}
}
syncer::SyncData sync_data =
syncer::SyncData::CreateLocalData(app_id, "Test", specifics);
list->push_back(syncer::SyncChange(FROM_HERE, type, sync_data));
}
void InsertAddPinChange(syncer::SyncChangeList* list,
int position,
const std::string& app_id) {
InsertPinChange(list, position, true, app_id,
syncer::SyncChange::ACTION_ADD);
}
void InsertUpdatePinChange(syncer::SyncChangeList* list,
int position,
const std::string& app_id) {
InsertPinChange(list, position, true, app_id,
syncer::SyncChange::ACTION_UPDATE);
}
void InsertRemovePinChange(syncer::SyncChangeList* list,
const std::string& app_id) {
InsertPinChange(list, -1, true, app_id, syncer::SyncChange::ACTION_UPDATE);
}
void InsertLegacyPinChange(syncer::SyncChangeList* list,
const std::string& app_id) {
InsertPinChange(list, -1, false, app_id, syncer::SyncChange::ACTION_UPDATE);
}
void ResetPinModel() {
syncer::SyncChangeList sync_list;
InsertRemoveAllPinsChange(&sync_list);
InsertAddPinChange(&sync_list, 0, kDummyAppId);
app_list_syncable_service_->ProcessSyncChanges(FROM_HERE, sync_list);
}
void SendPinChanges(const syncer::SyncChangeList& sync_list,
bool reset_pin_model) {
if (!reset_pin_model) {
app_list_syncable_service_->ProcessSyncChanges(FROM_HERE, sync_list);
} else {
syncer::SyncChangeList combined_sync_list;
InsertRemoveAllPinsChange(&combined_sync_list);
combined_sync_list.insert(combined_sync_list.end(), sync_list.begin(),
sync_list.end());
app_list_syncable_service_->ProcessSyncChanges(FROM_HERE,
combined_sync_list);
}
content::RunAllTasksUntilIdle();
}
// Set the index at which the chrome icon should be.
void SetShelfChromeIconIndex(int index) {
DCHECK(
app_list_syncable_service_->GetPinPosition(app_constants::kChromeAppId)
.IsValid());
syncer::StringOrdinal chrome_position;
chrome_position = index == 0 ? GeneratePinPosition(0).CreateBefore()
: GeneratePinPosition(index - 1).CreateBetween(
GeneratePinPosition(index));
syncer::SyncChangeList sync_list;
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list_specifics =
specifics.mutable_app_list();
app_list_specifics->set_item_id(app_constants::kChromeAppId);
app_list_specifics->set_item_type(sync_pb::AppListSpecifics::TYPE_APP);
app_list_specifics->set_item_pin_ordinal(chrome_position.ToInternalValue());
syncer::SyncData sync_data = syncer::SyncData::CreateLocalData(
app_constants::kChromeAppId, "Test", specifics);
sync_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data));
app_list_syncable_service_->ProcessSyncChanges(FROM_HERE, sync_list);
content::RunAllTasksUntilIdle();
}
// Gets the IDs of the currently pinned app items.
void GetPinnedAppIds(ChromeShelfController* controller,
std::vector<std::string>* app_ids) {
app_ids->clear();
for (const auto& item : model_->items()) {
if (item.type == ash::TYPE_PINNED_APP)
app_ids->push_back(item.id.app_id);
}
}
// Get the setup of the currently shown shelf items in one string.
// Each pinned element will start with a big letter, each running but not
// pinned V1 app will start with a small letter and each running but not
// pinned V2 app will start with a '*' + small letter.
std::string GetPinnedAppStatus() {
std::string result;
for (int i = 0; i < model_->item_count(); i++) {
if (!result.empty())
result.append(", ");
switch (model_->items()[i].type) {
case ash::TYPE_APP: {
if (shelf_controller_->IsPlatformApp(model_->items()[i].id))
result += "*";
const std::string& app = model_->items()[i].id.app_id;
EXPECT_FALSE(shelf_controller_->IsAppPinned(app));
if (app == extension1_->id()) {
result += "app1";
} else if (app == extension2_->id()) {
result += "app2";
} else if (app == extension5_->id()) {
result += "app5";
} else if (app == extension6_->id()) {
result += "app6";
} else if (app == extension7_->id()) {
result += "app7";
} else if (app == extension8_->id()) {
result += "app8";
} else if (app == web_app::kGmailAppId) {
result += "gmail";
} else if (app == extension_platform_app_->id()) {
result += "platform_app";
} else if (app == web_app::kGoogleDocsAppId) {
result += "doc";
} else if (app == web_app::kYoutubeAppId) {
result += "youtube";
} else {
result += app_service_test_.GetAppName(app);
}
break;
}
case ash::TYPE_PINNED_APP: {
if (shelf_controller_->IsPlatformApp(model_->items()[i].id))
result += "*";
const std::string& app = model_->items()[i].id.app_id;
EXPECT_TRUE(shelf_controller_->IsAppPinned(app));
if (app == extension1_->id()) {
result += "App1";
} else if (app == extension2_->id()) {
result += "App2";
} else if (app == extension5_->id()) {
result += "App5";
} else if (app == extension6_->id()) {
result += "App6";
} else if (app == extension7_->id()) {
result += "App7";
} else if (app == extension8_->id()) {
result += "App8";
} else if (app == web_app::kGmailAppId) {
result += "Gmail";
} else if (app == web_app::kGoogleCalendarAppId) {
result += "Calendar";
} else if (app == web_app::kGoogleDocsAppId) {
result += "Doc";
} else if (app == web_app::kMessagesAppId) {
result += "Messages";
} else if (app == web_app::kYoutubeAppId) {
result += "Youtube";
} else if (app == extension_files_app_->id()) {
result += "Files";
} else if (app == extension_platform_app_->id()) {
result += "Platform_App";
} else if (app == arc_support_host_->id()) {
result += "Play Store";
} else if (app == arc::kSettingsAppId) {
result += "Android Settings";
} else {
bool arc_app_found = false;
for (const auto& arc_app : arc_test_.fake_apps()) {
if (app == ArcAppTest::GetAppId(*arc_app)) {
result += arc_app->name;
arc_app_found = true;
break;
}
}
if (!arc_app_found) {
result += app_service_test_.GetAppName(app);
}
}
break;
}
case ash::TYPE_BROWSER_SHORTCUT:
result += "Chrome";
break;
default:
result += "Unknown";
break;
}
}
return result;
}
bool IsAppPolicyPinned(const std::string& app_id) {
return GetPinnableForAppID(app_id, profile()) ==
AppListControllerDelegate::PIN_FIXED;
}
// Returns the list containing app IDs of items shown in shelf. The order of
// IDs matches the order of associated shelf items in the shelf model.
std::vector<std::string> GetAppsShownInShelf() const {
std::vector<std::string> app_ids;
for (auto& item : model_->items())
app_ids.push_back(item.id.app_id);
return app_ids;
}
// Remember the order of unpinned but running applications for the current
// user.
void RememberUnpinnedRunningApplicationOrder() {
shelf_controller_->RememberUnpinnedRunningApplicationOrder();
}
// Restore the order of running but unpinned applications for a given user.
void RestoreUnpinnedRunningApplicationOrder(const AccountId& account_id) {
shelf_controller_->RestoreUnpinnedRunningApplicationOrder(
account_id.GetUserEmail());
}
void SendListOfArcApps() {
arc_test_.app_instance()->SendRefreshAppList(arc_test_.fake_apps());
}
void SendListOfArcShortcuts() {
arc_test_.app_instance()->SendInstallShortcuts(arc_test_.fake_shortcuts());
}
void UninstallArcApps() {
arc_test_.app_instance()->SendRefreshAppList(
std::vector<arc::mojom::AppInfoPtr>());
}
// TODO(victorhsieh): Add test coverage for when ARC is started regardless
// Play Store opt-in status, and the followed opt-in and opt-out.
void EnablePlayStore(bool enabled) {
arc::SetArcPlayStoreEnabledForProfile(profile(), enabled);
base::RunLoop().RunUntilIdle();
}
void ValidateArcState(bool arc_enabled,
bool arc_managed,
arc::ArcSessionManager::State state,
const std::string& pin_status) {
EXPECT_EQ(arc_enabled, arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_EQ(arc_managed,
arc::IsArcPlayStoreEnabledPreferenceManagedForProfile(profile()));
EXPECT_EQ(state, arc_test_.arc_session_manager()->state());
EXPECT_EQ(pin_status, GetPinnedAppStatus());
}
// Creates app window and set optional ARC application id.
views::Widget* CreateArcWindow(const std::string& window_app_id) {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(5, 5, 20, 20);
params.context = GetContext();
views::Widget* widget = new views::Widget();
widget->Init(std::move(params));
// Set ARC id before showing the window to be recognized in
// AppServiceAppWindowArcTracker.
exo::SetShellApplicationId(widget->GetNativeWindow(), window_app_id);
widget->Show();
widget->Activate();
return widget;
}
arc::mojom::AppInfoPtr CreateAppInfo(const std::string& name,
const std::string& activity,
const std::string& package_name) {
auto appinfo = arc::mojom::AppInfo::New();
appinfo->name = name;
appinfo->package_name = package_name;
appinfo->activity = activity;
return appinfo;
}
std::string AddArcAppAndShortcut(const arc::mojom::AppInfo& app_info) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
absl::optional<uint64_t> app_size_in_bytes;
absl::optional<uint64_t> data_size_in_bytes;
if (!app_info.app_storage.is_null()) {
app_size_in_bytes = app_info.app_storage->app_size_in_bytes;
data_size_in_bytes = app_info.app_storage->data_size_in_bytes;
}
// Adding app to the prefs, and check that the app is accessible by id.
prefs->AddAppAndShortcut(
app_info.name, app_info.package_name, app_info.activity,
std::string() /* intent_uri */, std::string() /* icon_resource_id */,
app_info.version_name, false /* sticky */,
true /* notifications_enabled */, true /* app_ready */,
false /* suspended */, false /* shortcut */, true /* launchable */,
false /* need_fixup */, ArcAppListPrefs::WindowLayout(),
app_size_in_bytes, data_size_in_bytes, app_info.app_category);
const std::string app_id =
ArcAppListPrefs::GetAppId(app_info.package_name, app_info.activity);
EXPECT_TRUE(prefs->GetApp(app_id));
return app_id;
}
void NotifyOnTaskCreated(const arc::mojom::AppInfo& appinfo,
int32_t task_id) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
prefs->OnTaskCreated(task_id, appinfo.package_name, appinfo.activity,
appinfo.name, std::string(), /*session_id=*/0);
}
// Creates a window with TYPE_APP shelf item type and the given app_id.
views::Widget* CreateShelfAppWindow(const std::string& app_id) {
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.context = GetContext();
params.bounds = gfx::Rect(5, 5, 20, 20);
views::Widget* widget = new views::Widget();
widget->Init(std::move(params));
aura::Window* window = widget->GetNativeWindow();
const ash::ShelfID shelf_id(app_id);
window->SetProperty(ash::kShelfIDKey, shelf_id.Serialize());
window->SetProperty<int>(ash::kShelfItemTypeKey, ash::TYPE_APP);
window->SetProperty(ash::kAppIDKey, app_id);
widget->Show();
widget->Activate();
return widget;
}
void NotifyOnTaskDestroyed(int32_t task_id) {
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
prefs->OnTaskDestroyed(task_id);
}
// Add extension.
void AddExtension(const Extension* extension) {
extension_service_->AddExtension(extension);
}
// Uninstall extension.
void UninstallExtension(const std::string& extension_id,
extensions::UninstallReason reason) {
extension_service_->UninstallExtension(extension_id, reason, nullptr);
}
// Unload extension.
void UnloadExtension(const std::string& extension_id,
UnloadedExtensionReason reason) {
extension_service_->UnloadExtension(extension_id, reason);
}
const GURL& GetWebAppUrl(const std::string& web_app_id) const {
static const base::flat_map<std::string, GURL> web_app_id_to_start_url{
{web_app::kGmailAppId,
GURL("https://mail.google.com/mail/?usp=installed_webapp")},
{web_app::kGoogleCalendarAppId,
GURL("https://calendar.google.com/calendar/r")},
{web_app::kGoogleDocsAppId,
GURL("https://docs.google.com/document/?usp=installed_webapp")},
{web_app::kMessagesAppId, GURL("https://messages.google.com/web/")},
{web_app::kYoutubeAppId,
GURL("https://www.youtube.com/?feature=ytca")}};
DCHECK(base::Contains(web_app_id_to_start_url, web_app_id));
return web_app_id_to_start_url.at(web_app_id);
}
void AddWebApp(const std::string& web_app_id) {
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->start_url = GetWebAppUrl(web_app_id);
web_app::AppId installed_app_id =
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
ASSERT_EQ(installed_app_id, web_app_id);
web_app::AppReadinessWaiter(profile(), web_app_id).Await();
}
web_app::AppId InstallExternalWebApp(
const GURL& start_url,
const absl::optional<GURL>& install_url = {}) {
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->start_url = GURL(start_url);
web_app_info->install_url = GURL(install_url ? *install_url : start_url);
const web_app::AppId expected_web_app_id = web_app::GenerateAppId(
/*manifest_id=*/absl::nullopt, web_app_info->start_url);
PrefService* prefs = browser()->profile()->GetPrefs();
web_app::ExternallyInstalledWebAppPrefs web_app_prefs(prefs);
web_app_prefs.Insert(GURL(web_app_info->install_url), expected_web_app_id,
web_app::ExternalInstallSource::kExternalPolicy);
// Ensure prefs are written before the web app install process reads
// them.
base::RunLoop run_loop;
prefs->CommitPendingWrite(run_loop.QuitClosure());
run_loop.Run();
web_app::AppId web_app_id = web_app::test::InstallWebApp(
profile(), std::move(web_app_info),
/*overwrite_existing_manifest_fields =*/false,
webapps::WebappInstallSource::EXTERNAL_POLICY);
DCHECK_EQ(expected_web_app_id, web_app_id);
return web_app_id;
}
web_app::AppId InstallExternalWebApp(
const std::string& start_url,
const absl::optional<std::string>& install_url = {}) {
return InstallExternalWebApp(GURL(start_url), install_url
? GURL(*install_url)
: absl::optional<GURL>());
}
void RemoveWebApp(const char* web_app_id) {
web_app::test::UninstallWebApp(profile(), web_app_id);
web_app::AppReadinessWaiter(profile(), web_app_id,
apps::Readiness::kUninstalledByUser)
.Await();
}
void OnAppRegistryCacheWillBeDestroyed(
apps::AppRegistryCache* cache) override {
Observe(nullptr);
}
apps::AppServiceTest& app_service_test() { return app_service_test_; }
// Needed for extension service & friends to work.
scoped_refptr<Extension> extension_chrome_;
scoped_refptr<Extension> extension1_;
scoped_refptr<Extension> extension2_;
scoped_refptr<Extension> extension5_;
scoped_refptr<Extension> extension6_;
scoped_refptr<Extension> extension7_;
scoped_refptr<Extension> extension8_;
scoped_refptr<Extension> extension_files_app_;
scoped_refptr<Extension> extension_platform_app_;
scoped_refptr<Extension> arc_support_host_;
ArcAppTest arc_test_;
bool auto_start_arc_test_ = false;
std::unique_ptr<ChromeShelfItemFactory> shelf_item_factory_;
std::unique_ptr<ChromeShelfController> shelf_controller_;
std::unique_ptr<ash::ShelfModel> model_;
// |item_delegate_manager_| owns |test_controller_|.
raw_ptr<ash::ShelfItemDelegate, ExperimentalAsh> test_controller_ = nullptr;
raw_ptr<extensions::ExtensionRegistry, ExperimentalAsh> extension_registry_ =
nullptr;
raw_ptr<extensions::ExtensionService, ExperimentalAsh> extension_service_ =
nullptr;
raw_ptr<app_list::AppListSyncableService, ExperimentalAsh>
app_list_syncable_service_ = nullptr;
private:
TestBrowserWindow* CreateTestBrowserWindowAura() {
std::unique_ptr<aura::Window> window(
new aura::Window(nullptr, aura::client::WINDOW_TYPE_NORMAL));
window->SetId(0);
window->Init(ui::LAYER_TEXTURED);
aura::client::ParentWindowWithContext(window.get(), GetContext(),
gfx::Rect(200, 200));
return new TestBrowserWindowAura(std::move(window));
}
apps::AppServiceTest app_service_test_;
};
class ChromeShelfControllerWithArcTest : public ChromeShelfControllerTestBase {
protected:
ChromeShelfControllerWithArcTest() { auto_start_arc_test_ = true; }
ChromeShelfControllerWithArcTest(const ChromeShelfControllerWithArcTest&) =
delete;
ChromeShelfControllerWithArcTest& operator=(
const ChromeShelfControllerWithArcTest&) = delete;
~ChromeShelfControllerWithArcTest() override = default;
void SetUp() override {
// To prevent crash on test exit and pending decode request.
ArcAppIcon::DisableSafeDecodingForTesting();
ChromeShelfControllerTestBase::SetUp();
}
};
class ChromeShelfControllerTest : public ChromeShelfControllerTestBase {
public:
ChromeShelfControllerTest() {
// `media_router::kMediaRouter` is disabled because it has unmet
// dependencies and is unrelated to this unit test.
feature_list_.InitAndDisableFeature(media_router::kMediaRouter);
}
~ChromeShelfControllerTest() override = default;
private:
// CrostiniTestHelper overrides feature list after GPU thread has started.
viz::TestGpuServiceHolder::ScopedAllowRacyFeatureListOverrides
gpu_thread_allow_racy_overrides_;
base::test::ScopedFeatureList feature_list_;
};
// Tests for Lacros integration. Exists as a separate class because the feature
// must be initialized before ChromeShelfControllerTestBase::SetUp().
class ChromeShelfControllerLacrosTest : public ChromeShelfControllerTestBase {
public:
ChromeShelfControllerLacrosTest() {
feature_list_.InitAndEnableFeature(ash::features::kLacrosSupport);
crosapi::browser_util::SetProfileMigrationCompletedForTest(true);
}
ChromeShelfControllerLacrosTest(const ChromeShelfControllerLacrosTest&) =
delete;
ChromeShelfControllerLacrosTest& operator=(
const ChromeShelfControllerLacrosTest&) = delete;
~ChromeShelfControllerLacrosTest() override = default;
// testing::Test:
void SetUp() override {
// Checking to see if Lacros is allowed requires a user.
auto user_manager = std::make_unique<ash::FakeChromeUserManager>();
auto* fake_user_manager = user_manager.get();
scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
std::move(user_manager));
// Login a user. The "email" must match the TestingProfile's
// GetProfileUserName() so that profile() will be the primary profile.
const AccountId account_id =
AccountId::FromUserEmail("testing_profile@test");
fake_user_manager->AddUser(account_id);
fake_user_manager->LoginUser(account_id);
// Creates profile().
ChromeShelfControllerTestBase::SetUp();
ASSERT_TRUE(ash::ProfileHelper::Get()->IsPrimaryProfile(profile()));
}
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};
class ChromeShelfControllerLacrosPrimaryTest
: public ChromeShelfControllerLacrosTest {
public:
ChromeShelfControllerLacrosPrimaryTest() = default;
ChromeShelfControllerLacrosPrimaryTest(
const ChromeShelfControllerLacrosPrimaryTest&) = delete;
ChromeShelfControllerLacrosPrimaryTest& operator=(
const ChromeShelfControllerLacrosPrimaryTest&) = delete;
~ChromeShelfControllerLacrosPrimaryTest() override = default;
void SetUp() override {
ChromeShelfControllerLacrosTest::SetUp();
proxy_ = apps::AppServiceProxyFactory::GetForProfile(profile());
ASSERT_TRUE(proxy_);
}
void AddChromeAppItem(const std::string& app_id, aura::Window* window) {
ash::ShelfItem item;
item.id = ash::ShelfID(app_id);
item.type = ash::TYPE_APP;
auto delegate =
std::make_unique<StandaloneBrowserExtensionAppShelfItemController>(
ash::ShelfID(kDummyAppId), window);
chrome_app_shelf_item_ = delegate.get();
ash::ShelfModel::Get()->Add(item, std::move(delegate));
}
// Verify instance for `window`. If `state` is not destroyed, verify `app_id`
// and `state`. Otherwise, verify there is no instance for `app_id` and
// `window`.
void VerifyInstance(const std::string& app_id,
aura::Window* window,
apps::InstanceState state) {
if (state != apps::InstanceState::kDestroyed) {
EXPECT_EQ(app_id, proxy_->InstanceRegistry().GetShelfId(window).app_id);
EXPECT_EQ(state, proxy_->InstanceRegistry().GetState(window));
} else {
EXPECT_FALSE(proxy_->InstanceRegistry().Exists(window));
}
}
StandaloneBrowserExtensionAppShelfItemController* chrome_app_shelf_item() {
return chrome_app_shelf_item_;
}
apps::AppServiceProxy* proxy() { return proxy_; }
private:
base::AutoReset<absl::optional<bool>> set_lacros_primary_ =
crosapi::browser_util::SetLacrosPrimaryBrowserForTest(true);
raw_ptr<StandaloneBrowserExtensionAppShelfItemController, ExperimentalAsh>
chrome_app_shelf_item_ = nullptr;
raw_ptr<apps::AppServiceProxy, ExperimentalAsh> proxy_ = nullptr;
};
// A V1 windowed application.
class V1App : public TestBrowserWindow {
public:
V1App(Profile* profile, const std::string& app_name) {
Browser::CreateParams params = Browser::CreateParams::CreateForApp(
kCrxAppPrefix + app_name, true /* trusted_source */, gfx::Rect(),
profile, true);
params.window = this;
browser_ = std::unique_ptr<Browser>(Browser::Create(params));
chrome::AddTabAt(browser_.get(), GURL(), 0, true);
}
V1App(const V1App&) = delete;
V1App& operator=(const V1App&) = delete;
~V1App() override {
// close all tabs. Note that we do not need to destroy the browser itself.
browser_->tab_strip_model()->CloseAllTabs();
}
Browser* browser() { return browser_.get(); }
private:
// The associated browser with this app.
std::unique_ptr<Browser> browser_;
};
// A V2 application window created with an |extension| and for a |profile|.
// Upon destruction it will properly close the application.
class V2App {
public:
V2App(Profile* profile,
const extensions::Extension* extension,
extensions::AppWindow::WindowType window_type =
extensions::AppWindow::WINDOW_TYPE_DEFAULT)
: creator_web_contents_(
content::WebContentsTester::CreateTestWebContents(profile,
nullptr)) {
window_ = new extensions::AppWindow(
profile, std::make_unique<ChromeAppDelegate>(profile, true), extension);
extensions::AppWindow::CreateParams params;
params.window_type = window_type;
// Note: normally, the creator RFH is the background page of the
// app/extension calling chrome.app.window.create. For unit testing
// purposes, just passing in a random RenderFrameHost is Good Enoughâ„¢.
window_->Init(GURL(std::string()),
std::make_unique<extensions::AppWindowContentsImpl>(window_),
creator_web_contents_->GetPrimaryMainFrame(), params);
}
V2App(const V2App&) = delete;
V2App& operator=(const V2App&) = delete;
virtual ~V2App() {
content::WebContentsDestroyedWatcher destroyed_watcher(
window_->web_contents());
window_->GetBaseWindow()->Close();
destroyed_watcher.Wait();
}
extensions::AppWindow* window() { return window_; }
private:
std::unique_ptr<content::WebContents> creator_web_contents_;
// The app window which represents the application. Note that the window
// deletes itself asynchronously after window_->GetBaseWindow()->Close() gets
// called.
raw_ptr<extensions::AppWindow, ExperimentalAsh> window_;
};
// The testing framework to test multi profile scenarios.
class MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest
: public ChromeShelfControllerTestBase {
protected:
MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest() {
// `kLacrosSupport` is disabled since Lacros does not support the ChromeOS
// Legacy multi profile feature. `kMediaRouter` is disabled because it has
// unmet dependencies and is unrelated to this unit test.
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{}, /*disabled_features=*/{
ash::features::kLacrosSupport, media_router::kMediaRouter});
}
MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest(
const MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest&) =
delete;
MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest& operator=(
const MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest&) =
delete;
~MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest() override =
default;
// Overwrite the Setup function to enable multi profile and needed objects.
void SetUp() override {
// Initialize the UserManager singleton to a fresh FakeUserManager instance.
user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>(
std::make_unique<ash::FakeChromeUserManager>());
// Initialize the rest.
ChromeShelfControllerTestBase::SetUp();
// Ensure there are multiple profiles. User 0 is created during setup.
CreateMultiUserProfile("user1");
ASSERT_TRUE(SessionControllerClientImpl::IsMultiProfileAvailable());
}
void TearDown() override {
ChromeShelfControllerTestBase::TearDown();
user_manager_enabler_.reset();
// A Task is leaked if we don't destroy everything, then run the message
// loop.
base::RunLoop().RunUntilIdle();
}
bool StartWebAppProviderForMainProfile() const override {
// The provider is started in CreateMultiUserProfile()
return false;
}
// Creates a profile for a given |user_name|. Note that this class will keep
// the ownership of the created object.
TestingProfile* CreateMultiUserProfile(const std::string& user_name) {
const std::string email_string = user_name + "@example.com";
const AccountId account_id(AccountId::FromUserEmail(email_string));
// Add a user to the fake user manager.
auto* user = GetFakeUserManager()->AddUser(account_id);
ash_test_helper()->test_session_controller_client()->AddUserSession(
user->GetDisplayEmail());
GetFakeUserManager()->LoginUser(account_id);
TestingProfile* profile =
profile_manager()->CreateTestingProfile(account_id.GetUserEmail());
EXPECT_TRUE(profile);
StartWebAppProvider(profile);
// Remember the profile name so that we can destroy it upon destruction.
created_profiles_[profile] = account_id.GetUserEmail();
if (MultiUserWindowManagerHelper::GetInstance())
MultiUserWindowManagerHelper::GetInstance()->AddUser(profile);
if (shelf_controller_)
shelf_controller_->AdditionalUserAddedToSession(profile);
return profile;
}
// Switch to another user.
void SwitchActiveUser(const AccountId& account_id) {
GetFakeUserManager()->SwitchActiveUser(account_id);
ash::MultiUserWindowManagerImpl::Get()->SetAnimationSpeedForTest(
ash::MultiUserWindowManagerImpl::ANIMATION_SPEED_DISABLED);
ash::MultiUserWindowManagerImpl::Get()->OnActiveUserSessionChanged(
account_id);
}
// Creates a browser with a |profile| and load a tab with a |title| and |url|.
std::unique_ptr<Browser> CreateBrowserAndTabWithProfile(
Profile* profile,
const std::string& title,
const std::string& url) {
std::unique_ptr<Browser> browser(
CreateBrowserWithTestWindowForProfile(profile));
chrome::NewTab(browser.get());
browser->window()->Show();
NavigateAndCommitActiveTabWithTitle(browser.get(), GURL(url),
ASCIIToUTF16(title));
return browser;
}
// Creates a running V1 application.
// Note that with the use of the shelf_controller_helper as done below,
// this is only usable with a single v1 application.
V1App* CreateRunningV1App(Profile* profile,
const std::string& app_name,
const std::string& url) {
V1App* v1_app = new V1App(profile, app_name);
NavigateAndCommitActiveTabWithTitle(v1_app->browser(), GURL(url),
std::u16string());
return v1_app;
}
// Override BrowserWithTestWindowTest:
TestingProfile* CreateProfile() override {
return CreateMultiUserProfile("user0");
}
private:
typedef std::map<Profile*, std::string> ProfileToNameMap;
ash::FakeChromeUserManager* GetFakeUserManager() {
return static_cast<ash::FakeChromeUserManager*>(
user_manager::UserManager::Get());
}
std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_;
ProfileToNameMap created_profiles_;
base::test::ScopedFeatureList scoped_feature_list_;
};
class ChromeShelfControllerMultiProfileWithArcTest
: public MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest {
protected:
ChromeShelfControllerMultiProfileWithArcTest() {
auto_start_arc_test_ = true;
}
ChromeShelfControllerMultiProfileWithArcTest(
const ChromeShelfControllerMultiProfileWithArcTest&) = delete;
ChromeShelfControllerMultiProfileWithArcTest& operator=(
const ChromeShelfControllerMultiProfileWithArcTest&) = delete;
~ChromeShelfControllerMultiProfileWithArcTest() override = default;
};
TEST_F(ChromeShelfControllerTest, DefaultShelfPrefValues) {
StartPrefSyncService(syncer::SyncDataList());
InitShelfController();
// Verify shelf prefs are initialized to default values if they're not set
// either locally nor in sync data.
PrefService* const prefs = browser()->profile()->GetPrefs();
EXPECT_EQ(ash::ShelfAlignment::kBottom,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kNever,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
// Verify that shelf pref values don't change locally if they change in sync
// after local value has been initialized.
syncer::SyncChangeList os_change_list;
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentLeft));
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAutoHideBehavior,
ash::kShelfAutoHideBehaviorAlways));
GetPrefSyncService()->ProcessSyncChanges(FROM_HERE, os_change_list);
EXPECT_EQ(ash::ShelfAlignment::kBottom,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kNever,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
}
TEST_F(ChromeShelfControllerTest, ShelfPrefsInitializedFromSyncData) {
// Add shelf prefs to synced pref data, and start syncing.
syncer::SyncDataList sync_list;
sync_list.push_back(CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentLeft));
sync_list.push_back(CreateStringPrefsSyncData(
ash::prefs::kShelfAutoHideBehavior, ash::kShelfAutoHideBehaviorAlways));
StartPrefSyncService(std::move(sync_list));
// Verify local shelf state is initialized to reflect values in sync data if
// shelf controller gets initialized after initial sync pref values have been
// received.
InitShelfController();
PrefService* const prefs = browser()->profile()->GetPrefs();
EXPECT_EQ(ash::ShelfAlignment::kLeft,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kAlways,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
// Verify further synced shelf prefs changes do not affect local shelf state.
syncer::SyncChangeList os_change_list;
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentBottom));
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAutoHideBehavior,
ash::kShelfAutoHideBehaviorNever));
GetPrefSyncService()->ProcessSyncChanges(FROM_HERE, os_change_list);
EXPECT_EQ(ash::ShelfAlignment::kLeft,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kAlways,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
}
TEST_F(ChromeShelfControllerTest,
ShelfControllerUpdatesShelfPrefAfterInitialPrefSync) {
// Initialize shelf controller, and verify shelf state reflects default values
// before initial synced prefs have been received.
InitShelfController();
PrefService* const prefs = browser()->profile()->GetPrefs();
EXPECT_EQ(ash::ShelfAlignment::kBottom,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kNever,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
// If initial sync data contains shelf prefs, local shelf state should be
// updated to reflect shelf state in the initial sync data.
syncer::SyncDataList sync_list;
sync_list.push_back(CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentLeft));
sync_list.push_back(CreateStringPrefsSyncData(
ash::prefs::kShelfAutoHideBehavior, ash::kShelfAutoHideBehaviorAlways));
StartPrefSyncService(std::move(sync_list));
EXPECT_EQ(ash::ShelfAlignment::kLeft,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kAlways,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
// Verify further synced shelf prefs changes do not affect local shelf state.
syncer::SyncChangeList os_change_list;
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentBottom));
os_change_list.emplace_back(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateStringPrefsSyncData(ash::prefs::kShelfAutoHideBehavior,
ash::kShelfAutoHideBehaviorNever));
GetPrefSyncService()->ProcessSyncChanges(FROM_HERE, os_change_list);
EXPECT_EQ(ash::ShelfAlignment::kLeft,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kAlways,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
}
TEST_F(ChromeShelfControllerTest, SyncedShelfPrefsDontOverrideLocalPref) {
// Initialize shelf prefs before shelf controller gets initialized.
PrefService* const prefs = browser()->profile()->GetPrefs();
ash::SetShelfAlignmentPref(prefs, GetPrimaryDisplayId(),
ash::ShelfAlignment::kLeft);
ash::SetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId(),
ash::ShelfAutoHideBehavior::kAlways);
// Set initial sync data to conflict with the local shelf prefs.
syncer::SyncDataList sync_list;
sync_list.push_back(CreateStringPrefsSyncData(ash::prefs::kShelfAlignment,
ash::kShelfAlignmentBottom));
sync_list.push_back(CreateStringPrefsSyncData(
ash::prefs::kShelfAutoHideBehavior, ash::kShelfAutoHideBehaviorNever));
StartPrefSyncService(std::move(sync_list));
// Initialize shelf controller, and verify shelf state reflects initially set
// local prefs.
InitShelfController();
EXPECT_EQ(ash::ShelfAlignment::kLeft,
ash::GetShelfAlignmentPref(prefs, GetPrimaryDisplayId()));
EXPECT_EQ(ash::ShelfAutoHideBehavior::kAlways,
ash::GetShelfAutoHideBehaviorPref(prefs, GetPrimaryDisplayId()));
}
TEST_F(ChromeShelfControllerTest, PreinstalledApps) {
InitShelfController();
// The model should only contain the browser shortcut item.
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Pinning the non-preinstalled app. It should appear at the end. No
// preinstalled app is currently installed.
extension_service_->AddExtension(extension1_.get());
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
// Install preinstalled apps in reverse order, compared how they are declared.
// However pin positions should be in the order as they declared. Note,
// preinstalled apps appear on shelf between manually pinned App1.
// Prefs are not yet synced. No default pin appears.
AddWebApp(web_app::kYoutubeAppId);
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
StartPrefSyncService(syncer::SyncDataList());
EXPECT_EQ("Chrome, Youtube, App1", GetPinnedAppStatus());
AddWebApp(web_app::kMessagesAppId);
EXPECT_EQ("Chrome, Messages, Youtube, App1", GetPinnedAppStatus());
AddExtension(extension_files_app_.get());
EXPECT_EQ("Chrome, Files, Messages, Youtube, App1", GetPinnedAppStatus());
AddWebApp(web_app::kGoogleCalendarAppId);
EXPECT_EQ("Chrome, Calendar, Files, Messages, Youtube, App1",
GetPinnedAppStatus());
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("Chrome, Gmail, Calendar, Files, Messages, Youtube, App1",
GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerLacrosTest, LacrosPinnedByDefault) {
InitShelfController();
EXPECT_EQ("Chrome, Lacros", GetPinnedAppStatus());
}
// Checks that AppService instance is updated appropriately for one Chrome app
// window.
TEST_F(ChromeShelfControllerLacrosPrimaryTest, ChromeAppWindow) {
InitShelfController();
auto window = std::make_unique<aura::Window>(nullptr);
window->Init(ui::LAYER_NOT_DRAWN);
AddChromeAppItem(kDummyAppId, window.get());
VerifyInstance(
kDummyAppId, window.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
// Set window visibility as true.
window->Show();
VerifyInstance(
kDummyAppId, window.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning |
apps::InstanceState::kVisible));
// Set window as activated.
chrome_app_shelf_item()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT,
window.get(), nullptr);
VerifyInstance(
kDummyAppId, window.get(),
static_cast<apps::InstanceState>(
apps::InstanceState::kStarted | apps::InstanceState::kRunning |
apps::InstanceState::kVisible | apps::InstanceState::kActive));
// Set window as inactivated.
chrome_app_shelf_item()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT,
nullptr, window.get());
VerifyInstance(
kDummyAppId, window.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning |
apps::InstanceState::kVisible));
// Set window visibility as false.
window->Hide();
VerifyInstance(
kDummyAppId, window.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
// Remove window.
window.reset();
VerifyInstance(kDummyAppId, window.get(), apps::InstanceState::kDestroyed);
}
// Checks that AppService instance is updated appropriately for multiple Chrome
// app windows.
TEST_F(ChromeShelfControllerLacrosPrimaryTest, ChromeAppWindows) {
InitShelfController();
auto window1 = std::make_unique<aura::Window>(nullptr);
window1->Init(ui::LAYER_NOT_DRAWN);
auto window2 = std::make_unique<aura::Window>(nullptr);
window2->Init(ui::LAYER_NOT_DRAWN);
AddChromeAppItem(kDummyAppId, window1.get());
VerifyInstance(
kDummyAppId, window1.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
// Add `window2`.
window2->Show();
chrome_app_shelf_item()->StartTrackingInstance(window2.get());
VerifyInstance(
kDummyAppId, window2.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning |
apps::InstanceState::kVisible));
// Set `window2` as activated.
chrome_app_shelf_item()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT,
window2.get(), window1.get());
VerifyInstance(
kDummyAppId, window2.get(),
static_cast<apps::InstanceState>(
apps::InstanceState::kStarted | apps::InstanceState::kRunning |
apps::InstanceState::kVisible | apps::InstanceState::kActive));
VerifyInstance(
kDummyAppId, window1.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
// Set `window1` as visible and activated.
window2->Hide();
window1->Show();
chrome_app_shelf_item()->OnWindowActivated(
wm::ActivationChangeObserver::ActivationReason::ACTIVATION_CLIENT,
window1.get(), window2.get());
VerifyInstance(
kDummyAppId, window1.get(),
static_cast<apps::InstanceState>(
apps::InstanceState::kStarted | apps::InstanceState::kRunning |
apps::InstanceState::kVisible | apps::InstanceState::kActive));
VerifyInstance(
kDummyAppId, window2.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
// Remove `window1`.
window1.reset();
VerifyInstance(kDummyAppId, window1.get(), apps::InstanceState::kDestroyed);
VerifyInstance(
kDummyAppId, window2.get(),
static_cast<apps::InstanceState>(apps::InstanceState::kStarted |
apps::InstanceState::kRunning));
EXPECT_TRUE(proxy()->InstanceRegistry().ContainsAppId(kDummyAppId));
// Remove `window2`.
window2.reset();
VerifyInstance(kDummyAppId, window2.get(), apps::InstanceState::kDestroyed);
EXPECT_FALSE(proxy()->InstanceRegistry().ContainsAppId(kDummyAppId));
}
// Regression test for crash. crbug.com/1296949
TEST_F(ChromeShelfControllerLacrosPrimaryTest, WithoutAppService) {
Profile* const controller_profile = profile()->GetOffTheRecordProfile(
Profile::OTRProfileID::CreateUniqueForTesting(),
/*create_if_needed=*/true);
EXPECT_FALSE(apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(
controller_profile));
ChromeShelfPrefs::SkipPinnedAppsFromSyncForTest();
ash::ShelfModel model;
FakeChromeShelfItemFactory shelf_item_factory(controller_profile);
ChromeShelfController(controller_profile, &model, &shelf_item_factory).Init();
}
TEST_F(ChromeShelfControllerWithArcTest, ArcAppsHiddenFromLaunchCanBePinned) {
InitShelfController();
// Register Android Settings.
arc::mojom::AppHost* app_host = arc_test_.arc_app_list_prefs();
app_host->OnAppListRefreshed(GetArcSettingsAppInfo());
// Pin Android settings.
PinAppWithIDToShelf(arc::kSettingsAppId);
EXPECT_EQ("Chrome, Android Settings", GetPinnedAppStatus());
// The pin should remain after syncing prefs. Play Store should now appear.
StartPrefSyncService(syncer::SyncDataList());
EXPECT_EQ("Chrome, Play Store, Android Settings", GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerWithArcTest, ArcAppPinCrossPlatformWorkflow) {
// Work on ARC disabled platform first.
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
const std::string arc_app_id3 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[2]);
InitShelfController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
AddWebApp(web_app::kGmailAppId);
// extension 1, 3 are pinned by user
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, arc_app_id1);
InsertAddPinChange(&sync_list, 2, extension2_->id());
InsertAddPinChange(&sync_list, 3, arc_app_id2);
InsertAddPinChange(&sync_list, 4, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
SetShelfChromeIconIndex(1);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("App1, Chrome, App2, Gmail", GetPinnedAppStatus());
// Persist pin state, we don't have active pin for ARC apps yet, but pin
// model should have it.
syncer::SyncDataList copy_sync_list =
app_list_syncable_service_->GetAllSyncDataForTesting();
ResetShelfController();
SendPinChanges(syncer::SyncChangeList(), true);
StopAppSyncService();
EXPECT_EQ(0U, app_list_syncable_service_->sync_items().size());
// Move to ARC enabled platform, restart syncing with stored data.
StartAppSyncService(copy_sync_list);
RecreateShelfController()->Init();
// Set FakeAppServiceAppIconLoader to wait for icons loaded.
auto fake_app_service_icon_loader =
std::make_unique<FakeAppServiceAppIconLoader>(
profile(), extension_misc::EXTENSION_ICON_MEDIUM,
shelf_controller_.get());
FakeAppServiceAppIconLoader* icon_loader = fake_app_service_icon_loader.get();
SetAppIconLoader(std::move(fake_app_service_icon_loader));
// Pins must be automatically updated.
SendListOfArcApps();
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("App1, Chrome, Fake App 0, App2, Fake App 1, Gmail",
GetPinnedAppStatus());
// Now move pins on ARC enabled platform.
model_->Move(0, 3);
model_->Move(2, 0);
model_->Move(2, 4);
model_->Move(3, 1);
EXPECT_EQ("App2, Fake App 1, Chrome, App1, Fake App 0, Gmail",
GetPinnedAppStatus());
// ARC apps, Fake App 1 and Fake App 0, are updated due to icon updates. So
// wait for ARC apps icon updated by AppService before reset sync service.
icon_loader->WaitForIconLoadded(
std::vector<std::string>{arc_app_id1, arc_app_id2});
copy_sync_list = app_list_syncable_service_->GetAllSyncDataForTesting();
ResetShelfController();
ResetPinModel();
SendPinChanges(syncer::SyncChangeList(), true);
StopAppSyncService();
EXPECT_EQ(0U, app_list_syncable_service_->sync_items().size());
// Move back to ARC disabled platform.
EnablePlayStore(false);
StartAppSyncService(copy_sync_list);
RecreateShelfController()->Init();
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("App2, Chrome, App1, Gmail", GetPinnedAppStatus());
// Now move/remove pins on ARC disabled platform.
model_->Move(3, 1);
shelf_controller_->UnpinAppWithID(extension2_->id());
EXPECT_EQ("Gmail, Chrome, App1", GetPinnedAppStatus());
EnablePlayStore(true);
SendListOfArcApps();
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id3));
EXPECT_EQ("Gmail, Fake App 1, Chrome, App1, Fake App 0",
GetPinnedAppStatus());
}
// Ensure correct merging of policy pinned apps and user pinned apps.
TEST_F(ChromeShelfControllerTest, MergePolicyAndUserPrefPinnedApps) {
InitShelfController();
// Install two versions of google docs with different install_urls.
const GURL& google_docs_start_url = GetWebAppUrl(web_app::kGoogleDocsAppId);
const GURL google_docs_install_url_v1{
base::StrCat({google_docs_start_url.spec(), "&v=1"})};
const GURL google_docs_install_url_v2{
base::StrCat({google_docs_start_url.spec(), "&v=2"})};
InstallExternalWebApp(/*start_url=*/google_docs_start_url,
/*install_url=*/google_docs_install_url_v1);
InstallExternalWebApp(/*start_url=*/google_docs_start_url,
/*install_url=*/google_docs_install_url_v2);
// Check that both values are propagated to PolicyIds().
std::vector<std::string> google_docs_install_urls = {
google_docs_install_url_v1.spec(),
google_docs_install_url_v2.spec(),
};
apps::AppServiceProxyFactory::GetForProfile(profile())
->AppRegistryCache()
.ForOneApp(web_app::kGoogleDocsAppId, [&google_docs_install_urls](
const auto& update) {
ASSERT_THAT(update.PolicyIds(), testing::UnorderedElementsAreArray(
google_docs_install_urls));
});
InstallExternalWebApp(GetWebAppUrl(web_app::kGmailAppId));
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension5_.get());
// extension 1, 3 are pinned by user
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, app_constants::kChromeAppId);
InsertAddPinChange(&sync_list, 2, web_app::kGmailAppId);
SendPinChanges(sync_list, /*reset_pin_model=*/true);
base::Value::List policy_value;
// extension 2 4 are pinned by policy
AppendPrefValue(policy_value, extension2_->id());
AppendPrefValue(policy_value, google_docs_install_url_v2.spec());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
// 2 is not pinned as it's not installed
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGoogleDocsAppId));
// install extension 2 and check
AddExtension(extension2_.get());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
// Check user can manually pin or unpin these apps
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(extension1_->id(), profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension2_->id(), profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(web_app::kGmailAppId, profile()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(web_app::kGoogleDocsAppId, profile()));
// Check the order of shelf pinned apps
EXPECT_EQ("App2, Doc, App1, Chrome, Gmail", GetPinnedAppStatus());
}
// Check that the restoration of shelf items is happening in the same order
// as the user has pinned them (on another system) when they are synced reverse
// order.
TEST_F(ChromeShelfControllerTest, RestorePreinstalledAppsReverseOrder) {
InitShelfController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Installing Gmail should add it to the shelf - behind the
// chrome icon.
ash::ShelfItem item;
AddWebApp(web_app::kGmailAppId);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ("Chrome, Gmail", GetPinnedAppStatus());
// Installing |extension2_| should add it to the shelf - behind the
// chrome icon, but in first location.
AddExtension(extension2_.get());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ("Chrome, App2, Gmail", GetPinnedAppStatus());
// Installing |extension1_| should add it to the shelf - behind the
// chrome icon, but in first location.
AddExtension(extension1_.get());
EXPECT_EQ("Chrome, App1, App2, Gmail", GetPinnedAppStatus());
}
// Check that the restoration of shelf items is happening in the same order
// as the user has pinned them (on another system) when they are synced random
// order.
TEST_F(ChromeShelfControllerTest, RestorePreinstalledAppsRandomOrder) {
InitShelfController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Installing |extension2_| should add it to the shelf - behind the
// chrome icon.
AddExtension(extension2_.get());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome, App2", GetPinnedAppStatus());
// Installing |extension1_| should add it to the shelf - behind the
// chrome icon, but in first location.
AddExtension(extension1_.get());
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
// Installing Gmail should add it to the shelf - behind the chrome icon,
// but in first location.
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("Chrome, App1, App2, Gmail", GetPinnedAppStatus());
}
// Check that the restoration of shelf items is happening in the same order
// as the user has pinned / moved them (on another system) when they are synced
// random order - including the chrome icon.
TEST_F(ChromeShelfControllerTest,
RestorePreinstalledAppsRandomOrderChromeMoved) {
InitShelfController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, app_constants::kChromeAppId);
InsertAddPinChange(&sync_list, 2, extension2_->id());
InsertAddPinChange(&sync_list, 3, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
// The model should only contain the browser shortcut and app list items.
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome", GetPinnedAppStatus());
// Installing |extension2_| should add it to the shelf - behind the
// chrome icon.
ash::ShelfItem item;
AddExtension(extension2_.get());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("Chrome, App2", GetPinnedAppStatus());
// Installing |extension1_| should add it to the shelf - behind the
// chrome icon, but in first location.
AddExtension(extension1_.get());
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_EQ("App1, Chrome, App2", GetPinnedAppStatus());
// Installing Gmail should add it to the shelf - behind the chrome icon,
// but in first location.
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("App1, Chrome, App2, Gmail", GetPinnedAppStatus());
}
// Check that syncing to a different state does the correct thing.
TEST_F(ChromeShelfControllerTest, RestorePreinstalledAppsResyncOrder) {
InitShelfController();
syncer::SyncChangeList sync_list0;
InsertAddPinChange(&sync_list0, 0, extension1_->id());
InsertAddPinChange(&sync_list0, 1, extension2_->id());
InsertAddPinChange(&sync_list0, 2, web_app::kGmailAppId);
SendPinChanges(sync_list0, true);
// The shelf layout has always one static item at the beginning (App List).
AddExtension(extension2_.get());
EXPECT_EQ("Chrome, App2", GetPinnedAppStatus());
AddExtension(extension1_.get());
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("Chrome, App1, App2, Gmail", GetPinnedAppStatus());
// Change the order with increasing chrome position and decreasing position.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list1, 1, extension1_->id());
InsertAddPinChange(&sync_list1, 2, extension2_->id());
InsertAddPinChange(&sync_list1, 3, app_constants::kChromeAppId);
SendPinChanges(sync_list1, true);
EXPECT_EQ("Gmail, App1, App2, Chrome", GetPinnedAppStatus());
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, extension2_->id());
InsertAddPinChange(&sync_list2, 1, web_app::kGmailAppId);
InsertAddPinChange(&sync_list2, 2, app_constants::kChromeAppId);
InsertAddPinChange(&sync_list2, 3, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("App2, Gmail, Chrome, App1", GetPinnedAppStatus());
// Check that the chrome icon can also be at the first possible location.
syncer::SyncChangeList sync_list3;
InsertAddPinChange(&sync_list3, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list3, 1, extension2_->id());
InsertAddPinChange(&sync_list3, 2, extension1_->id());
SendPinChanges(sync_list3, true);
EXPECT_EQ("Chrome, Gmail, App2, App1", GetPinnedAppStatus());
// Check that uninstalling of extensions works as expected.
UninstallExtension(extension1_->id(),
extensions::UninstallReason::UNINSTALL_REASON_FOR_TESTING);
EXPECT_EQ("Chrome, Gmail, App2", GetPinnedAppStatus());
// Check that an update of an extension does not crash the system.
UnloadExtension(extension2_->id(), UnloadedExtensionReason::UPDATE);
EXPECT_EQ("Chrome, Gmail, App2", GetPinnedAppStatus());
}
// Test the V1 app interaction flow: run it, activate it, close it.
TEST_F(ChromeShelfControllerTest, V1AppRunActivateClose) {
InitShelfController();
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is running should create a new shelf item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is running again should have no effect.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
// Reporting that the app is closed should remove its shelf item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is closed again should have no effect.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(1, model_->item_count());
}
// Test the V1 app interaction flow: pin it, run it, close it, unpin it.
TEST_F(ChromeShelfControllerTest, V1AppPinRunCloseUnpin) {
InitShelfController();
// The model should only contain the browser shortcut.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Pinning the app should create a new shelf item.
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is running should just update the existing item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is closed should just update the existing item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Unpinning the app should remove its shelf item.
shelf_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
}
// Test the V1 app interaction flow: run it, pin it, close it, unpin it.
TEST_F(ChromeShelfControllerTest, V1AppRunPinCloseUnpin) {
InitShelfController();
// The model should only contain the browser shortcut.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is running should create a new shelf item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Pinning the app should just update the existing item.
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is closed should just update the existing item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Unpinning the app should remove its shelf item.
shelf_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
}
// Test the V1 app interaction flow: pin it, run it, unpin it, close it.
TEST_F(ChromeShelfControllerTest, V1AppPinRunUnpinClose) {
InitShelfController();
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Pinning the app should create a new shelf item.
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is running should just update the existing item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Unpinning the app should just update the existing item.
shelf_controller_->UnpinAppWithID(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_NE(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
// Reporting that the app is closed should remove its shelf item.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(nullptr,
shelf_controller_->GetItem(ash::ShelfID(extension1_->id())));
}
// Ensure unpinned V1 app ordering is properly restored after user changes.
TEST_F(ChromeShelfControllerTest, CheckRunningV1AppOrder) {
InitShelfController();
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
// Add a few running applications.
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_RUNNING);
shelf_controller_->SetAppStatus(extension2_->id(), ash::STATUS_RUNNING);
shelf_controller_->SetAppStatus(web_app::kGmailAppId, ash::STATUS_RUNNING);
EXPECT_EQ(4, model_->item_count());
// Note that this not only checks the order of applications but also the
// running type.
EXPECT_EQ("Chrome, app1, app2, gmail", GetPinnedAppStatus());
// Remember the current order of applications for the current user.
const AccountId& current_account_id =
multi_user_util::GetAccountIdFromProfile(profile());
RememberUnpinnedRunningApplicationOrder();
// Switch some items and check that restoring a user which was not yet
// remembered changes nothing.
model_->Move(1, 2);
EXPECT_EQ("Chrome, app2, app1, gmail", GetPinnedAppStatus());
const AccountId second_fake_account_id(
AccountId::FromUserEmail("second-fake-user@fake.com"));
RestoreUnpinnedRunningApplicationOrder(second_fake_account_id);
EXPECT_EQ("Chrome, app2, app1, gmail", GetPinnedAppStatus());
// Restoring the stored user should however do the right thing.
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("Chrome, app1, app2, gmail", GetPinnedAppStatus());
// Switch again some items and even delete one - making sure that the missing
// item gets properly handled.
model_->Move(2, 3);
shelf_controller_->SetAppStatus(extension1_->id(), ash::STATUS_CLOSED);
EXPECT_EQ("Chrome, gmail, app2", GetPinnedAppStatus());
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("Chrome, app2, gmail", GetPinnedAppStatus());
// Check that removing more items does not crash and changes nothing.
shelf_controller_->SetAppStatus(extension2_->id(), ash::STATUS_CLOSED);
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("Chrome, gmail", GetPinnedAppStatus());
shelf_controller_->SetAppStatus(web_app::kGmailAppId, ash::STATUS_CLOSED);
RestoreUnpinnedRunningApplicationOrder(current_account_id);
EXPECT_EQ("Chrome", GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerWithArcTest, ArcDeferredLaunch) {
InitShelfController();
const arc::mojom::ShortcutInfo& shortcut = arc_test_.fake_shortcuts()[0];
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
const std::string arc_app_id3 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[2]);
const std::string arc_shortcut_id = ArcAppTest::GetAppId(shortcut);
SendListOfArcApps();
SendListOfArcShortcuts();
arc_test_.StopArcInstance();
const ash::ShelfID shelf_id_app_1(arc_app_id1);
const ash::ShelfID shelf_id_app_2(arc_app_id2);
const ash::ShelfID shelf_id_app_3(arc_app_id3);
const ash::ShelfID shelf_id_shortcut(arc_shortcut_id);
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_1));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_2));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_3));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_shortcut));
arc::LaunchApp(profile(), arc_app_id1, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
arc::LaunchApp(profile(), arc_app_id1, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
arc::LaunchApp(profile(), arc_app_id2, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
arc::LaunchApp(profile(), arc_app_id3, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
arc::LaunchApp(profile(), arc_shortcut_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_app_1));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_app_2));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_app_3));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_shortcut));
// We activated arc_app_id1 twice but expect one close for item controller
// stops launching request.
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(shelf_id_app_1);
ASSERT_NE(nullptr, item_delegate);
item_delegate->Close();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_1));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_app_2));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_app_3));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id_shortcut));
arc_test_.RestartArcInstance();
SendListOfArcApps();
base::RunLoop().RunUntilIdle();
// Now spinner controllers should go away together with shelf items and ARC
// app instance should receive request for launching apps and shortcuts.
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_1));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_2));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_app_3));
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id_shortcut));
EXPECT_EQ(0U, arc_test_.app_instance()->launch_requests().size());
ASSERT_EQ(3U, arc_test_.app_instance()->launch_intents().size());
EXPECT_GE(arc_test_.app_instance()->launch_intents()[0].find(
"component=fake.app.1/.activity;"),
0u);
EXPECT_GE(arc_test_.app_instance()->launch_intents()[0].find(
"S.org.chromium.arc.request.deferred.start="),
0u);
EXPECT_GE(arc_test_.app_instance()->launch_intents()[1].find(
"component=fake.app.2/.activity;"),
0u);
EXPECT_GE(arc_test_.app_instance()->launch_intents()[1].find(
"S.org.chromium.arc.request.deferred.start="),
0u);
EXPECT_EQ(arc_test_.app_instance()->launch_intents()[2].c_str(),
shortcut.intent_uri);
}
// Launch is canceled in case app becomes suspended.
TEST_F(ChromeShelfControllerWithArcTest, ArcDeferredLaunchForSuspendedApp) {
InitShelfController();
// Register app first.
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc_test_.fake_apps()[0]->Clone());
const std::string app_id = ArcAppTest::GetAppId(*apps[0]);
arc_test_.app_instance()->SendRefreshAppList(apps);
arc_test_.StopArcInstance();
// Restart ARC
arc_test_.RestartArcInstance();
// Deferred controller should be allocated on start.
const ash::ShelfID shelf_id(app_id);
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
// Send app with suspended state.
apps[0]->suspended = true;
arc_test_.app_instance()->SendRefreshAppList(apps);
// Controller automatically closed.
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
// And no launch request issued.
EXPECT_TRUE(arc_test_.app_instance()->launch_requests().empty());
}
// Ensure the spinner controller does not override the active app controller
// (crbug.com/701152).
TEST_F(ChromeShelfControllerWithArcTest, ArcDeferredLaunchForActiveApp) {
InitShelfController();
SendListOfArcApps();
arc_test_.StopArcInstance();
const std::string app_id = ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
PinAppWithIDToShelf(app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
const ash::ShelfID shelf_id(app_id);
const ash::ShelfItem* item = shelf_controller_->GetItem(shelf_id);
ASSERT_NE(nullptr, item);
EXPECT_EQ(ash::STATUS_CLOSED, item->status);
EXPECT_EQ(ash::TYPE_PINNED_APP, item->type);
// Play Store app is ARC app that might be represented by native Chrome
// platform app.
model_->ReplaceShelfItemDelegate(
shelf_id,
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id, shelf_controller_->app_service_app_window_controller()));
shelf_controller_->SetItemStatus(shelf_id, ash::STATUS_RUNNING);
// This launch request should be ignored in case of active app.
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Closing the app should leave a pinned but closed shelf item shortcut.
shelf_controller_->ReplaceWithAppShortcutOrRemove(shelf_id);
item = shelf_controller_->GetItem(shelf_id);
ASSERT_NE(nullptr, item);
EXPECT_EQ(ash::STATUS_CLOSED, item->status);
EXPECT_EQ(ash::TYPE_PINNED_APP, item->type);
// Now launch request should not be ignored.
arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
}
// TODO(crbug.com/915840): this test is flakey and/or often crashes.
TEST_F(ChromeShelfControllerMultiProfileWithArcTest, DISABLED_ArcMultiUser) {
SendListOfArcApps();
InitShelfController();
SetShelfControllerHelper(new TestShelfControllerHelper);
// App1 exists all the time.
// App2 is created when primary user is active and destroyed when secondary
// user is active.
// Gmail created when secondary user is active.
const std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
const std::string arc_app_id3 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[2]);
std::string window_app_id1("org.chromium.arc.1");
views::Widget* arc_window1 = CreateArcWindow(window_app_id1);
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
std::string window_app_id2("org.chromium.arc.2");
views::Widget* arc_window2 = CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, *arc_test_.fake_apps()[1],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
shelf_controller_->SetProfileForTest(profile2);
SwitchActiveUser(account_id2);
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
std::string window_app_id3("org.chromium.arc.3");
views::Widget* arc_window3 = CreateArcWindow(window_app_id3);
arc_test_.app_instance()->SendTaskCreated(3, *arc_test_.fake_apps()[2],
std::string());
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id3)));
arc_window2->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(2);
shelf_controller_->SetProfileForTest(profile());
SwitchActiveUser(account_id);
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id3)));
// Close active window to let test passes.
arc_window1->CloseNow();
arc_window3->CloseNow();
}
TEST_F(ChromeShelfControllerWithArcTest, ArcRunningApp) {
InitShelfController();
const std::string arc_app_id =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
SendListOfArcApps();
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
// Normal flow, create/destroy tasks.
std::string window_app_id1("org.chromium.arc.1");
std::string window_app_id2("org.chromium.arc.2");
std::string window_app_id3("org.chromium.arc.3");
CreateArcWindow(window_app_id1);
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
// Stopping bridge removes apps.
CreateArcWindow(window_app_id3);
arc_test_.app_instance()->SendTaskCreated(3, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
arc_test_.StopArcInstance();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
}
// Test race creation/deletion of ARC app.
// TODO(khmel): Remove after moving everything to wayland protocol.
TEST_F(ChromeShelfControllerWithArcTest, ArcRaceCreateClose) {
InitShelfController();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
SendListOfArcApps();
// ARC window created before and closed after mojom notification.
std::string window_app_id1("org.chromium.arc.1");
views::Widget* arc_window = CreateArcWindow(window_app_id1);
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
arc_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id1)));
// ARC window created after and closed before mojom notification.
std::string window_app_id2("org.chromium.arc.2");
arc_test_.app_instance()->SendTaskCreated(2, *arc_test_.fake_apps()[1],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
arc_window = CreateArcWindow(window_app_id2);
ASSERT_TRUE(arc_window);
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
arc_window->Close();
base::RunLoop().RunUntilIdle();
// Closing window does not close shelf item. It is closed on task destroy.
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
arc_test_.app_instance()->SendTaskDestroyed(2);
EXPECT_FALSE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id2)));
}
TEST_F(ChromeShelfControllerWithArcTest, ArcWindowRecreation) {
InitShelfController();
const std::string arc_app_id =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
SendListOfArcApps();
std::string window_app_id("org.chromium.arc.1");
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
for (int i = 0; i < 3; ++i) {
arc_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc_app_id)));
}
}
// Verifies edge cases when Extension app item controller may be overwritten by
// ARC app item controller and vice versa. This should not happen in normal
// cases but in case of ARC boot failure this may lead to such situation. This
// test verifies that dynamic change of app item controllers is safe. See more
// crbug.com/770005.
TEST_F(ChromeShelfControllerWithArcTest, OverrideAppItemController) {
extension_service_->AddExtension(arc_support_host_.get());
InitShelfController();
SendListOfArcApps();
arc::mojom::AppInfoPtr app_info = CreateAppInfo(
"Play Store", arc::kPlayStoreActivity, arc::kPlayStorePackage);
EXPECT_EQ(arc::kPlayStoreAppId, AddArcAppAndShortcut(*app_info));
std::string window_app_id("org.chromium.arc.1");
const ash::ShelfID play_store_shelf_id(arc::kPlayStoreAppId);
shelf_controller_->UnpinAppWithID(arc::kPlayStoreAppId);
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
// Try 4 different scenarios with different creation and destroying orders.
// Scenario 1: Create OptIn, Play Store. Destroy OptIn, Play Store.
{
std::unique_ptr<V2App> play_store_optin =
std::make_unique<V2App>(profile(), arc_support_host_.get(),
extensions::AppWindow::WINDOW_TYPE_DEFAULT);
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *app_info, std::string());
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
play_store_optin.reset();
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
arc_window->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
}
// Scenario 2: Create OptIn, Play Store. Destroy Play Store, OptIn.
{
std::unique_ptr<V2App> play_store_optin =
std::make_unique<V2App>(profile(), arc_support_host_.get(),
extensions::AppWindow::WINDOW_TYPE_DEFAULT);
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *app_info, std::string());
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
arc_window->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
play_store_optin.reset();
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
}
// Scenario 3: Create Play Store, OptIn. Destroy OptIn, Play Store.
{
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *app_info, std::string());
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
std::unique_ptr<V2App> play_store_optin =
std::make_unique<V2App>(profile(), arc_support_host_.get(),
extensions::AppWindow::WINDOW_TYPE_DEFAULT);
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
play_store_optin.reset();
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
arc_window->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
}
// Scenario 4: Create Play Store, OptIn. Destroy Play Store, OptIn.
{
views::Widget* arc_window = CreateArcWindow(window_app_id);
ASSERT_TRUE(arc_window);
arc_test_.app_instance()->SendTaskCreated(1, *app_info, std::string());
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
std::unique_ptr<V2App> play_store_optin =
std::make_unique<V2App>(profile(), arc_support_host_.get(),
extensions::AppWindow::WINDOW_TYPE_DEFAULT);
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
arc_window->CloseNow();
arc_test_.app_instance()->SendTaskDestroyed(1);
EXPECT_TRUE(shelf_controller_->GetItem(play_store_shelf_id));
play_store_optin.reset();
EXPECT_FALSE(shelf_controller_->GetItem(play_store_shelf_id));
}
}
// Validate that ARC app is pinned correctly and pin is removed automatically
// once app is uninstalled.
TEST_F(ChromeShelfControllerWithArcTest, ArcAppPin) {
InitShelfController();
const std::string arc_app_id =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
SendListOfArcApps();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
// Allow async callbacks to run.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
PinAppWithIDToShelf(extension1_->id());
PinAppWithIDToShelf(arc_app_id);
PinAppWithIDToShelf(extension2_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ("Chrome, App1, Fake App 0, App2", GetPinnedAppStatus());
UninstallArcApps();
// Allow async callbacks to run.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id));
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
SendListOfArcApps();
// Allow async callbacks to run.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id));
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
// Opt-Out/Opt-In remove item from the shelf.
PinAppWithIDToShelf(arc_app_id);
EXPECT_EQ("Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
EnablePlayStore(false);
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
EnablePlayStore(true);
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
SendListOfArcApps();
// Allow async callbacks to run.
base::RunLoop().RunUntilIdle();
EXPECT_EQ("Chrome, App1, App2, Fake App 0", GetPinnedAppStatus());
}
// Validates that ARC app pins persist across OptOut/OptIn.
TEST_F(ChromeShelfControllerWithArcTest, ArcAppPinOptOutOptIn) {
InitShelfController();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
SendListOfArcApps();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
PinAppWithIDToShelf(extension1_->id());
PinAppWithIDToShelf(arc_app_id2);
PinAppWithIDToShelf(extension2_->id());
PinAppWithIDToShelf(arc_app_id1);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_EQ("Chrome, App1, Fake App 1, App2, Fake App 0", GetPinnedAppStatus());
EnablePlayStore(false);
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id2));
EnablePlayStore(true);
SendListOfArcApps();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_EQ("Chrome, App1, Fake App 1, App2, Fake App 0", GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerWithArcTest, DISABLED_ArcCustomAppIcon) {
InitShelfController();
// Wait until other apps are updated to avoid race condition while accessing
// last updated item.
base::RunLoop().RunUntilIdle();
// Register fake ARC apps.
SendListOfArcApps();
// Use first fake ARC app for testing.
const std::string arc_app_id =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const ash::ShelfID arc_shelf_id(arc_app_id);
// Generate icon for the testing app and use compressed png content as test
// input. Take shortcut to separate from default app icon.
auto icon = arc_test_.app_instance()->GenerateIconResponse(
extension_misc::EXTENSION_ICON_SMALL, false /* app_icon */);
ASSERT_TRUE(icon);
ASSERT_TRUE(icon->icon_png_data.has_value());
EXPECT_FALSE(icon->icon_png_data->empty());
std::string png_data(icon->icon_png_data->begin(),
icon->icon_png_data->end());
// Some input that represents invalid png content.
std::string invalid_png_data("aaaaaa");
EXPECT_FALSE(shelf_controller_->GetItem(arc_shelf_id));
std::string window_app_id1("org.chromium.arc.1");
std::string window_app_id2("org.chromium.arc.2");
views::Widget* window1 = CreateArcWindow(window_app_id1);
ASSERT_TRUE(window1 && window1->GetNativeWindow());
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
views::Widget* window2 = CreateArcWindow(window_app_id2);
ASSERT_TRUE(window2 && window2->GetNativeWindow());
arc_test_.app_instance()->SendTaskCreated(2, *arc_test_.fake_apps()[0],
std::string());
EXPECT_TRUE(shelf_controller_->GetItem(arc_shelf_id));
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(arc_shelf_id);
ASSERT_TRUE(item_delegate);
base::RunLoop().RunUntilIdle();
auto get_icon = [=]() {
return *shelf_controller_->GetItem(arc_shelf_id)->image.bitmap();
};
const SkBitmap default_icon = get_icon();
// No custom icon set. Acitivating windows should not change icon.
window1->Activate();
EXPECT_TRUE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
window2->Activate();
EXPECT_TRUE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
// Set custom icon on active item. Icon should change to custom.
arc_test_.app_instance()->SendTaskDescription(2, std::string(), png_data);
const SkBitmap custom_icon = get_icon();
EXPECT_FALSE(gfx::test::AreBitmapsEqual(default_icon, custom_icon));
// Switch back to the item without custom icon. Icon should be changed to
// default.
window1->Activate();
EXPECT_TRUE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
// Test that setting an invalid icon should not change custom icon.
arc_test_.app_instance()->SendTaskDescription(1, std::string(), png_data);
EXPECT_TRUE(gfx::test::AreBitmapsEqual(custom_icon, get_icon()));
arc_test_.app_instance()->SendTaskDescription(1, std::string(),
invalid_png_data);
EXPECT_TRUE(gfx::test::AreBitmapsEqual(custom_icon, get_icon()));
// Check window removing with active custom icon. Reseting custom icon of
// inactive window doesn't reset shelf icon.
arc_test_.app_instance()->SendTaskDescription(2, std::string(),
std::string());
EXPECT_TRUE(gfx::test::AreBitmapsEqual(custom_icon, get_icon()));
// Set custom icon back to validate closing active window later.
arc_test_.app_instance()->SendTaskDescription(2, std::string(), png_data);
EXPECT_TRUE(gfx::test::AreBitmapsEqual(custom_icon, get_icon()));
// Reseting custom icon of active window resets shelf icon.
arc_test_.app_instance()->SendTaskDescription(1, std::string(),
std::string());
// Wait for default icon load.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
window1->CloseNow();
EXPECT_TRUE(gfx::test::AreBitmapsEqual(custom_icon, get_icon()));
}
TEST_F(ChromeShelfControllerWithArcTest, ArcWindowPackageName) {
InitShelfController();
SendListOfArcApps();
std::string window_app_id1("org.chromium.arc.1");
std::string window_app_id2("org.chromium.arc.2");
std::string window_app_id3("org.chromium.arc.3");
views::Widget* arc_window1 = CreateArcWindow(window_app_id1);
arc_test_.app_instance()->SendTaskCreated(1, *arc_test_.fake_apps()[0],
std::string());
const std::string* package_name1 =
arc_window1->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey);
ASSERT_TRUE(package_name1);
EXPECT_EQ(*package_name1, arc_test_.fake_apps()[0]->package_name);
views::Widget* arc_window2 = CreateArcWindow(window_app_id2);
arc_test_.app_instance()->SendTaskCreated(2, *arc_test_.fake_apps()[1],
std::string());
const std::string* package_name2 =
arc_window2->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey);
ASSERT_TRUE(package_name2);
EXPECT_EQ(*package_name2, arc_test_.fake_apps()[1]->package_name);
// Create another window with the same package name.
views::Widget* arc_window3 = CreateArcWindow(window_app_id3);
arc_test_.app_instance()->SendTaskCreated(3, *arc_test_.fake_apps()[1],
std::string());
const std::string* package_name3 =
arc_window3->GetNativeWindow()->GetProperty(ash::kArcPackageNameKey);
ASSERT_TRUE(package_name3);
EXPECT_EQ(*package_name3, arc_test_.fake_apps()[1]->package_name);
arc_window1->CloseNow();
arc_window2->CloseNow();
arc_window3->CloseNow();
}
// Check that with multi profile V1 apps are properly added / removed from the
// shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V1AppUpdateOnUserSwitch) {
// Create a browser item in the controller.
InitShelfController();
EXPECT_EQ(1, model_->item_count());
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile(), extension_misc::kGmailAppId, kGmailUrl));
EXPECT_EQ(2, model_->item_count());
// After switching to a second user the item should be gone.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
// After switching back the item should be back.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
// Note we destroy now the gmail app with the closure end.
}
EXPECT_EQ(1, model_->item_count());
}
// Check edge cases with multi profile V1 apps in the shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V1AppUpdateOnUserSwitchEdgecases) {
// Create a browser item in the controller.
InitShelfController();
// First test: Create an app when the user is not active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile2, extension_misc::kGmailAppId, kGmailUrl));
EXPECT_EQ(1, model_->item_count());
// However - switching to the user should show it.
SwitchActiveUser(account_id2);
EXPECT_EQ(2, model_->item_count());
// Second test: Remove the app when the user is not active and see that it
// works.
SwitchActiveUser(account_id);
EXPECT_EQ(1, model_->item_count());
// Note: the closure ends and the browser will go away.
}
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(1, model_->item_count());
}
// Check edge case where a visiting V1 app gets closed (crbug.com/321374).
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V1CloseOnVisitingDesktop) {
// Create a browser item in the controller.
InitShelfController();
// First create an app when the user is active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(CreateRunningV1App(
profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
EXPECT_EQ(2, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
}
// After the app was destroyed, switch back. (which caused already a crash).
SwitchActiveUser(account_id);
// Create the same app again - which was also causing the crash.
EXPECT_EQ(1, model_->item_count());
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(CreateRunningV1App(
profile(), extension_misc::kGmailAppId, kGmailLaunchURL));
EXPECT_EQ(2, model_->item_count());
}
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
}
// Check edge cases with multi profile V1 apps in the shelf.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V1AppUpdateOnUserSwitchEdgecases2) {
// Create a browser item in the controller.
InitShelfController();
// First test: Create an app when the user is not active.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
SwitchActiveUser(account_id2);
{
// Create a "windowed gmail app".
std::unique_ptr<V1App> v1_app(
CreateRunningV1App(profile(), extension_misc::kGmailAppId, kGmailUrl));
EXPECT_EQ(1, model_->item_count());
// However - switching to the user should show it.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
// Second test: Remove the app when the user is not active and see that it
// works.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
v1_app.reset();
}
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
}
// Check that activating an item which is on another user's desktop, will bring
// it back.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
TestShelfActivationPullsBackWindow) {
// Create a browser item in the controller.
InitShelfController();
ash::MultiUserWindowManager* window_manager =
MultiUserWindowManagerHelper::GetWindowManager();
// Create a second test profile. The first is the one in profile() created in
// BrowserWithTestWindowTest::SetUp(). No need to add the profiles to the
// MultiUserWindowManagerHelper here. CreateMultiUserProfile() already does
// that.
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId current_user =
multi_user_util::GetAccountIdFromProfile(profile());
// Create a browser window with a native window for the current user.
std::unique_ptr<Browser> browser(
CreateBrowserWithTestWindowForProfile(profile()));
BrowserWindow* browser_window = browser->window();
aura::Window* window = browser_window->GetNativeWindow();
window_manager->SetWindowOwner(window, current_user);
// Check that an activation of the window on its owner's desktop does not
// change the visibility to another user.
shelf_controller_->ActivateWindowOrMinimizeIfActive(browser_window, false);
EXPECT_TRUE(IsWindowOnDesktopOfUser(window, current_user));
// Transfer the window to another user's desktop and check that activating it
// does pull it back to that user.
window_manager->ShowWindowForUser(
window, multi_user_util::GetAccountIdFromProfile(profile2));
EXPECT_FALSE(IsWindowOnDesktopOfUser(window, current_user));
shelf_controller_->ActivateWindowOrMinimizeIfActive(browser_window, false);
EXPECT_TRUE(IsWindowOnDesktopOfUser(window, current_user));
}
// Tests that web app icon is removed from shelf after user switch if the app is
// not installed by the new active user, even if the user has the URL associated
// with the app open in a tab.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
WebAppNotShownIfNotInstalledAfterUserSwitch) {
// Create a browser item in the controller.
InitShelfController();
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
constexpr char kWebAppUrl[] = "https://webappone.com/";
constexpr char kWebAppName[] = "WebApp1";
// Set up the test so
// * the primary user has a test app pinned to shelf, and
// * secondary user has a tab with the URL associated with the app open (but
// does not have the app installed).
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->start_url = GURL(kWebAppUrl);
web_app::AppId installed_app_id =
web_app::test::InstallWebApp(profile(), std::move(web_app_info));
PinAppWithIDToShelf(installed_app_id);
std::unique_ptr<Browser> profile2_browser =
CreateBrowserAndTabWithProfile(profile2, kWebAppName, kWebAppUrl);
EXPECT_EQ(
std::vector<std::string>({app_constants::kChromeAppId, installed_app_id}),
GetAppsShownInShelf());
// Switch to the secondary user, and verify the app only installed in the
// primary profile is removed from the model.
SwitchActiveUser(account_id2);
EXPECT_EQ(std::vector<std::string>({app_constants::kChromeAppId}),
GetAppsShownInShelf());
chrome::CloseTab(profile2_browser.get());
}
// Check that a running windowed V1 application will be properly pinned and
// unpinned when the order gets changed through a profile / policy change.
TEST_F(ChromeShelfControllerTest, RestoreDefaultAndRunningV1AppsResyncOrder) {
InitShelfController();
StartPrefSyncService(syncer::SyncDataList());
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
// The shelf layout has always one static item at the beginning (App List).
AddExtension(extension1_.get());
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
AddExtension(extension2_.get());
// No new app icon will be generated.
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
// Set the app status as running, which will add an unpinned item.
shelf_controller_->SetAppStatus(extension2_->id(), ash::STATUS_RUNNING);
EXPECT_EQ("Chrome, App1, app2", GetPinnedAppStatus());
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("Chrome, App1, Gmail, app2", GetPinnedAppStatus());
// Now request to pin all items, which will pin the running unpinned items.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list1, 1, extension2_->id());
InsertAddPinChange(&sync_list1, 2, extension1_->id());
SendPinChanges(sync_list1, true);
EXPECT_EQ("Chrome, Gmail, App2, App1", GetPinnedAppStatus());
// Removing the requirement for app 2 to be pinned should convert it back to
// running but not pinned. It should move towards the end of the shelf, after
// the pinned items, as determined by the |ShelfModel|'s weight system.
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list2, 1, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("Chrome, Gmail, App1, app2", GetPinnedAppStatus());
// Removing an item should simply close it and everything should shift.
syncer::SyncChangeList sync_list3;
InsertRemovePinChange(&sync_list3, extension1_->id());
SendPinChanges(sync_list3, false /* reset_pin_model */);
EXPECT_EQ("Chrome, Gmail, app2", GetPinnedAppStatus());
}
// Check that a running unpinned V2 application will be properly pinned and
// unpinned when the order gets changed through a profile / policy change.
TEST_F(ChromeShelfControllerTest, RestoreDefaultAndRunningV2AppsResyncOrder) {
InitShelfController();
syncer::SyncChangeList sync_list0;
InsertAddPinChange(&sync_list0, 0, extension1_->id());
InsertAddPinChange(&sync_list0, 1, web_app::kGmailAppId);
SendPinChanges(sync_list0, true);
// The shelf layout has always one static item at the beginning (app List).
AddExtension(extension1_.get());
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
AddExtension(extension_platform_app_.get());
// No new app icon will be generated.
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
// Add an unpinned but running V2 app.
CreateRunningV2App(extension_platform_app_->id());
EXPECT_EQ("Chrome, App1, *platform_app", GetPinnedAppStatus());
AddWebApp(web_app::kGmailAppId);
EXPECT_EQ("Chrome, App1, Gmail, *platform_app", GetPinnedAppStatus());
// Now request to pin all items, which should pin the running unpinned item.
syncer::SyncChangeList sync_list1;
InsertAddPinChange(&sync_list1, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list1, 1, extension_platform_app_->id());
InsertAddPinChange(&sync_list1, 2, extension1_->id());
SendPinChanges(sync_list1, true);
EXPECT_EQ("Chrome, Gmail, *Platform_App, App1", GetPinnedAppStatus());
// Removing the requirement for app 2 to be pinned should convert it back to
// running but not pinned. It should move towards the end of the shelf, after
// the pinned items, as determined by the |ShelfModel|'s weight system.
syncer::SyncChangeList sync_list2;
InsertAddPinChange(&sync_list2, 0, web_app::kGmailAppId);
InsertAddPinChange(&sync_list2, 1, extension1_->id());
SendPinChanges(sync_list2, true);
EXPECT_EQ("Chrome, Gmail, App1, *platform_app", GetPinnedAppStatus());
// Removing an item should simply close it and everything should shift.
syncer::SyncChangeList sync_list3;
InsertAddPinChange(&sync_list3, 0, web_app::kGmailAppId);
SendPinChanges(sync_list3, true);
EXPECT_EQ("Chrome, Gmail, *platform_app", GetPinnedAppStatus());
}
// Each user has a different set of applications pinned. Check that when
// switching between the two users, the state gets properly set.
TEST_F(ChromeShelfControllerTest, UserSwitchIconRestore) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Show user 1.
SendPinChanges(user_a, true);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, App5, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("App6, App7, App8, Chrome", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, App5, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("App6, App7, App8, Chrome", GetPinnedAppStatus());
}
// Each user has a different set of applications pinned, and one user has an
// application running. Check that when switching between the two users, the
// state gets properly set.
TEST_F(ChromeShelfControllerTest, UserSwitchIconRestoreWithRunningV2App) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Run the platform (V2) app.
CreateRunningV2App(extension_platform_app_->id());
// Show user 1.
SendPinChanges(user_a, true);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, App5, Chrome",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
EXPECT_EQ("App6, App7, App8, Chrome, *platform_app", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, App5, Chrome",
GetPinnedAppStatus());
// Switch back to 2.
SendPinChanges(user_b, true);
EXPECT_EQ("App6, App7, App8, Chrome, *platform_app", GetPinnedAppStatus());
}
// Each user has a different set of applications pinned, and one user has an
// application running. The chrome icon is not the last item in the list.
// Check that when switching between the two users, the state gets properly set.
// There was once a bug associated with this.
TEST_F(ChromeShelfControllerTest,
UserSwitchIconRestoreWithRunningV2AppChromeInMiddle) {
syncer::SyncChangeList user_a;
syncer::SyncChangeList user_b;
SetUpMultiUserScenario(&user_a, &user_b);
// Run the platform (V2) app.
CreateRunningV2App(extension_platform_app_->id());
// Show user 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, Chrome, App5",
GetPinnedAppStatus());
// Show user 2.
SendPinChanges(user_b, true);
SetShelfChromeIconIndex(3);
EXPECT_EQ("App6, App7, App8, Chrome, *platform_app", GetPinnedAppStatus());
// Switch back to 1.
SendPinChanges(user_a, true);
SetShelfChromeIconIndex(5);
EXPECT_EQ("App1, App2, Gmail, *Platform_App, Doc, Chrome, App5",
GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerTest, Policy) {
InstallSystemWebApp(std::make_unique<CameraSystemAppDelegate>(profile()));
extension_service_->AddExtension(extension2_.get());
AddWebApp(web_app::kYoutubeAppId);
// Here and below we pretend that Gmail is both a preinstalled and a
// force-installed app and instruct that "gmail" policy_id has to be treated
// accordingly. This means that both "gmail" and
// "https://mail.google.com/mail/?usp=installed_webapp" are valid policy_ids
// for web_app::kGmailAppId.
constexpr base::StringPiece kGmailPolicyId = "gmail";
base::flat_map<base::StringPiece, base::StringPiece>
preinstalled_web_apps_mapping;
preinstalled_web_apps_mapping.emplace(kGmailPolicyId, web_app::kGmailAppId);
apps_util::SetPreinstalledWebAppsMappingForTesting(
preinstalled_web_apps_mapping);
// Force-install Gmail.
auto gmail_start_url = GetWebAppUrl(web_app::kGmailAppId);
auto gmail_install_url = gmail_start_url;
InstallExternalWebApp(gmail_start_url, gmail_install_url);
// Pin policy should be initilized before controller start.
base::Value::List policy_value;
AppendPrefValue(policy_value, extension1_->id());
AppendPrefValue(policy_value, extension2_->id());
AppendPrefValue(policy_value,
std::string{*apps_util::GetPolicyIdForSystemWebAppType(
ash::SystemWebAppType::CAMERA)});
// Pin Gmail by its install_url (see above).
AppendPrefValue(policy_value, gmail_install_url.spec());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
InitShelfController();
// |extension2_|, Camera and Gmail should get pinned. |extension1_| is
// specified but not installed, and Youtube is part of the default set, but
// that shouldn't take effect when the policy override is in place.
EXPECT_EQ("Chrome, App2, Camera, Gmail", GetPinnedAppStatus());
EXPECT_TRUE(IsAppPolicyPinned(extension2_->id()));
EXPECT_TRUE(IsAppPolicyPinned(web_app::kCameraAppId));
EXPECT_TRUE(IsAppPolicyPinned(web_app::kGmailAppId));
// Installing |extension1_| should add it to the shelf. Note, App1 goes
// before App2 that is aligned with the pin order in policy.
AddExtension(extension1_.get());
EXPECT_EQ("Chrome, App1, App2, Camera, Gmail", GetPinnedAppStatus());
EXPECT_TRUE(IsAppPolicyPinned(extension1_->id()));
// Removing |extension1_| from the policy should not be reflected in the
// shelf and pin will exist.
RemovePrefValue(policy_value, extension1_->id());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_EQ("Chrome, App1, App2, Camera, Gmail", GetPinnedAppStatus());
EXPECT_FALSE(IsAppPolicyPinned(extension1_->id()));
// Remove Gmail from the policy. It should stay pinned, but is no longer
// fixed.
RemovePrefValue(policy_value, gmail_install_url.spec());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_EQ("Chrome, App1, App2, Camera, Gmail", GetPinnedAppStatus());
EXPECT_FALSE(IsAppPolicyPinned(web_app::kGmailAppId));
// Check that Gmail can also be pinned by direct mapping.
AppendPrefValue(policy_value, std::string(kGmailPolicyId));
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_EQ("Chrome, App1, App2, Camera, Gmail", GetPinnedAppStatus());
EXPECT_TRUE(IsAppPolicyPinned(web_app::kGmailAppId));
}
TEST_F(ChromeShelfControllerTest, UnpinWithUninstall) {
AddWebApp(web_app::kGmailAppId);
AddWebApp(web_app::kYoutubeAppId);
InitShelfController();
StartPrefSyncService(syncer::SyncDataList());
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kYoutubeAppId));
RemoveWebApp(web_app::kGmailAppId);
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kYoutubeAppId));
}
TEST_F(ChromeShelfControllerTest, SyncUpdates) {
extension_service_->AddExtension(extension2_.get());
AddWebApp(web_app::kGmailAppId);
AddWebApp(web_app::kGoogleDocsAppId);
InitShelfController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 10, app_constants::kChromeAppId);
SendPinChanges(sync_list, true);
std::vector<std::string> expected_pinned_apps;
std::vector<std::string> actual_pinned_apps;
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Unavailable extensions don't create shelf items.
sync_list.clear();
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 3, web_app::kGoogleDocsAppId);
SendPinChanges(sync_list, false);
expected_pinned_apps.push_back(extension2_->id());
expected_pinned_apps.push_back(web_app::kGoogleDocsAppId);
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertAddPinChange(&sync_list, 2, web_app::kGmailAppId);
SendPinChanges(sync_list, false);
expected_pinned_apps.insert(expected_pinned_apps.begin() + 1,
web_app::kGmailAppId);
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertUpdatePinChange(&sync_list, 0, web_app::kGoogleDocsAppId);
InsertUpdatePinChange(&sync_list, 1, web_app::kGmailAppId);
InsertUpdatePinChange(&sync_list, 2, extension2_->id());
SendPinChanges(sync_list, false);
std::reverse(expected_pinned_apps.begin(), expected_pinned_apps.end());
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Sending legacy sync change without pin info should not affect pin model.
sync_list.clear();
InsertLegacyPinChange(&sync_list, web_app::kGoogleDocsAppId);
SendPinChanges(sync_list, false);
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertRemovePinChange(&sync_list, web_app::kGoogleDocsAppId);
SendPinChanges(sync_list, false);
expected_pinned_apps.erase(expected_pinned_apps.begin());
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
sync_list.clear();
InsertRemovePinChange(&sync_list, web_app::kGmailAppId);
InsertRemovePinChange(&sync_list, extension2_->id());
SendPinChanges(sync_list, false);
expected_pinned_apps.clear();
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
}
TEST_F(ChromeShelfControllerTest, PendingInsertionOrder) {
extension_service_->AddExtension(extension1_.get());
AddWebApp(web_app::kGmailAppId);
InitShelfController();
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 2, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
std::vector<std::string> expected_pinned_apps;
expected_pinned_apps.push_back(extension1_->id());
expected_pinned_apps.push_back(web_app::kGmailAppId);
std::vector<std::string> actual_pinned_apps;
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
// Install |extension2| and verify it shows up between the other two.
AddExtension(extension2_.get());
expected_pinned_apps.insert(expected_pinned_apps.begin() + 1,
extension2_->id());
GetPinnedAppIds(shelf_controller_.get(), &actual_pinned_apps);
EXPECT_EQ(expected_pinned_apps, actual_pinned_apps);
}
// Ensure |controller| creates the expected menu items for the given shelf item.
void CheckAppMenu(ChromeShelfController* controller,
const ash::ShelfItem& item,
size_t expected_item_count,
std::u16string expected_item_titles[]) {
auto items = controller->GetAppMenuItemsForTesting(item);
ASSERT_EQ(expected_item_count, items.size());
for (size_t i = 0; i < expected_item_count; i++)
EXPECT_EQ(expected_item_titles[i], items[i].title);
}
// Check that browsers get reflected correctly in the shelf menu.
TEST_F(ChromeShelfControllerTest, BrowserMenuGeneration) {
EXPECT_EQ(1U, chrome::GetTotalBrowserCount());
chrome::NewTab(browser());
InitShelfController();
// Check that the browser list is empty at this time.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id = ash::ShelfID(app_constants::kChromeAppId);
CheckAppMenu(shelf_controller_.get(), item_browser, 0, nullptr);
// Now make the created browser() visible by showing its browser window.
browser()->window()->Show();
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL("http://test1"), title1);
std::u16string one_menu_item[] = {title1};
CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item);
// Create one more browser/window and check that one more was added.
std::unique_ptr<Browser> browser2(
CreateBrowserWithTestWindowForProfile(profile()));
chrome::NewTab(browser2.get());
browser2->window()->Show();
std::u16string title2 = u"Test2";
NavigateAndCommitActiveTabWithTitle(browser2.get(), GURL("http://test2"),
title2);
// Check that the list contains now two entries - make furthermore sure that
// the active item is the first entry.
std::u16string two_menu_items[] = {title1, title2};
CheckAppMenu(shelf_controller_.get(), item_browser, 2, two_menu_items);
// Apparently we have to close all tabs we have.
chrome::CloseTab(browser2.get());
}
// Check the multi profile case where only user related browsers should show up.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
BrowserMenuGenerationTwoUsers) {
// Create a browser item in the controller.
InitShelfController();
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id = ash::ShelfID(app_constants::kChromeAppId);
// Check that the menu is empty.
chrome::NewTab(browser());
CheckAppMenu(shelf_controller_.get(), item_browser, 0, nullptr);
// Show the created |browser()| by showing its window.
browser()->window()->Show();
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL("http://test1"), title1);
std::u16string one_menu_item1[] = {title1};
CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item1);
// Create a browser for another user and check that it is not included in the
// users running browser list.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
std::unique_ptr<Browser> browser2(
CreateBrowserAndTabWithProfile(profile2, user2, "http://test2"));
std::u16string one_menu_item2[] = {ASCIIToUTF16(user2)};
CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item1);
// Switch to the other user and make sure that only that browser window gets
// shown.
SwitchActiveUser(account_id2);
CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item2);
// Transferred browsers of other users should not show up in the list.
MultiUserWindowManagerHelper::GetWindowManager()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
CheckAppMenu(shelf_controller_.get(), item_browser, 1, one_menu_item2);
chrome::CloseTab(browser2.get());
}
// Check that V1 apps are correctly reflected in the shelf menu using the
// refocus logic.
// Note that the extension matching logic is tested by the extension system
// and does not need a separate test here.
TEST_F(ChromeShelfControllerTest, V1AppMenuGeneration) {
EXPECT_EQ(1U, chrome::GetTotalBrowserCount());
EXPECT_EQ(0, browser()->tab_strip_model()->count());
InitShelfControllerWithBrowser();
StartPrefSyncService(syncer::SyncDataList());
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
// Installing Gmail pins it to the shelf.
const ash::ShelfID gmail_id(web_app::kGmailAppId);
AddWebApp(web_app::kGmailAppId);
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
SetRefocusURL(gmail_id, GURL(kGmailUrl));
// Check the menu content.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id = ash::ShelfID(app_constants::kChromeAppId);
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
CheckAppMenu(shelf_controller_.get(), item_gmail, 0, nullptr);
// Set the gmail URL to a new tab.
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title1);
std::u16string one_menu_item[] = {title1};
CheckAppMenu(shelf_controller_.get(), item_gmail, 1, one_menu_item);
// Create one empty tab.
chrome::NewTab(browser());
std::u16string title2 = u"Test2";
NavigateAndCommitActiveTabWithTitle(browser(), GURL("https://bla"), title2);
// and another one with another gmail instance.
chrome::NewTab(browser());
std::u16string title3 = u"Test3";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title3);
std::u16string two_menu_items[] = {title1, title3};
CheckAppMenu(shelf_controller_.get(), item_gmail, 2, two_menu_items);
// Even though the item is in the V1 app list, it should also be in the
// browser list.
std::u16string browser_menu_item[] = {title3};
CheckAppMenu(shelf_controller_.get(), item_browser, 1, browser_menu_item);
// Test that closing of (all) the item(s) does work (and all menus get
// updated properly).
shelf_controller_->Close(item_gmail.id);
CheckAppMenu(shelf_controller_.get(), item_gmail, 0, nullptr);
std::u16string browser_menu_item2[] = {title2};
CheckAppMenu(shelf_controller_.get(), item_browser, 1, browser_menu_item2);
}
// Check the multi profile case where only user related apps should show up.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V1AppMenuGenerationTwoUsers) {
// Create a browser item in the controller.
InitShelfController();
StartPrefSyncService(syncer::SyncDataList());
chrome::NewTab(browser());
// Installing Gmail pins it to the shelf.
const ash::ShelfID gmail_id(web_app::kGmailAppId);
AddWebApp(web_app::kGmailAppId);
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app::kGmailAppId));
SetRefocusURL(gmail_id, GURL(kGmailUrl));
// Check the menu content.
ash::ShelfItem item_browser;
item_browser.type = ash::TYPE_BROWSER_SHORTCUT;
item_browser.id = ash::ShelfID(app_constants::kChromeAppId);
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
CheckAppMenu(shelf_controller_.get(), item_gmail, 0, nullptr);
// Set the gmail URL to a new tab.
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title1);
std::u16string one_menu_item[] = {title1};
CheckAppMenu(shelf_controller_.get(), item_gmail, 1, one_menu_item);
// Create a second profile and switch to that user.
std::string user2 = "user2";
TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
SwitchActiveUser(account_id2);
// No item should have content yet.
CheckAppMenu(shelf_controller_.get(), item_browser, 0, nullptr);
CheckAppMenu(shelf_controller_.get(), item_gmail, 0, nullptr);
// Transfer the browser of the first user - it should still not show up.
MultiUserWindowManagerHelper::GetWindowManager()->ShowWindowForUser(
browser()->window()->GetNativeWindow(), account_id2);
CheckAppMenu(shelf_controller_.get(), item_browser, 0, nullptr);
CheckAppMenu(shelf_controller_.get(), item_gmail, 0, nullptr);
}
// Check that V2 applications are creating items properly in the shelf when
// instantiated by the current user.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V2AppHandlingTwoUsers) {
InitShelfController();
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
// Check that there is a browser.
EXPECT_EQ(1, model_->item_count());
// Add a v2 app.
AddExtension(extension1_.get());
V2App v2_app(profile(), extension1_.get());
EXPECT_EQ(2, model_->item_count());
// Create a profile for our second user (will be destroyed by the framework).
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
// After switching users the item should go away.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
// And it should come back when switching back.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
}
// Check that V2 applications are creating items properly in edge cases:
// a background user creates a V2 app, gets active and inactive again and then
// deletes the app.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V2AppHandlingTwoUsersEdgeCases) {
InitShelfController();
// Create a profile for our second user (will be destroyed by the framework).
TestingProfile* profile2 = CreateMultiUserProfile("user2");
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
// Check that there is a browser, back button and an app.
EXPECT_EQ(1, model_->item_count());
// Switch to an inactive user.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
// Add the v2 app to the inactive user and check that no item was added to
// the shelf.
{
AddExtension(extension1_.get());
V2App v2_app(profile(), extension1_.get());
EXPECT_EQ(1, model_->item_count());
// Switch to the primary user and check that the item is shown.
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
// Switch to the second user and check that the item goes away - even if the
// item gets closed.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
}
// After the application was killed there should still be 1 item.
EXPECT_EQ(1, model_->item_count());
// Switching then back to the default user should not show the additional
// item anymore.
SwitchActiveUser(account_id);
EXPECT_EQ(1, model_->item_count());
}
TEST_F(ChromeShelfControllerTest, Active) {
InitShelfController();
// Creates a new app window.
int initial_item_count = model_->item_count();
AddExtension(extension1_.get());
V2App app_1(profile(), extension1_.get());
EXPECT_TRUE(app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_EQ(initial_item_count + 1, model_->item_count());
ash::ShelfItemDelegate* app_item_delegate_1 =
model_->GetShelfItemDelegate(model_->items()[initial_item_count].id);
ASSERT_TRUE(app_item_delegate_1);
AppWindowShelfItemController* app_item_controller_1 =
app_item_delegate_1->AsAppWindowShelfItemController();
ASSERT_TRUE(app_item_controller_1);
ui::BaseWindow* last_active =
GetLastActiveWindowForItemController(app_item_controller_1);
EXPECT_EQ(app_1.window()->GetNativeWindow(), last_active->GetNativeWindow());
// Change the status so that we can verify it gets reset when the active
// window changes.
shelf_controller_->SetItemStatus(app_item_delegate_1->shelf_id(),
ash::STATUS_ATTENTION);
// Creates another app window, which should become active and reset |app_1|'s
// status (to running).
AddExtension(extension2_.get());
V2App app_2(profile(), extension2_.get());
EXPECT_TRUE(app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_EQ(initial_item_count + 2, model_->item_count());
ash::ShelfItemDelegate* app_item_delegate_2 =
model_->GetShelfItemDelegate(model_->items()[initial_item_count + 1].id);
ASSERT_TRUE(app_item_delegate_2);
AppWindowShelfItemController* app_item_controller_2 =
app_item_delegate_2->AsAppWindowShelfItemController();
ASSERT_TRUE(app_item_controller_2);
last_active = GetLastActiveWindowForItemController(app_item_controller_2);
EXPECT_EQ(app_2.window()->GetNativeWindow(), last_active->GetNativeWindow());
const ash::ShelfItem* shelf_item_1 =
shelf_controller_->GetItem(app_item_delegate_1->shelf_id());
ASSERT_TRUE(shelf_item_1);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_item_1->status);
shelf_controller_->SetItemStatus(app_item_delegate_2->shelf_id(),
ash::STATUS_ATTENTION);
// Activate the first window, which should reset the status of the
// second apps window.
app_1.window()->GetBaseWindow()->Activate();
const ash::ShelfItem* shelf_item_2 =
shelf_controller_->GetItem(app_item_delegate_2->shelf_id());
ASSERT_TRUE(shelf_item_2);
EXPECT_EQ(ash::STATUS_RUNNING, shelf_item_2->status);
}
// Check that V2 applications will be made visible on the target desktop if
// another window of the same type got previously teleported there.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V2AppFollowsTeleportedWindow) {
InitShelfController();
ash::MultiUserWindowManager* window_manager =
MultiUserWindowManagerHelper::GetWindowManager();
// Create and add three users / profiles, and go to #1's desktop.
TestingProfile* profile1 = CreateMultiUserProfile("user-1");
TestingProfile* profile2 = CreateMultiUserProfile("user-2");
TestingProfile* profile3 = CreateMultiUserProfile("user-3");
const AccountId account_id1(
multi_user_util::GetAccountIdFromProfile(profile1));
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const AccountId account_id3(
multi_user_util::GetAccountIdFromProfile(profile3));
extensions::TestExtensionSystem* extension_system1(
static_cast<extensions::TestExtensionSystem*>(
extensions::ExtensionSystem::Get(profile1)));
extensions::ExtensionService* extension_service1 =
extension_system1->CreateExtensionService(
base::CommandLine::ForCurrentProcess(), base::FilePath(), false);
extension_service1->Init();
SwitchActiveUser(account_id1);
// A v2 app for user #1 should be shown first and get hidden when switching
// to desktop #2.
extension_service1->AddExtension(extension1_.get());
V2App v2_app_1(profile1, extension1_.get());
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
SwitchActiveUser(account_id2);
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
// Add a v2 app for user #1 while on desktop #2 should not be shown.
V2App v2_app_2(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
// Teleport the app from user #1 to the desktop #2 should show it.
window_manager->ShowWindowForUser(v2_app_1.window()->GetNativeWindow(),
account_id2);
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
// Creating a new application for user #1 on desktop #2 should teleport it
// there automatically.
V2App v2_app_3(profile1, extension1_.get());
EXPECT_TRUE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_3.window()->GetNativeWindow()->IsVisible());
// Switching back to desktop#1 and creating an app for user #1 should move
// the app on desktop #1.
SwitchActiveUser(account_id1);
V2App v2_app_4(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_3.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_4.window()->GetNativeWindow()->IsVisible());
// Switching to desktop #3 and creating an app for user #1 should place it
// on that user's desktop (#1).
SwitchActiveUser(account_id3);
V2App v2_app_5(profile1, extension1_.get());
EXPECT_FALSE(v2_app_5.window()->GetNativeWindow()->IsVisible());
SwitchActiveUser(account_id1);
EXPECT_TRUE(v2_app_5.window()->GetNativeWindow()->IsVisible());
// Switching to desktop #2, hiding the app window and creating an app should
// teleport there automatically.
SwitchActiveUser(account_id2);
v2_app_1.window()->Hide();
V2App v2_app_6(profile1, extension1_.get());
EXPECT_FALSE(v2_app_1.window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(v2_app_2.window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(v2_app_6.window()->GetNativeWindow()->IsVisible());
}
// Check that V2 applications hide correctly on the shelf when the app window
// is hidden.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
V2AppHiddenWindows) {
InitShelfController();
TestingProfile* profile2 = CreateMultiUserProfile("user-2");
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
// If switch to account_id2 is not run, the following switch to account_id
// is invalid, because the user account is not changed, so switch to
// account_id2 first.
SwitchActiveUser(account_id2);
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
SwitchActiveUser(account_id);
EXPECT_EQ(1, model_->item_count());
AddExtension(extension1_.get());
V2App v2_app_1(profile(), extension1_.get());
EXPECT_EQ(2, model_->item_count());
{
// Hide and show the app.
v2_app_1.window()->Hide();
EXPECT_EQ(1, model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(2, model_->item_count());
}
{
// Switch user, hide and show the app and switch back.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
v2_app_1.window()->Hide();
EXPECT_EQ(1, model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
}
{
// Switch user, hide the app, switch back and then show it again.
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
v2_app_1.window()->Hide();
EXPECT_EQ(1, model_->item_count());
SwitchActiveUser(account_id);
// The following expectation does not work in current impl. It was working
// before because MultiProfileSupport is not attached to user associated
// with profile() hence not actually handling windows for the user. It is
// a real bug. See http://crbug.com/693634 EXPECT_EQ(2,
// model_->item_count());
v2_app_1.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(2, model_->item_count());
}
{
// Create a second app, hide and show it and then hide both apps.
V2App v2_app_2(profile(), extension1_.get());
EXPECT_EQ(2, model_->item_count());
v2_app_2.window()->Hide();
EXPECT_EQ(2, model_->item_count());
v2_app_2.window()->Show(extensions::AppWindow::SHOW_ACTIVE);
EXPECT_EQ(2, model_->item_count());
v2_app_1.window()->Hide();
v2_app_2.window()->Hide();
EXPECT_EQ(1, model_->item_count());
}
}
// Checks that spinners are hidden and restored on profile switching
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
SpinnersUpdateOnUserSwitch) {
InitShelfController();
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const std::string user2 = "user2";
const TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const std::string app_id = extension1_->id();
extension_service_->AddExtension(extension1_.get());
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Add a spinner to the shelf
shelf_controller_->GetShelfSpinnerController()->AddSpinnerToShelf(
app_id, std::make_unique<ShelfSpinnerItemController>(app_id));
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Switch to a new profile
SwitchActiveUser(account_id2);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Switch back
SwitchActiveUser(account_id);
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Close the spinner
shelf_controller_->GetShelfSpinnerController()->CloseSpinner(app_id);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
}
// Checks that pinned spinners are hidden and restored on profile switching
// but are not removed when the spinner closes.
TEST_F(MultiProfileMultiBrowserShelfLayoutChromeShelfControllerTest,
PinnedSpinnersUpdateOnUserSwitch) {
InitShelfController();
const AccountId account_id(
multi_user_util::GetAccountIdFromProfile(profile()));
const std::string user2 = "user2";
const TestingProfile* profile2 = CreateMultiUserProfile(user2);
const AccountId account_id2(
multi_user_util::GetAccountIdFromProfile(profile2));
const std::string app_id = extension1_->id();
AddExtension(extension1_.get());
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Pin an app to the shelf
PinAppWithIDToShelf(app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Activate the spinner
shelf_controller_->GetShelfSpinnerController()->AddSpinnerToShelf(
app_id, std::make_unique<ShelfSpinnerItemController>(app_id));
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Switch to a new profile
SwitchActiveUser(account_id2);
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Switch back
SwitchActiveUser(account_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Close the spinner
shelf_controller_->GetShelfSpinnerController()->CloseSpinner(app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(2, model_->item_count());
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
}
// Checks that the generated menu list properly activates items.
TEST_F(ChromeShelfControllerTest, V1AppMenuExecution) {
InitShelfControllerWithBrowser();
StartPrefSyncService(syncer::SyncDataList());
// Add Gmail to the shelf and add two items.
GURL gmail = GURL("https://mail.google.com/mail/u");
const ash::ShelfID gmail_id(web_app::kGmailAppId);
AddWebApp(web_app::kGmailAppId);
SetRefocusURL(gmail_id, GURL(kGmailUrl));
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title1);
chrome::NewTab(browser());
std::u16string title2 = u"Test2";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title2);
// Check that the menu is properly set.
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
std::u16string two_menu_items[] = {title1, title2};
CheckAppMenu(shelf_controller_.get(), item_gmail, 2, two_menu_items);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(gmail_id);
ASSERT_TRUE(item_delegate);
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the second item in the menu, after the title,
// this shouldn't do anything since that item is already the active tab.
{
ash::ShelfApplicationMenuModel menu(
std::u16string(),
shelf_controller_->GetAppMenuItemsForTesting(item_gmail),
item_delegate);
menu.ActivatedAt(2);
}
EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
// Execute the first item in the menu, after the title,
// this should activate the other tab.
{
ash::ShelfApplicationMenuModel menu(
std::u16string(),
shelf_controller_->GetAppMenuItemsForTesting(item_gmail),
item_delegate);
menu.ActivatedAt(1);
}
EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
}
// Checks that the generated menu list properly deletes items.
TEST_F(ChromeShelfControllerTest, V1AppMenuDeletionExecution) {
InitShelfControllerWithBrowser();
StartPrefSyncService(syncer::SyncDataList());
// Add Gmail to the shelf and add two items.
const ash::ShelfID gmail_id(web_app::kGmailAppId);
AddWebApp(web_app::kGmailAppId);
SetRefocusURL(gmail_id, GURL(kGmailUrl));
std::u16string title1 = u"Test1";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title1);
chrome::NewTab(browser());
std::u16string title2 = u"Test2";
NavigateAndCommitActiveTabWithTitle(browser(), GURL(kGmailUrl), title2);
// Check that the menu is properly set.
ash::ShelfItem item_gmail;
item_gmail.type = ash::TYPE_PINNED_APP;
item_gmail.id = gmail_id;
std::u16string two_menu_items[] = {title1, title2};
CheckAppMenu(shelf_controller_.get(), item_gmail, 2, two_menu_items);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(gmail_id);
ASSERT_TRUE(item_delegate);
int tabs = browser()->tab_strip_model()->count();
// Activate the proper tab through the menu item.
{
auto items = shelf_controller_->GetAppMenuItemsForTesting(item_gmail);
item_delegate->ExecuteCommand(false, 1, ui::EF_NONE,
display::kInvalidDisplayId);
EXPECT_EQ(tabs, browser()->tab_strip_model()->count());
}
// Delete one tab through the menu item.
{
auto items = shelf_controller_->GetAppMenuItemsForTesting(item_gmail);
item_delegate->ExecuteCommand(false, 1, ui::EF_SHIFT_DOWN,
display::kInvalidDisplayId);
EXPECT_EQ(--tabs, browser()->tab_strip_model()->count());
}
}
// Verify that the shelf item positions are persisted and restored.
TEST_F(ChromeShelfControllerTest, PersistShelfItemPositions) {
InitShelfController();
TestShelfControllerHelper* helper = new TestShelfControllerHelper;
SetShelfControllerHelper(helper);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[0].type);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(0, tab_strip_model->count());
chrome::NewTab(browser());
chrome::NewTab(browser());
EXPECT_EQ(2, tab_strip_model->count());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2");
EXPECT_FALSE(shelf_controller_->IsAppPinned("1"));
PinAppWithIDToShelf("1");
EXPECT_TRUE(shelf_controller_->IsAppPinned("1"));
PinAppWithIDToShelf("2");
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[2].type);
// Move browser shortcut item from index 0 to index 2.
model_->Move(0, 2);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[2].type);
RecreateShelfController();
helper = new TestShelfControllerHelper(profile());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
helper->SetAppID(tab_strip_model->GetWebContentsAt(1), "2");
SetShelfControllerHelper(helper);
shelf_controller_->Init();
// Check ShelfItems are restored after resetting ChromeShelfController.
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[0].type);
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::TYPE_BROWSER_SHORTCUT, model_->items()[2].type);
}
// Verifies pinned apps are persisted and restored.
TEST_F(ChromeShelfControllerTest, PersistPinned) {
InitShelfControllerWithBrowser();
size_t initial_size = model_->items().size();
TabStripModel* tab_strip_model = browser()->tab_strip_model();
EXPECT_EQ(1, tab_strip_model->count());
TestShelfControllerHelper* helper = new TestShelfControllerHelper;
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
SetShelfControllerHelper(helper);
// app_icon_loader is owned by ChromeShelfController.
TestAppIconLoaderImpl* app_icon_loader = new TestAppIconLoaderImpl;
app_icon_loader->AddSupportedApp("1");
SetAppIconLoader(std::unique_ptr<AppIconLoader>(app_icon_loader));
EXPECT_EQ(0, app_icon_loader->fetch_count());
PinAppWithIDToShelf("1");
const int app_index = model_->ItemIndexByID(ash::ShelfID("1"));
EXPECT_EQ(1, app_icon_loader->fetch_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[app_index].type);
EXPECT_TRUE(shelf_controller_->IsAppPinned("1"));
EXPECT_FALSE(shelf_controller_->IsAppPinned("0"));
EXPECT_EQ(initial_size + 1, model_->items().size());
RecreateShelfController();
helper = new TestShelfControllerHelper(profile());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
SetShelfControllerHelper(helper);
// app_icon_loader is owned by ChromeShelfController.
app_icon_loader = new TestAppIconLoaderImpl;
app_icon_loader->AddSupportedApp("1");
SetAppIconLoader(std::unique_ptr<AppIconLoader>(app_icon_loader));
shelf_controller_->Init();
EXPECT_EQ(1, app_icon_loader->fetch_count());
ASSERT_EQ(initial_size + 1, model_->items().size());
EXPECT_TRUE(shelf_controller_->IsAppPinned("1"));
EXPECT_FALSE(shelf_controller_->IsAppPinned("0"));
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[app_index].type);
shelf_controller_->UnpinAppWithID("1");
EXPECT_EQ(initial_size + 1, model_->items().size());
tab_strip_model->CloseWebContentsAt(0, 0);
EXPECT_EQ(initial_size, model_->items().size());
}
// Verifies that ShelfID property is updated for browsers that are present when
// ChromeShelfController is created.
TEST_F(ChromeShelfControllerTest, ExistingBrowserWindowShelfIDSet) {
InitShelfControllerWithBrowser();
PinAppWithIDToShelf("1");
TabStripModel* tab_strip_model = browser()->tab_strip_model();
ASSERT_EQ(1, tab_strip_model->count());
TestShelfControllerHelper* helper = new TestShelfControllerHelper;
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "0");
SetShelfControllerHelper(helper);
RecreateShelfController();
helper = new TestShelfControllerHelper(profile());
helper->SetAppID(tab_strip_model->GetWebContentsAt(0), "1");
SetShelfControllerHelper(helper);
shelf_controller_->Init();
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID("1")));
EXPECT_EQ(ash::ShelfID("1"),
ash::ShelfID::Deserialize(
browser()->window()->GetNativeWindow()->GetProperty(
ash::kShelfIDKey)));
}
TEST_F(ChromeShelfControllerTest, MultipleAppIconLoaders) {
InitShelfControllerWithBrowser();
const ash::ShelfID shelf_id1(extension1_->id());
const ash::ShelfID shelf_id2(extension2_->id());
const ash::ShelfID shelf_id3(web_app::kGmailAppId);
// app_icon_loader1 and app_icon_loader2 are owned by
// ChromeShelfController.
TestAppIconLoaderImpl* app_icon_loader1 = new TestAppIconLoaderImpl();
TestAppIconLoaderImpl* app_icon_loader2 = new TestAppIconLoaderImpl();
app_icon_loader1->AddSupportedApp(shelf_id1.app_id);
app_icon_loader2->AddSupportedApp(shelf_id2.app_id);
SetAppIconLoaders(std::unique_ptr<AppIconLoader>(app_icon_loader1),
std::unique_ptr<AppIconLoader>(app_icon_loader2));
shelf_controller_->CreateAppItem(
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id3, shelf_controller_->app_service_app_window_controller()),
ash::STATUS_RUNNING);
EXPECT_EQ(0, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(0, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
shelf_controller_->CreateAppItem(
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id2, shelf_controller_->app_service_app_window_controller()),
ash::STATUS_RUNNING);
EXPECT_EQ(0, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
shelf_controller_->CreateAppItem(
std::make_unique<AppServiceAppWindowShelfItemController>(
shelf_id1, shelf_controller_->app_service_app_window_controller()),
ash::STATUS_RUNNING);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(0, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
shelf_controller_->ReplaceWithAppShortcutOrRemove(shelf_id1);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(0, app_icon_loader2->clear_count());
shelf_controller_->ReplaceWithAppShortcutOrRemove(shelf_id2);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(1, app_icon_loader2->clear_count());
shelf_controller_->ReplaceWithAppShortcutOrRemove(shelf_id3);
EXPECT_EQ(1, app_icon_loader1->fetch_count());
EXPECT_EQ(1, app_icon_loader1->clear_count());
EXPECT_EQ(1, app_icon_loader2->fetch_count());
EXPECT_EQ(1, app_icon_loader2->clear_count());
}
TEST_F(ChromeShelfControllerWithArcTest, ArcAppPinPolicy) {
InitShelfControllerWithBrowser();
constexpr char kExampleArcPackageName[] = "com.example.app";
arc::mojom::AppInfoPtr appinfo =
CreateAppInfo("Some App", "SomeActivity", kExampleArcPackageName);
const std::string example_app_id = AddArcAppAndShortcut(*appinfo);
// Sets up policy that pins this ARC app. Unlike native extensions, ARC apps
// are pinned by |package_name| rather than the actual |app_id|.
base::Value::List policy_value;
AppendPrefValue(policy_value, kExampleArcPackageName);
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(example_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(example_app_id, profile()));
}
TEST_F(ChromeShelfControllerWithArcTest, ApkWebAppPinPolicy) {
InitShelfControllerWithBrowser();
constexpr char kMapsWebPackageName[] = "com.google.maps";
auto* service = ash::ApkWebAppService::Get(browser()->profile());
ASSERT_TRUE(service);
service->SetArcAppListPrefsForTesting(arc_test_.arc_app_list_prefs());
base::test::TestFuture<const std::string&, const web_app::AppId&> future;
service->SetWebAppInstalledCallbackForTesting(future.GetCallback());
auto package = arc::mojom::ArcPackageInfo::New(kMapsWebPackageName, 1, 1, 1,
/*sync=*/true);
package->web_app_info =
arc::mojom::WebAppInfo::New("Google Maps", "https://www.google.com/maps/",
"https://www.google.com/", 1000000);
std::vector<arc::mojom::ArcPackageInfoPtr> packages;
packages.push_back(std::move(package));
arc_test_.app_instance()->SendRefreshPackageList(std::move(packages));
auto [maps_package_name, maps_app_id] = future.Take();
ASSERT_EQ(maps_package_name, kMapsWebPackageName);
// Sets up policy that pins this apk-based Web App. Unlike regular Web Apps,
// Web Apps originating from apks are pinned by |package_name| rather than
// their |install_url|.
base::Value::List policy_value;
AppendPrefValue(policy_value, kMapsWebPackageName);
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(maps_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(maps_app_id, profile()));
}
TEST_F(ChromeShelfControllerWithArcTest, ArcManaged) {
extension_service_->AddExtension(arc_support_host_.get());
// Test enables ARC, so turn it off for initial values.
EnablePlayStore(false);
InitShelfController();
// To prevent import legacy pins each time.
// Initially pins are imported from legacy pref based model.
StartPrefSyncService(syncer::SyncDataList());
// Initial run, ARC is not managed and disabled, Play Store pin should be
// available.
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"Chrome, Play Store");
// ARC is managed and enabled, Play Store pin should be available.
// Note: CHECKING_REQUIREMENTS here means that opt-in flow is skipped.
profile()->GetTestingPrefService()->SetManagedPref(
arc::prefs::kArcEnabled, std::make_unique<base::Value>(true));
base::RunLoop().RunUntilIdle();
ValidateArcState(true, true,
arc::ArcSessionManager::State::CHECKING_REQUIREMENTS,
"Chrome, Play Store");
// ARC is managed and disabled, Play Store pin should not be available.
profile()->GetTestingPrefService()->SetManagedPref(
arc::prefs::kArcEnabled, std::make_unique<base::Value>(false));
base::RunLoop().RunUntilIdle();
ValidateArcState(false, true, arc::ArcSessionManager::State::STOPPED,
"Chrome");
// ARC is not managed and disabled, Play Store pin should be available.
profile()->GetTestingPrefService()->RemoveManagedPref(
arc::prefs::kArcEnabled);
base::RunLoop().RunUntilIdle();
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"Chrome, Play Store");
// ARC is not managed and enabled, Play Store pin should be available.
// Note: CHECKING_REQUIREMENTS here means that opt-in flow starts.
EnablePlayStore(true);
ValidateArcState(true, false,
arc::ArcSessionManager::State::CHECKING_REQUIREMENTS,
"Chrome, Play Store");
// User disables ARC. ARC is not managed and disabled, Play Store pin should
// be automatically removed.
EnablePlayStore(false);
ValidateArcState(false, false, arc::ArcSessionManager::State::STOPPED,
"Chrome");
// Even if re-enable it again, Play Store pin does not appear automatically.
EnablePlayStore(true);
ValidateArcState(true, false,
arc::ArcSessionManager::State::CHECKING_REQUIREMENTS,
"Chrome");
}
// Test the application menu of a shelf item with multiple ARC windows.
TEST_F(ChromeShelfControllerWithArcTest, ShelfItemWithMultipleWindows) {
InitShelfControllerWithBrowser();
arc::mojom::AppInfoPtr appinfo =
CreateAppInfo("Test1", "test", "com.example.app");
AddArcAppAndShortcut(*appinfo);
// Widgets will be deleted by the system.
NotifyOnTaskCreated(*appinfo, 1 /* task_id */);
views::Widget* window1 = CreateArcWindow("org.chromium.arc.1");
ASSERT_TRUE(window1);
EXPECT_TRUE(window1->IsActive());
NotifyOnTaskCreated(*appinfo, 2 /* task_id */);
views::Widget* window2 = CreateArcWindow("org.chromium.arc.2");
ASSERT_TRUE(window2);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
const std::string app_id = ArcAppTest::GetAppId(*appinfo);
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(ash::ShelfID(app_id));
ASSERT_TRUE(item_delegate);
// Selecting the item will show its application menu. It does not change the
// active window.
SelectItem(item_delegate);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
// Command ids are just app window indices. Note, apps are registered in
// opposite order. Last created goes in front.
auto items = item_delegate->GetAppMenuItems(0, base::NullCallback());
ASSERT_EQ(items.size(), 2U);
// Execute command 1 to activate the first window.
item_delegate->ExecuteCommand(false, 1, ui::EF_NONE,
display::kInvalidDisplayId);
EXPECT_TRUE(window1->IsActive());
EXPECT_FALSE(window2->IsActive());
// Selecting the item will show its application menu. It does not change the
// active window.
SelectItem(item_delegate);
EXPECT_TRUE(window1->IsActive());
EXPECT_FALSE(window2->IsActive());
// Execute command 0 to activate the second window.
item_delegate->ExecuteCommand(false, 0, ui::EF_NONE,
display::kInvalidDisplayId);
EXPECT_FALSE(window1->IsActive());
EXPECT_TRUE(window2->IsActive());
}
namespace {
class ChromeShelfControllerArcDefaultAppsTest
: public ChromeShelfControllerTestBase {
public:
ChromeShelfControllerArcDefaultAppsTest() = default;
ChromeShelfControllerArcDefaultAppsTest(
const ChromeShelfControllerArcDefaultAppsTest&) = delete;
ChromeShelfControllerArcDefaultAppsTest& operator=(
const ChromeShelfControllerArcDefaultAppsTest&) = delete;
~ChromeShelfControllerArcDefaultAppsTest() override = default;
protected:
void SetUp() override {
ArcAppIcon::DisableSafeDecodingForTesting();
ArcDefaultAppList::UseTestAppsDirectory();
ChromeShelfControllerTestBase::SetUp();
}
};
class ChromeShelfControllerPlayStoreAvailabilityTest
: public ChromeShelfControllerTestBase,
public ::testing::WithParamInterface<bool> {
public:
ChromeShelfControllerPlayStoreAvailabilityTest() = default;
ChromeShelfControllerPlayStoreAvailabilityTest(
const ChromeShelfControllerPlayStoreAvailabilityTest&) = delete;
ChromeShelfControllerPlayStoreAvailabilityTest& operator=(
const ChromeShelfControllerPlayStoreAvailabilityTest&) = delete;
~ChromeShelfControllerPlayStoreAvailabilityTest() override = default;
protected:
void SetUp() override {
if (GetParam())
arc::SetArcAlwaysStartWithoutPlayStoreForTesting();
// To prevent crash on test exit and pending decode request.
ArcAppIcon::DisableSafeDecodingForTesting();
ArcDefaultAppList::UseTestAppsDirectory();
ChromeShelfControllerTestBase::SetUp();
}
};
} // namespace
// TODO(https://crbug.com/1411138) Test is flaky on ChromeOS.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DefaultApps DISABLED_DefaultApps
#else
#define MAYBE_DefaultApps DefaultApps
#endif
TEST_F(ChromeShelfControllerArcDefaultAppsTest, MAYBE_DefaultApps) {
arc_test_.SetUp(profile());
InitShelfController();
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EnablePlayStore(false);
EXPECT_FALSE(arc::IsArcPlayStoreEnabledForProfile(profile()));
ASSERT_TRUE(prefs->GetAppIds().size());
const std::string app_id =
ArcAppTest::GetAppId(*arc_test_.fake_default_apps()[0]);
const ash::ShelfID shelf_id(app_id);
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED));
EXPECT_TRUE(arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
// Stop ARC again. Shelf item should go away.
EnablePlayStore(false);
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
EXPECT_TRUE(arc::LaunchApp(profile(), app_id, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED));
EXPECT_TRUE(arc::IsArcPlayStoreEnabledForProfile(profile()));
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
auto* item_delegate = model_->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
// Initially, a default icon is set for the shelf item.
EXPECT_FALSE(item_delegate->image_set_by_controller());
std::string window_app_id("org.chromium.arc.1");
CreateArcWindow(window_app_id);
arc_test_.app_instance()->SendTaskCreated(
1, *arc_test_.fake_default_apps()[0], std::string());
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
// Refresh delegate, it was changed.
item_delegate = model_->GetShelfItemDelegate(shelf_id);
ASSERT_TRUE(item_delegate);
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
EXPECT_FALSE(item_delegate->image_set_by_controller());
// Wait for the real app icon image to be decoded and set for the shelf item.
base::RunLoop().RunUntilIdle();
const std::vector<ui::ResourceScaleFactor>& scale_factors =
ui::GetSupportedResourceScaleFactors();
for (auto& scale_factor : scale_factors) {
// Force the icon to be loaded.
shelf_controller_->GetItem(shelf_id)->image.GetRepresentation(
ui::GetScaleForResourceScaleFactor(scale_factor));
}
EXPECT_TRUE(
ValidateImageIsFullyLoaded(shelf_controller_->GetItem(shelf_id)->image));
}
TEST_F(ChromeShelfControllerArcDefaultAppsTest, PlayStoreDeferredLaunch) {
// Add ARC host app to enable Play Store default app.
extension_service_->AddExtension(arc_support_host_.get());
arc_test_.SetUp(profile());
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EXPECT_TRUE(prefs->IsRegistered(arc::kPlayStoreAppId));
InitShelfController();
EnablePlayStore(true);
// Pin Play Store. It should be pinned but not scheduled for deferred launch.
PinAppWithIDToShelf(arc::kPlayStoreAppId);
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc::kPlayStoreAppId));
EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(
arc::kPlayStoreAppId));
// Simulate click. This should schedule Play Store for deferred launch.
ash::ShelfItemDelegate* item_delegate =
model_->GetShelfItemDelegate(ash::ShelfID(arc::kPlayStoreAppId));
EXPECT_TRUE(item_delegate);
SelectItem(item_delegate);
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc::kPlayStoreAppId));
EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(
arc::kPlayStoreAppId));
}
TEST_F(ChromeShelfControllerArcDefaultAppsTest, PlayStoreLaunchMetric) {
extension_service_->AddExtension(arc_support_host_.get());
arc_test_.SetUp(profile());
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
InitShelfController();
EnablePlayStore(true);
// Play Store available now as a default app but is not ready yet.
std::unique_ptr<ArcAppListPrefs::AppInfo> app_info =
prefs->GetApp(arc::kPlayStoreAppId);
ASSERT_TRUE(app_info);
EXPECT_FALSE(app_info->ready);
constexpr char kHistogramName[] = "Arc.PlayStoreLaunch.TimeDelta";
// Launch Play Store in deferred mode.
arc::LaunchApp(profile(), arc::kPlayStoreAppId, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
// This is deferred launch, no actual intents are delivered to ARC.
EXPECT_EQ(0U, arc_test_.app_instance()->launch_intents().size());
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc::mojom::AppInfo::New("", arc::kPlayStorePackage,
arc::kPlayStoreActivity));
arc_test_.app_instance()->SendRefreshAppList(apps);
ASSERT_EQ(1U, arc_test_.app_instance()->launch_intents().size());
std::string play_store_window_id("org.chromium.arc.1");
views::Widget* play_store_window = CreateArcWindow(play_store_window_id);
arc_test_.app_instance()->SendTaskCreated(
1, *apps[0], arc_test_.app_instance()->launch_intents()[0]);
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc::kPlayStoreAppId)));
// UMA is reported since app becomes ready.
base::HistogramBase* const histogram =
base::StatisticsRecorder::FindHistogram(kHistogramName);
ASSERT_TRUE(histogram);
std::unique_ptr<base::HistogramSamples> samples = histogram->SnapshotDelta();
ASSERT_EQ(1, samples->TotalCount());
play_store_window->Close();
// Launch Play Store in app-ready mode.
arc::LaunchApp(profile(), arc::kPlayStoreAppId, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
ASSERT_EQ(2U, arc_test_.app_instance()->launch_intents().size());
play_store_window_id = "org.chromium.arc.2";
play_store_window = CreateArcWindow(play_store_window_id);
arc_test_.app_instance()->SendTaskCreated(
2, *apps[0], arc_test_.app_instance()->launch_intents()[1]);
EXPECT_TRUE(shelf_controller_->GetItem(ash::ShelfID(arc::kPlayStoreAppId)));
// UMA is reported for app-ready launch. Note, previous call of SnapshotDelta
// resets samples, so we expect here only one recorded.
EXPECT_EQ(1, histogram->SnapshotDelta()->TotalCount());
play_store_window->Close();
}
TEST_F(ChromeShelfControllerArcDefaultAppsTest, DeferredLaunchMetric) {
extension_service_->AddExtension(arc_support_host_.get());
arc_test_.SetUp(profile());
InitShelfController();
EnablePlayStore(true);
constexpr char kHistogramName[] =
"Arc.FirstAppLaunchDelay.TimeDeltaUntilAppLaunch";
// Launch Play Store in deferred mode.
arc::LaunchApp(profile(), arc::kPlayStoreAppId, ui::EF_LEFT_MOUSE_BUTTON,
arc::UserInteractionType::NOT_USER_INITIATED);
EXPECT_FALSE(base::StatisticsRecorder::FindHistogram(kHistogramName));
std::vector<arc::mojom::AppInfoPtr> apps;
apps.emplace_back(arc::mojom::AppInfo::New("", arc::kPlayStorePackage,
arc::kPlayStoreActivity));
arc_test_.app_instance()->SendRefreshAppList(apps);
// No window attached at this time.
EXPECT_FALSE(base::StatisticsRecorder::FindHistogram(kHistogramName));
std::string play_store_window_id("org.chromium.arc.1");
views::Widget* const play_store_window =
CreateArcWindow(play_store_window_id);
ASSERT_EQ(1U, arc_test_.app_instance()->launch_intents().size());
arc_test_.app_instance()->SendTaskCreated(
1, *apps[0], arc_test_.app_instance()->launch_intents()[0]);
// UMA is reported since app becomes ready.
base::HistogramBase* const histogram =
base::StatisticsRecorder::FindHistogram(kHistogramName);
ASSERT_TRUE(histogram);
std::unique_ptr<base::HistogramSamples> samples = histogram->SnapshotDelta();
ASSERT_EQ(1, samples->TotalCount());
play_store_window->Close();
}
// Tests that the Play Store is not visible in AOSP image and visible in default
// images.
TEST_P(ChromeShelfControllerPlayStoreAvailabilityTest, Visible) {
extension_service_->AddExtension(arc_support_host_.get());
arc_test_.SetUp(profile());
InitShelfController();
StartPrefSyncService(syncer::SyncDataList());
ArcAppListPrefs* const prefs = arc_test_.arc_app_list_prefs();
EXPECT_EQ(arc::IsPlayStoreAvailable(),
prefs->IsRegistered(arc::kPlayStoreAppId));
// If the Play Store available, it is pinned by default.
EXPECT_EQ(arc::IsPlayStoreAvailable(),
shelf_controller_->IsAppPinned(arc::kPlayStoreAppId));
arc_test_.TearDown();
}
// Checks the case when several app items have the same ordinal position (which
// is valid case).
TEST_F(ChromeShelfControllerTest, CheckPositionConflict) {
InitShelfController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
AddWebApp(web_app::kGmailAppId);
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, app_constants::kChromeAppId);
InsertAddPinChange(&sync_list, 1, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
InsertAddPinChange(&sync_list, 1, web_app::kGmailAppId);
SendPinChanges(sync_list, true);
EXPECT_EQ("Chrome, App1, App2, Gmail", GetPinnedAppStatus());
const syncer::StringOrdinal position_chrome =
app_list_syncable_service_->GetPinPosition(app_constants::kChromeAppId);
const syncer::StringOrdinal position_1 =
app_list_syncable_service_->GetPinPosition(extension1_->id());
const syncer::StringOrdinal position_2 =
app_list_syncable_service_->GetPinPosition(extension2_->id());
const syncer::StringOrdinal position_3 =
app_list_syncable_service_->GetPinPosition(web_app::kGmailAppId);
EXPECT_TRUE(position_chrome.LessThan(position_1));
EXPECT_TRUE(position_1.Equals(position_2));
EXPECT_TRUE(position_2.Equals(position_3));
// Move Chrome between App1 and App2.
// Note, move target_index is in context when moved element is removed from
// array first.
model_->Move(0, 1);
EXPECT_EQ("App1, Chrome, App2, Gmail", GetPinnedAppStatus());
// Expect sync positions for only Chrome is updated and its resolution is
// after all duplicated ordinals.
EXPECT_TRUE(position_3.LessThan(
app_list_syncable_service_->GetPinPosition(app_constants::kChromeAppId)));
EXPECT_TRUE(position_1.Equals(
app_list_syncable_service_->GetPinPosition(extension1_->id())));
EXPECT_TRUE(position_1.Equals(
app_list_syncable_service_->GetPinPosition(extension1_->id())));
EXPECT_TRUE(position_2.Equals(
app_list_syncable_service_->GetPinPosition(extension2_->id())));
EXPECT_TRUE(position_3.Equals(
app_list_syncable_service_->GetPinPosition(web_app::kGmailAppId)));
}
// Test the case when sync app is turned off and we need to use local copy to
// support user's pins.
TEST_F(ChromeShelfControllerTest, SyncOffLocalUpdate) {
InitShelfController();
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, app_constants::kChromeAppId);
InsertAddPinChange(&sync_list, 1, extension1_->id());
InsertAddPinChange(&sync_list, 1, extension2_->id());
SendPinChanges(sync_list, true);
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
syncer::SyncDataList copy_sync_list =
app_list_syncable_service_->GetAllSyncDataForTesting();
app_list_syncable_service_->StopSyncing(syncer::APP_LIST);
RecreateShelfController()->Init();
// Pinned state should not change.
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
shelf_controller_->UnpinAppWithID(extension2_->id());
EXPECT_EQ("Chrome, App1", GetPinnedAppStatus());
// Resume syncing and sync information overrides local copy.
StartAppSyncService(copy_sync_list);
base::RunLoop().RunUntilIdle();
EXPECT_EQ("Chrome, App1, App2", GetPinnedAppStatus());
}
// Test the Settings can be pinned and unpinned.
TEST_F(ChromeShelfControllerTest, InternalAppPinUnpin) {
InitShelfController();
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
const std::string app_id = ash::kInternalAppIdSettings;
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
// Pin Settings.
PinAppWithIDToShelf(app_id);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
// Unpin Settings.
shelf_controller_->UnpinAppWithID(app_id);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
}
// Test that internal app can be added and removed on shelf.
TEST_F(ChromeShelfControllerTest, InternalAppWindowRecreation) {
InitShelfController();
// Only test the first internal app. The others should be the same.
const auto& internal_app = app_list::GetInternalAppList(profile()).front();
const std::string app_id = internal_app.app_id;
const ash::ShelfID shelf_id(app_id);
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
views::Widget* internal_app_window = CreateShelfAppWindow(app_id);
ASSERT_TRUE(internal_app_window);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
internal_app_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
// Create and close again.
internal_app_window = CreateShelfAppWindow(app_id);
ASSERT_TRUE(internal_app_window);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
internal_app_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
}
// Test that internal app can be added and removed by SetProperty of
// ash::kShelfIDKey.
TEST_F(ChromeShelfControllerTest, InternalAppWindowPropertyChanged) {
InitShelfController();
// Only test the first internal app. The others should be the same.
const auto internal_app = app_list::GetInternalAppList(profile()).front();
std::string app_id;
ash::ShelfID shelf_id;
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
// Set an empty ash::kShelfIDKey.
views::Widget* internal_app_window = CreateShelfAppWindow(app_id);
ASSERT_TRUE(internal_app_window);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
// Set an invalid ash::kShelfIDKey.
app_id = "An invalid internal app id";
shelf_id = ash::ShelfID(app_id);
internal_app_window->GetNativeWindow()->SetProperty(
ash::kShelfIDKey, new std::string(shelf_id.Serialize()));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
// Set a valid ash::kShelfIDKey.
app_id = internal_app.app_id;
shelf_id = ash::ShelfID(app_id);
internal_app_window->GetNativeWindow()->SetProperty(
ash::kShelfIDKey, new std::string(shelf_id.Serialize()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(shelf_controller_->GetItem(shelf_id));
internal_app_window->Close();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(shelf_controller_->GetItem(shelf_id));
}
// TODO(b/194627475): Move these tests to chrome_shelf_controller_browsertest.cc
class ChromeShelfControllerDemoModeTest : public ChromeShelfControllerTestBase {
protected:
ChromeShelfControllerDemoModeTest() { auto_start_arc_test_ = true; }
ChromeShelfControllerDemoModeTest(const ChromeShelfControllerDemoModeTest&) =
delete;
ChromeShelfControllerDemoModeTest& operator=(
const ChromeShelfControllerDemoModeTest&) = delete;
~ChromeShelfControllerDemoModeTest() override = default;
void SetUp() override {
// To prevent crash on test exit and pending decode request.
ArcAppIcon::DisableSafeDecodingForTesting();
ChromeShelfControllerTestBase::SetUp();
// Fake Demo Mode.
demo_mode_test_helper_ = std::make_unique<ash::DemoModeTestHelper>();
GetInstallAttributes()->SetDemoMode();
demo_mode_test_helper_->InitializeSession();
}
void TearDown() override {
demo_mode_test_helper_.reset();
ChromeShelfControllerTestBase::TearDown();
}
private:
std::unique_ptr<ash::DemoModeTestHelper> demo_mode_test_helper_;
};
// TODO(crbug.com/1363613): PinnedAppsOnline is flaky on linux-chromeos-rel.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_PinnedAppsOnline DISABLED_PinnedAppsOnline
#else
#define MAYBE_PinnedAppsOnline PinnedAppsOnline
#endif
TEST_F(ChromeShelfControllerDemoModeTest, MAYBE_PinnedAppsOnline) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_ETHERNET);
InitShelfControllerWithBrowser();
base::Value::List policy_value;
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
AppendPrefValue(policy_value, extension1_->id());
AppendPrefValue(policy_value, extension2_->id());
arc::mojom::AppInfoPtr appinfo =
CreateAppInfo("Some App", "SomeActivity", "com.example.app");
const std::string app_id = AddArcAppAndShortcut(*appinfo);
arc::mojom::AppInfoPtr online_only_appinfo =
CreateAppInfo("Some App", "SomeActivity", "com.example.onlineonly");
const std::string online_only_app_id =
AddArcAppAndShortcut(*online_only_appinfo);
AppendPrefValue(policy_value, appinfo->package_name);
AppendPrefValue(policy_value, online_only_appinfo->package_name);
constexpr char kWebAppUrl[] = "https://test-pwa.com/";
web_app::AppId web_app_id = InstallExternalWebApp(kWebAppUrl);
AppendPrefValue(policy_value, kWebAppUrl);
// If the device is offline, extension2, onlineonly, and TestPWA should be
// unpinned. Since the device is online here, these apps should still be
// pinned, even though we're ignoring them here.
ash::DemoSession::Get()->OverrideIgnorePinPolicyAppsForTesting(
{extension2_->id(), online_only_appinfo->package_name});
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
// Since the device is online, all policy pinned apps are pinned.
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension1_->id(), profile()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension2_->id(), profile()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(online_only_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(online_only_app_id, profile()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(web_app_id, profile()));
}
TEST_F(ChromeShelfControllerDemoModeTest, PinnedAppsOffline) {
network::TestNetworkConnectionTracker::GetInstance()->SetConnectionType(
network::mojom::ConnectionType::CONNECTION_NONE);
InitShelfControllerWithBrowser();
base::Value::List policy_value;
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
AppendPrefValue(policy_value, extension1_->id());
AppendPrefValue(policy_value, extension2_->id());
arc::mojom::AppInfoPtr appinfo =
CreateAppInfo("Some App", "SomeActivity", "com.example.app");
const std::string app_id = AddArcAppAndShortcut(*appinfo);
arc::mojom::AppInfoPtr online_only_appinfo =
CreateAppInfo("Some App", "SomeActivity", "com.example.onlineonly");
const std::string online_only_app_id =
AddArcAppAndShortcut(*online_only_appinfo);
AppendPrefValue(policy_value, appinfo->package_name);
AppendPrefValue(policy_value, online_only_appinfo->package_name);
constexpr char kWebAppUrl[] = "https://test-pwa.com/";
web_app::AppId web_app_id = InstallExternalWebApp(kWebAppUrl);
AppendPrefValue(policy_value, kWebAppUrl);
// If the device is offline, extension2 and onlineonly, and TestPWA should be
// unpinned.
ash::DemoSession::Get()->OverrideIgnorePinPolicyAppsForTesting(
{extension2_->id(), online_only_appinfo->package_name, kWebAppUrl});
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
// Since the device is offline, the policy pinned apps that shouldn't be
// pinned in Demo Mode are unpinned.
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(extension1_->id(), profile()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(extension2_->id(), profile()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_FIXED,
GetPinnableForAppID(app_id, profile()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(online_only_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(online_only_app_id, profile()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(web_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(web_app_id, profile()));
// Pin a Chrome app that would have been pinned by policy but was suppressed
// for Demo Mode.
PinAppWithIDToShelf(extension2_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(extension2_->id(), profile()));
// Pin an ARC app that would have been pinned by policy but was suppressed for
// Demo Mode.
PinAppWithIDToShelf(online_only_app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(online_only_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(online_only_app_id, profile()));
// Pin a web app that would have been pinned by policy but was suppressed for
// Demo Mode.
PinAppWithIDToShelf(web_app_id);
EXPECT_TRUE(shelf_controller_->IsAppPinned(web_app_id));
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(web_app_id, profile()));
}
// Tests behavior for ensuring some component apps can be marked unpinnable.
TEST_F(ChromeShelfControllerTest, UnpinnableComponentApps) {
InitShelfController();
const char* kPinnableApp = file_manager::kFileManagerAppId;
const char* kNoPinApps[] = {ash::eche_app::kEcheAppId};
EXPECT_EQ(AppListControllerDelegate::PIN_EDITABLE,
GetPinnableForAppID(kPinnableApp, profile()));
for (const char* id : kNoPinApps) {
EXPECT_EQ(AppListControllerDelegate::NO_PIN,
GetPinnableForAppID(id, profile()));
}
}
TEST_F(ChromeShelfControllerTest, DoNotShowInShelf) {
syncer::SyncChangeList sync_list;
InsertAddPinChange(&sync_list, 0, extension1_->id());
InsertAddPinChange(&sync_list, 0, extension2_->id());
SendPinChanges(sync_list, true);
AddExtension(extension1_.get());
AddExtension(extension2_.get());
// Set App1.show_in_shelf to false.
std::vector<apps::AppPtr> apps;
apps::AppPtr app =
std::make_unique<apps::App>(apps::AppType::kChromeApp, extension1_->id());
app->show_in_shelf = false;
apps.push_back(std::move(app));
apps::AppServiceProxyFactory::GetForProfile(profile())
->AppRegistryCache()
.OnApps(std::move(apps), apps::AppType::kChromeApp,
false /* should_notify_initialized */);
InitShelfController();
EXPECT_EQ("Chrome, App2", GetPinnedAppStatus());
}
TEST_F(ChromeShelfControllerWithArcTest, ReplacePinnedItem) {
InitShelfController();
SendListOfArcApps();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
PinAppWithIDToShelf(extension1_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id1));
// Replace pin extension to ARC app
shelf_controller_->ReplacePinnedItem(extension1_->id(), arc_app_id1);
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id1));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
// Replace pin ARC app to ARC app
shelf_controller_->ReplacePinnedItem(arc_app_id1, arc_app_id2);
EXPECT_TRUE(shelf_controller_->IsAppPinned(arc_app_id2));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id1));
// Replace pin ARC app to extension app
shelf_controller_->ReplacePinnedItem(arc_app_id2, extension1_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id2));
// Replace pin extension app to extension app
shelf_controller_->ReplacePinnedItem(extension1_->id(), extension2_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
// Try to replace item that is not pinned.
shelf_controller_->ReplacePinnedItem(arc_app_id2, extension1_->id());
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(shelf_controller_->IsAppPinned(arc_app_id2));
// Try to replace item with item that is already pinned.
PinAppWithIDToShelf(extension1_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
shelf_controller_->ReplacePinnedItem(extension2_->id(), extension1_->id());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
}
TEST_F(ChromeShelfControllerWithArcTest, PinAtIndex) {
InitShelfController();
SendListOfArcApps();
const std::string arc_app_id1 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[0]);
const std::string arc_app_id2 =
ArcAppTest::GetAppId(*arc_test_.fake_apps()[1]);
extension_service_->AddExtension(extension1_.get());
extension_service_->AddExtension(extension2_.get());
int index = 0;
shelf_controller_->PinAppAtIndex(extension1_->id(), index);
EXPECT_EQ(index,
shelf_controller_->PinnedItemIndexByAppID(extension1_->id()));
shelf_controller_->PinAppAtIndex(extension2_->id(), index);
EXPECT_EQ(index,
shelf_controller_->PinnedItemIndexByAppID(extension2_->id()));
EXPECT_NE(index,
shelf_controller_->PinnedItemIndexByAppID(extension1_->id()));
index = 3;
shelf_controller_->PinAppAtIndex(arc_app_id1, index);
EXPECT_EQ(index, shelf_controller_->PinnedItemIndexByAppID(arc_app_id1));
// Test pinning at invalid index.
index = -100;
shelf_controller_->PinAppAtIndex(arc_app_id2, index);
EXPECT_NE(index, shelf_controller_->PinnedItemIndexByAppID(arc_app_id2));
EXPECT_EQ(-1, shelf_controller_->PinnedItemIndexByAppID(arc_app_id2));
// Test pinning already pinned app.
index = 0;
shelf_controller_->PinAppAtIndex(arc_app_id1, index);
EXPECT_NE(index, shelf_controller_->PinnedItemIndexByAppID(arc_app_id1));
EXPECT_EQ(3, shelf_controller_->PinnedItemIndexByAppID(arc_app_id1));
}
class ChromeShelfControllerWebAppTest : public ChromeShelfControllerTestBase {
protected:
ChromeShelfControllerWebAppTest() {}
~ChromeShelfControllerWebAppTest() override = default;
};
// Test the web app interaction flow: pin it, run it, unpin it, close it.
TEST_F(ChromeShelfControllerWebAppTest, WebAppPinRunUnpinClose) {
constexpr char kWebAppUrl[] = "https://webappone.com/";
constexpr char kWebAppName[] = "WebApp1";
InitShelfController();
const web_app::AppId app_id = web_app::test::InstallDummyWebApp(
profile(), kWebAppName, GURL(kWebAppUrl));
base::RunLoop().RunUntilIdle();
// The model should only contain the browser shortcut item.
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(nullptr, shelf_controller_->GetItem(ash::ShelfID(app_id)));
// Pinning the app should create a new shelf item.
PinAppWithIDToShelf(app_id);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_CLOSED, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_NE(nullptr, shelf_controller_->GetItem(ash::ShelfID(app_id)));
// Reporting that the app is running should just update the existing item.
shelf_controller_->SetAppStatus(app_id, ash::STATUS_RUNNING);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_PINNED_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(app_id));
EXPECT_NE(nullptr, shelf_controller_->GetItem(ash::ShelfID(app_id)));
// Unpinning the app should just update the existing item.
shelf_controller_->UnpinAppWithID(app_id);
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::TYPE_APP, model_->items()[1].type);
EXPECT_EQ(ash::STATUS_RUNNING, model_->items()[1].status);
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
EXPECT_NE(nullptr, shelf_controller_->GetItem(ash::ShelfID(app_id)));
// Reporting that the app is closed should remove its shelf item.
shelf_controller_->SetAppStatus(app_id, ash::STATUS_CLOSED);
EXPECT_EQ(1, model_->item_count());
EXPECT_FALSE(shelf_controller_->IsAppPinned(app_id));
EXPECT_EQ(nullptr, shelf_controller_->GetItem(ash::ShelfID(app_id)));
}
// Test the app status when the paused app is blocked, un-blocked, and un-paused
TEST_F(ChromeShelfControllerTest, VerifyAppStatusForPausedApp) {
AddExtension(extension1_.get());
// Set the app as paused
UpdateAppRegistryCache(profile(), extension1_->id(), false /* block */,
true /* pause */, absl::nullopt /* show_in_shelf */);
InitShelfController();
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::AppStatus::kPaused, model_->items()[1].app_status);
// Set the app as blocked
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
true /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kBlocked, model_->items()[1].app_status);
// Set the app as ready, but still paused;
UpdateAppRegistryCache(profile(), extension1_->id(), false /* block */,
true /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kPaused, model_->items()[1].app_status);
// Set the app as ready, and not paused;
UpdateAppRegistryCache(profile(), extension1_->id(), false /* block */,
false /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kReady, model_->items()[1].app_status);
}
// Test the app status when the blocked app is paused, un-paused, hidden,
// visible and un-blocked
TEST_F(ChromeShelfControllerTest, VerifyAppStatusForBlockedApp) {
AddExtension(extension1_.get());
// Set the app as blocked
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
false /* pause */, absl::nullopt /* show_in_shelf */);
InitShelfController();
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_EQ(ash::AppStatus::kBlocked, model_->items()[1].app_status);
// Set the app as paused
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
true /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kBlocked, model_->items()[1].app_status);
// Set the app as blocked, but un-paused
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
false /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kBlocked, model_->items()[1].app_status);
// Set the app as ready, and not paused
UpdateAppRegistryCache(profile(), extension1_->id(), false /* block */,
false /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kReady, model_->items()[1].app_status);
// Set the app as blocked and hidden
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
false /* pause */, false /* show_in_shelf */);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
// Set the app as blocked and visible
UpdateAppRegistryCache(profile(), extension1_->id(), true /* block */,
false /* pause */, true /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kBlocked, model_->items()[1].app_status);
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
// Set the app as ready
UpdateAppRegistryCache(profile(), extension1_->id(), false /* block */,
false /* pause */, absl::nullopt /* show_in_shelf */);
EXPECT_EQ(ash::AppStatus::kReady, model_->items()[1].app_status);
}
TEST_F(ChromeShelfControllerTest, PinnedAppsRespectShownInShelfState) {
InitShelfController();
// Pin a test app.
AddExtension(extension1_.get());
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
// Update the app so it's considered not shown in shelf, and verify it's no
// longer pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
// Update the app so it's allowed in shelf again, verify it gets pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/true);
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(model_->AllowedToSetAppPinState(extension1_->id(), false));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
}
TEST_F(ChromeShelfControllerTest, AppIndexAfterUnhidingFirstPinnedApp) {
InitShelfController();
// Pin a test app.
AddExtension(extension1_.get());
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension1_->id()));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
model_->Move(1, 0);
EXPECT_EQ(0, model_->ItemIndexByAppID(extension1_->id()));
// Update the app so it's considered not shown in shelf, and verify it's no
// longer pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension1_->id()));
// Update the app so it's allowed in shelf again, verify it gets pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/true);
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(model_->AllowedToSetAppPinState(extension1_->id(), false));
EXPECT_EQ(0, model_->ItemIndexByAppID(extension1_->id()));
}
TEST_F(ChromeShelfControllerTest,
AppIndexAfterUnhidingtPinnedAppWithOtherHiddenApps) {
InitShelfController();
// Pin test apps.
AddExtension(extension1_.get());
AddExtension(extension2_.get());
AddExtension(extension5_.get());
AddExtension(extension6_.get());
PinAppWithIDToShelf(extension1_->id());
PinAppWithIDToShelf(extension2_->id());
PinAppWithIDToShelf(extension5_->id());
PinAppWithIDToShelf(extension6_->id());
EXPECT_EQ(5, model_->item_count());
EXPECT_TRUE(shelf_controller_->IsAppPinned(extension2_->id()));
EXPECT_EQ(2, model_->ItemIndexByAppID(extension2_->id()));
// Update all test apps so they're considered not shown in shelf, and verify
// it's no longer pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
UpdateAppRegistryCache(profile(), extension2_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
UpdateAppRegistryCache(profile(), extension5_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(shelf_controller_->IsAppPinned(extension2_->id()));
// Update app 2 so it's allowed in shelf again, verify it gets pinned.
UpdateAppRegistryCache(profile(), extension2_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/true);
EXPECT_TRUE(model_->IsAppPinned(extension2_->id()));
EXPECT_TRUE(model_->AllowedToSetAppPinState(extension2_->id(), false));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension2_->id()));
EXPECT_EQ(2, model_->ItemIndexByAppID(extension6_->id()));
}
TEST_F(ChromeShelfControllerTest, AppsHiddenFromShelfDontGetPinnedByPolicy) {
AddExtension(extension1_.get());
// Pin a test app by policy.
base::Value::List policy_value;
AppendPrefValue(policy_value, extension1_->id());
profile()->GetTestingPrefService()->SetManagedPref(
prefs::kPolicyPinnedLauncherApps, base::Value(policy_value.Clone()));
InitShelfController();
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
// Update the app so it's considered not shown in shelf, and verify it's no
// longer pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(model_->IsAppPinned(extension1_->id()));
// Update the app so it's allowed in shelf again, verify it gets pinned.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/true);
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_FALSE(model_->AllowedToSetAppPinState(extension1_->id(), false));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
}
TEST_F(ChromeShelfControllerTest, AppHiddenFromShelfNotPinnedOnInstall) {
AddExtension(extension1_.get());
InitShelfController();
PinAppWithIDToShelf(extension1_->id());
EXPECT_EQ(2, model_->item_count());
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
// Block the extension so it gets removed from shelf.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/true,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(model_->IsAppPinned(extension1_->id()));
// Unblock the extension, but mark it as not shown in shelf - verify it
// doesn't get pinned/added to shelf.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/false);
EXPECT_FALSE(model_->IsAppPinned(extension1_->id()));
// Allow the app to be shown in shelf, and verify it gets pinned again.
UpdateAppRegistryCache(profile(), extension1_->id(), /*block=*/false,
/*pause=*/false,
/*show_in_shelf=*/true);
EXPECT_TRUE(model_->IsAppPinned(extension1_->id()));
EXPECT_TRUE(model_->AllowedToSetAppPinState(extension1_->id(), false));
EXPECT_EQ(1, model_->ItemIndexByAppID(extension1_->id()));
}
INSTANTIATE_TEST_SUITE_P(All,
ChromeShelfControllerPlayStoreAvailabilityTest,
::testing::Values(false, true));