blob: 6f347f754fe34c40747d19871c673916372ca322 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/auto_reset.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/metrics/histogram_base.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_registry_cache_waiter.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/external_provider_impl.h"
#include "chrome/browser/extensions/external_provider_manager.h"
#include "chrome/browser/extensions/external_testing_loader.h"
#include "chrome/browser/extensions/launch_util.h"
#include "chrome/browser/extensions/updater/extension_updater.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/web_applications/test/ssl_test_utils.h"
#include "chrome/browser/web_applications/extension_status_utils.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/preinstalled_app_install_features.h"
#include "chrome/browser/web_applications/preinstalled_web_app_manager.h"
#include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/url_loader_interceptor.h"
#include "extensions/browser/app_sorting.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/updater/extension_cache_fake.h"
#include "extensions/browser/updater/extension_downloader_test_helper.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/public/cpp/app_list/app_list_types.h"
#include "chrome/browser/ash/app_list/app_list_model_updater.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service.h"
#include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
#include "chrome/browser/ash/app_list/chrome_app_list_item.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#endif
namespace {
constexpr char kMigrationFlag[] = "MigrationTest";
constexpr char kExtensionUpdatePath[] = "/update_extension";
constexpr char kExtensionId[] = "kbmnembihfiondgfjekmnmcbddelicoi";
constexpr char kExtensionVersion[] = "1.0.0.0";
constexpr char kExtensionCrxPath[] = "/extensions/hosted_app.crx";
constexpr char kWebAppPath[] = "/web_apps/basic.html";
} // namespace
namespace web_app {
class PreinstalledWebAppMigrationBrowserTest
: public extensions::ExtensionBrowserTest {
public:
PreinstalledWebAppMigrationBrowserTest()
: enable_chrome_apps_(
&extensions::testing::g_enable_chrome_apps_for_testing,
true),
skip_preinstalled_web_app_startup_(
PreinstalledWebAppManager::SkipStartupForTesting()),
bypass_offline_manifest_requirement_(
PreinstalledWebAppManager::
BypassOfflineManifestRequirementForTesting()) {
disable_external_extensions_scope_ =
extensions::ExternalProviderManager::DisableExternalUpdatesForTesting();
}
~PreinstalledWebAppMigrationBrowserTest() override = default;
extensions::ExtensionService& extension_service() {
return *extensions::ExtensionSystem::Get(profile())->extension_service();
}
GURL GetWebAppUrl() const {
return embedded_test_server()->GetURL(kWebAppPath);
}
webapps::AppId GetWebAppId() const {
return GenerateAppId(/*manifest_id=*/std::nullopt, GetWebAppUrl());
}
// extensions::ExtensionBrowserTest:
void SetUp() override {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&PreinstalledWebAppMigrationBrowserTest::RequestHandlerOverride,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
extensions::ExtensionBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
SetUpExtensionTestExternalProvider();
extensions::ExtensionBrowserTest::SetUpOnMainThread();
web_app::test::WaitUntilReady(WebAppProvider::GetForTest(profile()));
}
void TearDownOnMainThread() override {
auto* const provider = WebAppProvider::GetForTest(profile());
const WebAppRegistrar& registrar = provider->registrar_unsafe();
std::vector<webapps::AppId> app_ids = registrar.GetAppIds();
for (const auto& app_id : app_ids) {
if (!registrar.IsInRegistrar(app_id)) {
continue;
}
apps::AppReadinessWaiter(profile(), app_id).Await();
const WebApp* app = registrar.GetAppById(app_id);
DCHECK(app->CanUserUninstallWebApp());
web_app::test::UninstallWebApp(profile(), app_id);
apps::AppReadinessWaiter(
profile(), app_id, base::BindRepeating([](apps::Readiness readiness) {
return !apps_util::IsInstalled(readiness);
}))
.Await();
}
extensions::ExtensionBrowserTest::TearDownOnMainThread();
}
extensions::ExternalProviderManager* external_provider_manager() {
return extensions::ExternalProviderManager::Get(profile());
}
std::unique_ptr<net::test_server::HttpResponse> RequestHandlerOverride(
const net::test_server::HttpRequest& request) {
std::string request_path = request.GetURL().path();
if (request_path == kExtensionUpdatePath) {
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
response->set_code(net::HTTP_OK);
response->set_content(extensions::CreateUpdateManifest(
{extensions::UpdateManifestItem(kExtensionId)
.version(kExtensionVersion)
.codebase(
embedded_test_server()->GetURL(kExtensionCrxPath).spec())}));
response->set_content_type("text/xml");
return std::move(response);
}
return nullptr;
}
void SetUpExtensionTestExternalProvider() {
external_provider_manager()->ClearProvidersForTesting();
extensions::ExtensionUpdater::Get(profile())->SetExtensionCacheForTesting(
test_extension_cache_.get());
std::string external_extension_config = base::ReplaceStringPlaceholders(
R"({
"$1": {
"external_update_url": "$2",
"web_app_migration_flag": "$3"
}
})",
{kExtensionId,
embedded_test_server()->GetURL(kExtensionUpdatePath).spec(),
kMigrationFlag},
nullptr);
external_provider_manager()->AddProviderForTesting(
std::make_unique<extensions::ExternalProviderImpl>(
external_provider_manager(),
base::MakeRefCounted<extensions::ExternalTestingLoader>(
external_extension_config,
base::FilePath(FILE_PATH_LITERAL("//absolute/path"))),
profile(), extensions::mojom::ManifestLocation::kExternalPref,
extensions::mojom::ManifestLocation::kExternalPrefDownload,
// Matches |bundled_extension_creation_flags| in
// ExternalProviderImpl::CreateExternalProviders().
extensions::Extension::WAS_INSTALLED_BY_DEFAULT |
extensions::Extension::FROM_WEBSTORE));
disable_external_extensions_scope_.reset();
}
void SyncExternalExtensions() {
base::RunLoop run_loop;
external_provider_manager()
->set_external_updates_finished_callback_for_test(
run_loop.QuitWhenIdleClosure());
external_provider_manager()->CheckForExternalUpdates();
run_loop.Run();
}
void SyncExternalWebApps(bool expect_install,
std::optional<webapps::UninstallResultCode>
expect_uninstall = std::nullopt,
bool pass_config = true) {
base::RunLoop run_loop;
std::optional<webapps::InstallResultCode> code;
auto callback = base::BindLambdaForTesting(
[&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode> uninstall_results) {
if (expect_install) {
code = install_results.at(GetWebAppUrl()).code;
EXPECT_TRUE(
*code == webapps::InstallResultCode::kSuccessNewInstall ||
*code == webapps::InstallResultCode::kSuccessAlreadyInstalled ||
*code ==
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
} else {
EXPECT_EQ(install_results.find(GetWebAppUrl()),
install_results.end());
}
if (!expect_uninstall.has_value()) {
EXPECT_TRUE(uninstall_results.empty());
} else {
EXPECT_EQ(uninstall_results[GetWebAppUrl()], *expect_uninstall);
}
run_loop.Quit();
});
base::Value::List app_configs;
if (pass_config) {
std::string app_config_string = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"feature_name": "$2",
"uninstall_and_replace": ["$3"]
})",
{GetWebAppUrl().spec(), kMigrationFlag, uninstall_and_replace_},
nullptr);
app_configs.Append(*base::JSONReader::Read(app_config_string));
}
base::AutoReset<const base::Value::List*> configs_for_testing =
PreinstalledWebAppManager::SetConfigsForTesting(&app_configs);
WebAppProvider::GetForTest(profile())
->preinstalled_web_app_manager()
.LoadAndSynchronizeForTesting(std::move(callback));
run_loop.Run();
}
bool IsWebAppInstalled() {
std::optional<proto::InstallState> install_state =
WebAppProvider::GetForTest(profile())
->registrar_unsafe()
.GetInstallState(GetWebAppId());
return install_state == proto::INSTALLED_WITHOUT_OS_INTEGRATION ||
install_state == proto::INSTALLED_WITH_OS_INTEGRATION;
}
bool IsExtensionAppInstalled() {
return extensions::ExtensionRegistry::Get(profile())->GetExtensionById(
kExtensionId, extensions::ExtensionRegistry::EVERYTHING);
}
protected:
const char* uninstall_and_replace_ = kExtensionId;
base::test::ScopedFeatureList features_;
std::optional<base::AutoReset<bool>> disable_external_extensions_scope_;
std::unique_ptr<extensions::ExtensionCacheFake> test_extension_cache_;
private:
web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
base::AutoReset<bool> enable_chrome_apps_;
base::AutoReset<bool> skip_preinstalled_web_app_startup_;
base::AutoReset<bool> bypass_offline_manifest_requirement_;
};
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigrationBrowserTest,
MigrateRevertMigrate) {
#if BUILDFLAG(IS_CHROMEOS)
// Grab handles to the app list to update shelf/list state for apps later on.
app_list::AppListSyncableService* app_list_syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
AppListModelUpdater* app_list_model_updater =
app_list_syncable_service->GetModelUpdater();
app_list_model_updater->SetActive(true);
#endif
// Set up pre-migration state.
{
ASSERT_FALSE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
SyncExternalWebApps(/*expect_install=*/false);
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_TRUE(IsExtensionAppInstalled());
#if BUILDFLAG(IS_CHROMEOS)
app_list_model_updater->SetItemPosition(
kExtensionId, syncer::StringOrdinal("testapplistposition"));
app_list_syncable_service->SetPinPosition(
kExtensionId, syncer::StringOrdinal("testpinposition"));
EXPECT_EQ(app_list_syncable_service->GetSyncItem(kExtensionId)->ToString(),
"kbmnembi { Nothing } [testapplistposition] "
"[testpinposition](INVALID COLOR)");
#endif
}
// Migrate extension app to web app.
{
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
// Extension sticks around to be uninstalled by the replacement web app.
EXPECT_TRUE(IsExtensionAppInstalled());
{
base::HistogramTester histograms;
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()));
SyncExternalWebApps(/*expect_install=*/true);
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(uninstalled_app->id(), kExtensionId);
EXPECT_TRUE(IsWebAppInstalled());
EXPECT_FALSE(IsExtensionAppInstalled());
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramInstallResult,
webapps::InstallResultCode::kSuccessNewInstall, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramUninstallAndReplaceCount, 1, 1);
#if BUILDFLAG(IS_CHROMEOS)
// Chrome OS shelf/list position should migrate.
EXPECT_EQ(
app_list_syncable_service->GetSyncItem(GetWebAppId())->ToString(),
base::StringPrintf("%s { Basic web app } [testapplistposition] "
"[testpinposition](INVALID COLOR)",
GetWebAppId().substr(0, 8).c_str()));
// Old Chrome app prefs are retained.
EXPECT_EQ(
app_list_syncable_service->GetSyncItem(kExtensionId)->ToString(),
"kbmnembi { Nothing } [testapplistposition] "
"[testpinposition](INVALID COLOR)");
#endif
}
}
// Revert migration.
{
ASSERT_FALSE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
SyncExternalWebApps(
/*expect_install=*/false,
/*expect_uninstall=*/webapps::UninstallResultCode::kAppRemoved);
EXPECT_TRUE(IsExtensionAppInstalled());
EXPECT_FALSE(IsWebAppInstalled());
}
// Re-run migration.
{
base::HistogramTester histograms;
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()));
SyncExternalWebApps(/*expect_install=*/true);
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(uninstalled_app->id(), kExtensionId);
EXPECT_TRUE(IsWebAppInstalled());
EXPECT_FALSE(IsExtensionAppInstalled());
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramInstallResult,
webapps::InstallResultCode::kSuccessNewInstall, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramUninstallAndReplaceCount, 1, 1);
#if BUILDFLAG(IS_CHROMEOS)
// Chrome OS shelf/list position should re-migrate.
EXPECT_EQ(app_list_syncable_service->GetSyncItem(GetWebAppId())->ToString(),
base::StringPrintf("%s { Basic web app } [testapplistposition] "
"[testpinposition](INVALID COLOR)",
GetWebAppId().substr(0, 8).c_str()));
// Old Chrome app prefs are retained.
EXPECT_EQ(app_list_syncable_service->GetSyncItem(kExtensionId)->ToString(),
"kbmnembi { Nothing } [testapplistposition] "
"[testpinposition](INVALID COLOR)");
#endif
}
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigrationBrowserTest,
MigratePreferences) {
#if BUILDFLAG(IS_CHROMEOS)
app_list::AppListSyncableService* app_list_syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
AppListModelUpdater* app_list_model_updater =
app_list_syncable_service->GetModelUpdater();
app_list_model_updater->SetActive(true);
#endif
extensions::AppSorting* app_sorting =
extensions::ExtensionSystem::Get(profile())->app_sorting();
// Set up pre-migration state.
{
ASSERT_FALSE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
SyncExternalWebApps(/*expect_install=*/false);
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_TRUE(IsExtensionAppInstalled());
#if BUILDFLAG(IS_CHROMEOS)
app_list_model_updater->SetItemPosition(
kExtensionId, syncer::StringOrdinal("testapplistposition"));
app_list_syncable_service->SetPinPosition(
kExtensionId, syncer::StringOrdinal("testpinposition"));
EXPECT_EQ(app_list_syncable_service->GetSyncItem(kExtensionId)->ToString(),
"kbmnembi { Nothing } [testapplistposition] "
"[testpinposition](INVALID COLOR)");
#endif
// Set chrome://apps position.
app_sorting->SetAppLaunchOrdinal(kExtensionId,
syncer::StringOrdinal("testapplaunch"));
app_sorting->SetPageOrdinal(kExtensionId,
syncer::StringOrdinal("testpageordinal"));
// Set user preference to launch as browser tab.
extensions::SetLaunchType(profile(), kExtensionId,
extensions::LAUNCH_TYPE_REGULAR);
}
// Migrate extension app to web app.
{
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
// Extension sticks around to be uninstalled by the replacement web app.
EXPECT_TRUE(IsExtensionAppInstalled());
{
base::HistogramTester histograms;
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()));
SyncExternalWebApps(/*expect_install=*/true);
EXPECT_TRUE(IsWebAppInstalled());
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(uninstalled_app->id(), kExtensionId);
EXPECT_FALSE(IsExtensionAppInstalled());
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramInstallResult,
webapps::InstallResultCode::kSuccessNewInstall, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramUninstallAndReplaceCount, 1, 1);
}
}
// Check UI preferences have migrated across.
{
const webapps::AppId web_app_id = GetWebAppId();
#if BUILDFLAG(IS_CHROMEOS)
// Chrome OS shelf/list position should migrate.
EXPECT_EQ(app_list_syncable_service->GetSyncItem(GetWebAppId())->ToString(),
base::StringPrintf("%s { Basic web app } [testapplistposition] "
"[testpinposition](INVALID COLOR)",
GetWebAppId().substr(0, 8).c_str()));
// Chrome app shelf/list position should be retained.
EXPECT_EQ(app_list_syncable_service->GetSyncItem(kExtensionId)->ToString(),
"kbmnembi { Nothing } [testapplistposition] "
"[testpinposition](INVALID COLOR)");
#endif
// chrome://apps position should migrate.
EXPECT_EQ(app_sorting->GetAppLaunchOrdinal(web_app_id).ToDebugString(),
"testapplaunch");
EXPECT_EQ(app_sorting->GetPageOrdinal(web_app_id).ToDebugString(),
"testpageordinal");
// User launch preference should migrate across and override
// "launch_container": "window" in the JSON config.
EXPECT_EQ(WebAppProvider::GetForTest(profile())
->registrar_unsafe()
.GetAppUserDisplayMode(web_app_id),
mojom::UserDisplayMode::kBrowser);
}
}
static constexpr char kPlatformAppId[] = "dgbbhfbocdphnnabneckobeifilidpmj";
class PreinstalledWebAppMigratePlatformAppBrowserTest
: public PreinstalledWebAppMigrationBrowserTest {
public:
PreinstalledWebAppMigratePlatformAppBrowserTest()
: enable_chrome_apps_(
&extensions::testing::g_enable_chrome_apps_for_testing,
true) {
uninstall_and_replace_ = kPlatformAppId;
}
private:
base::AutoReset<bool> enable_chrome_apps_;
};
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigratePlatformAppBrowserTest,
MigratePlatformAppPreferences) {
// Install platform app to migrate.
{
apps::AppReadinessWaiter extension_app_registration_waiter(profile(),
kPlatformAppId);
ASSERT_EQ(InstallExtension(
test_data_dir_.AppendASCII("platform_apps/app_window_2"), 1)
->id(),
kPlatformAppId);
extension_app_registration_waiter.Await();
}
// Migrate extension app to web app.
{
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()));
SyncExternalWebApps(/*expect_install=*/true);
EXPECT_TRUE(IsWebAppInstalled());
uninstall_observer.WaitForExtensionUninstalled();
}
// Platform apps run in an app window so we must migrate as standalone.
EXPECT_EQ(WebAppProvider::GetForTest(profile())
->registrar_unsafe()
.GetAppUserDisplayMode(GetWebAppId()),
mojom::UserDisplayMode::kStandalone);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigrationBrowserTest,
UserUninstalledExtensionApp) {
// Set up pre-migration state.
{
ASSERT_FALSE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
SyncExternalWebApps(/*expect_install=*/false);
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_TRUE(IsExtensionAppInstalled());
}
{
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()), kExtensionId);
extensions::ExtensionRegistrar::Get(profile())->UninstallExtension(
kExtensionId, extensions::UNINSTALL_REASON_FOR_TESTING, nullptr);
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_FALSE(IsExtensionAppInstalled());
}
// Migrate extension app to web app.
{
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
EXPECT_FALSE(IsExtensionAppInstalled());
SyncExternalWebApps(/*expect_install=*/false);
EXPECT_FALSE(IsWebAppInstalled());
}
}
// Check histogram counts when an app to replace gets installed after the
// preinstalled web app is installed.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigrationBrowserTest,
AppToReplaceStillInstalled) {
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
// Preinstall web app.
{
base::HistogramTester histograms;
SyncExternalWebApps(/*expect_install=*/true);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramAppToReplaceStillInstalledCount, 0,
1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::
kHistogramAppToReplaceStillDefaultInstalledCount,
0, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::
kHistogramAppToReplaceStillInstalledInShelfCount,
0, 1);
}
// Manually install Extension app to be replaced.
LoadExtension(test_data_dir_.AppendASCII("hosted_app.crx"),
{.ignore_manifest_warnings = true});
// Re-sync preinstalled web apps.
{
base::HistogramTester histograms;
SyncExternalWebApps(/*expect_install=*/true);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramAppToReplaceStillInstalledCount, 1,
1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::
kHistogramAppToReplaceStillDefaultInstalledCount,
0, 1);
// Neither app has been added to the shelf.
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::
kHistogramAppToReplaceStillInstalledInShelfCount,
0, 1);
}
#if BUILDFLAG(IS_CHROMEOS)
// Pin both apps to the shelf.
PinAppWithIDToShelf(GetWebAppId());
PinAppWithIDToShelf(kExtensionId);
// Re-sync preinstalled web apps.
{
base::HistogramTester histograms;
SyncExternalWebApps(/*expect_install=*/true);
// Apps have been added to the shelf.
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::
kHistogramAppToReplaceStillInstalledInShelfCount,
1, 1);
}
#endif // BUILDFLAG(IS_CHROMEOS)
}
// Tests the migration from an extension-app to a preinstalled web app provided
// by the preinstalled apps (rather than an external config).
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppMigrationBrowserTest,
MigrateToPreinstalledWebApp) {
ScopedTestingPreinstalledAppData preinstalled_apps;
ExternalInstallOptions options(GetWebAppUrl(),
mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kExternalDefault);
options.gate_on_feature = kMigrationFlag;
options.user_type_allowlist = {"unmanaged"};
options.uninstall_and_replace.push_back(kExtensionId);
options.only_use_app_info_factory = true;
options.app_info_factory = base::BindLambdaForTesting([&]() {
auto info = WebAppInstallInfo::CreateWithStartUrlForTesting(GetWebAppUrl());
info->title = u"Test app";
return info;
});
preinstalled_apps.apps.push_back(std::move(options));
EXPECT_EQ(1u, GetPreinstalledWebApps(*profile()).size());
// Set up pre-migration state.
{
base::HistogramTester histograms;
ASSERT_FALSE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
SyncExternalWebApps(/*expect_install=*/false,
/*expect_uninstall=*/std::nullopt,
/*pass_config=*/false);
EXPECT_FALSE(IsWebAppInstalled());
EXPECT_TRUE(IsExtensionAppInstalled());
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramEnabledCount, 0, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramDisabledCount, 1, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramConfigErrorCount, 0, 1);
}
// Migrate extension app to web app.
{
base::AutoReset<bool> testing_scope =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
ASSERT_TRUE(IsPreinstalledAppInstallFeatureEnabled(kMigrationFlag));
SyncExternalExtensions();
// Extension sticks around to be uninstalled by the replacement web app.
EXPECT_TRUE(IsExtensionAppInstalled());
{
base::HistogramTester histograms;
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile()));
SyncExternalWebApps(/*expect_install=*/true,
/*expect_uninstall=*/std::nullopt,
/*pass_config=*/false);
EXPECT_TRUE(IsWebAppInstalled());
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(uninstalled_app->id(), kExtensionId);
EXPECT_FALSE(IsExtensionAppInstalled());
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramEnabledCount, 1, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramDisabledCount, 0, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramConfigErrorCount, 0, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramInstallResult,
webapps::InstallResultCode::kSuccessOfflineOnlyInstall, 1);
histograms.ExpectUniqueSample(
PreinstalledWebAppManager::kHistogramUninstallAndReplaceCount, 1, 1);
}
}
}
} // namespace web_app