blob: 2838ac3c595d15252a1716ba5b9547f246c715ad [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 "chrome/browser/web_applications/preinstalled_web_app_manager.h"
#include <string_view>
#include "base/auto_reset.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.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/intent_helper/preferred_apps_test_util.h"
#include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h"
#include "chrome/browser/extensions/extension_browsertest.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/ui/web_applications/test/web_app_browsertest_util.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/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/preinstalled_app_install_features.h"
#include "chrome/browser/web_applications/preinstalled_web_app_config_utils.h"
#include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/test_file_utils.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.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_install_info.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.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_update.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "content/public/test/url_loader_interceptor.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "net/ssl/ssl_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/touchscreen_device.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "ash/public/cpp/test/app_list_test_api.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.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 "chromeos/constants/chromeos_features.h"
#endif
namespace web_app {
namespace {
constexpr char kBaseDataDir[] = "chrome/test/data/banners";
// start_url in manifest.json matches navigation url for the simple
// manifest_test_page.html.
constexpr char kSimpleManifestStartUrl[] =
"https://example.org/manifest_test_page.html";
constexpr char kNoManifestTestPageStartUrl[] =
"https://example.org/no_manifest_test_page.html";
// Performs blocking IO operations.
base::FilePath GetDataFilePath(const base::FilePath& relative_path,
bool* path_exists) {
base::ScopedAllowBlockingForTesting allow_io;
base::FilePath root_path;
CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_path));
base::FilePath path = root_path.Append(relative_path);
*path_exists = base::PathExists(path);
return path;
}
#if BUILDFLAG(IS_CHROMEOS)
void ExpectInitialManifestFieldsFromBasicWebApp(WebAppIconManager& icon_manager,
const WebApp* web_app,
const GURL& expect_start_url,
const GURL& expect_scope) {
// Manifest fields:
EXPECT_EQ(web_app->untranslated_name(), "Basic web app");
EXPECT_EQ(web_app->start_url().spec(), expect_start_url);
EXPECT_EQ(web_app->scope().spec(), expect_scope);
EXPECT_EQ(web_app->display_mode(), DisplayMode::kStandalone);
EXPECT_FALSE(web_app->theme_color().has_value());
EXPECT_FALSE(web_app->sync_proto().has_theme_color());
EXPECT_EQ("Basic web app", web_app->sync_proto().name());
EXPECT_EQ(expect_scope.spec(), web_app->sync_proto().scope());
ASSERT_EQ(2, web_app->sync_proto().icon_infos_size());
EXPECT_EQ(expect_start_url.Resolve("basic-48.png").spec(),
web_app->sync_proto().icon_infos(0).url());
EXPECT_EQ(48, web_app->sync_proto().icon_infos(0).size_in_px());
EXPECT_EQ(sync_pb::WebAppIconInfo_Purpose_ANY,
web_app->sync_proto().icon_infos(0).purpose());
EXPECT_EQ(expect_start_url.Resolve("basic-192.png").spec(),
web_app->sync_proto().icon_infos(1).url());
EXPECT_EQ(192, web_app->sync_proto().icon_infos(1).size_in_px());
EXPECT_EQ(sync_pb::WebAppIconInfo_Purpose_ANY,
web_app->sync_proto().icon_infos(1).purpose());
// Manifest Resources: This is chrome/test/data/web_apps/basic-192.png
EXPECT_EQ(IconManagerReadAppIconPixel(icon_manager, web_app->app_id(),
/*size_px=*/192),
SK_ColorBLACK);
// User preferences:
EXPECT_EQ(web_app->user_display_mode(), mojom::UserDisplayMode::kStandalone);
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace
class PreinstalledWebAppManagerBrowserTestBase
: public extensions::ExtensionBrowserTest {
public:
PreinstalledWebAppManagerBrowserTestBase()
: skip_preinstalled_web_app_startup_(
PreinstalledWebAppManager::SkipStartupForTesting()) {}
// InProcessBrowserTest:
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
web_app::test::WaitUntilReady(
WebAppProvider::GetForTest(browser()->profile()));
}
void TearDownOnMainThread() override {
ResetInterceptor();
ExtensionBrowserTest::TearDownOnMainThread();
}
void InitUrlLoaderInterceptor() {
// We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since
// a stable app_id across tests requires stable origin, whereas
// EmbeddedTestServer serves content on a random port.
url_loader_interceptor_ =
std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
[](content::URLLoaderInterceptor::RequestParams* params) -> bool {
std::string relative_request = base::StrCat(
{kBaseDataDir, params->url_request.url.path_piece()});
base::FilePath relative_path =
base::FilePath().AppendASCII(relative_request);
bool path_exists = false;
base::FilePath path =
GetDataFilePath(relative_path, &path_exists);
if (!path_exists)
return /*intercepted=*/false;
// Provide fake SSLInfo to avoid NOT_FROM_SECURE_ORIGIN error in
// InstallableManager::GetData().
net::SSLInfo ssl_info;
CreateFakeSslInfoCertificate(&ssl_info);
content::URLLoaderInterceptor::WriteResponse(
path, params->client.get(), /*headers=*/nullptr, ssl_info,
params->url_request.url);
return /*intercepted=*/true;
}));
}
GURL GetAppUrl() const {
return embedded_test_server()->GetURL("/web_apps/basic.html");
}
WebAppRegistrar& registrar() { return provider().registrar_unsafe(); }
WebAppIconManager& icon_manager() { return provider().icon_manager(); }
PreinstalledWebAppManager& manager() {
return provider().preinstalled_web_app_manager();
}
WebAppProvider& provider() { return *WebAppProvider::GetForTest(profile()); }
void SyncEmptyConfigs() {
base::Value::List app_configs;
base::AutoReset<const base::Value::List*> configs_for_testing =
PreinstalledWebAppManager::SetConfigsForTesting(&app_configs);
base::RunLoop run_loop;
WebAppProvider::GetForTest(profile())
->preinstalled_web_app_manager()
.LoadAndSynchronizeForTesting(base::BindLambdaForTesting(
[&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode>
uninstall_results) {
EXPECT_EQ(install_results.size(), 0u);
EXPECT_EQ(uninstall_results.size(), 0u);
run_loop.Quit();
}));
run_loop.Run();
}
// Mocks "icon.png" as chrome/test/data/web_apps/blue-192.png.
std::optional<webapps::InstallResultCode> SyncPreinstalledAppConfig(
const GURL& install_url,
std::string_view app_config_string) {
base::FilePath test_config_dir(FILE_PATH_LITERAL("test_dir"));
auto config_auto_reset =
test::SetPreinstalledWebAppConfigDirForTesting(test_config_dir);
base::FilePath source_root_dir;
CHECK(
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &source_root_dir));
base::FilePath test_icon_path =
source_root_dir.Append(GetChromeTestDataDir())
.AppendASCII("web_apps/blue-192.png");
scoped_refptr<TestFileUtils> file_utils = TestFileUtils::Create(
{{base::FilePath(FILE_PATH_LITERAL("test_dir/icon.png")),
test_icon_path}});
base::AutoReset<FileUtilsWrapper*> file_utils_for_testing =
PreinstalledWebAppManager::SetFileUtilsForTesting(file_utils.get());
base::Value::List app_configs;
auto json_parse_result =
base::JSONReader::ReadAndReturnValueWithError(app_config_string);
EXPECT_TRUE(json_parse_result.has_value())
<< "JSON parse error: " << json_parse_result.error().message;
if (!json_parse_result.has_value())
return std::nullopt;
app_configs.Append(std::move(*json_parse_result));
base::AutoReset<const base::Value::List*> configs_for_testing =
PreinstalledWebAppManager::SetConfigsForTesting(&app_configs);
std::optional<webapps::InstallResultCode> code;
base::RunLoop sync_run_loop;
WebAppProvider::GetForTest(profile())
->preinstalled_web_app_manager()
.LoadAndSynchronizeForTesting(base::BindLambdaForTesting(
[&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode>
uninstall_results) {
auto it = install_results.find(install_url);
if (it != install_results.end())
code = it->second.code;
sync_run_loop.Quit();
}));
sync_run_loop.Run();
return code;
}
struct SyncResults {
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results;
std::map<GURL, webapps::UninstallResultCode> uninstall_results;
};
SyncResults SyncPreinstalledApps() {
base::test::TestFuture<
std::map<GURL, ExternallyManagedAppManager::InstallResult>,
std::map<GURL, webapps::UninstallResultCode>>
future;
WebAppProvider::GetForTest(profile())
->preinstalled_web_app_manager()
.LoadAndSynchronizeForTesting(future.GetCallback());
return {
.install_results = future.Get<
std::map<GURL, ExternallyManagedAppManager::InstallResult>>(),
.uninstall_results =
future.Get<std::map<GURL, webapps::UninstallResultCode>>(),
};
}
~PreinstalledWebAppManagerBrowserTestBase() override = default;
protected:
void ResetInterceptor() { url_loader_interceptor_.reset(); }
private:
web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
base::AutoReset<bool> skip_preinstalled_web_app_startup_;
};
class PreinstalledWebAppManagerBrowserTest
: public PreinstalledWebAppManagerBrowserTestBase {
public:
PreinstalledWebAppManagerBrowserTest() = default;
private:
base::test::ScopedFeatureList feature_list_{features::kRecordWebAppDebugInfo};
};
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
LaunchQueryParamsBasic) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
GURL start_url = embedded_test_server()->GetURL("/web_apps/basic.html");
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"launch_query_params": "test_launch_params"
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {start_url.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(start_url, app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), start_url);
GURL launch_url =
embedded_test_server()->GetURL("/web_apps/basic.html?test_launch_params");
EXPECT_EQ(registrar().GetAppLaunchUrl(app_id), launch_url);
// This is required to allow launching to work.
provider().scheduler().SynchronizeOsIntegration(app_id, base::DoNothing());
Browser* app_browser = LaunchWebAppBrowserAndWait(profile(), app_id);
EXPECT_EQ(
app_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
launch_url);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
LaunchQueryParamsDuplicate) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url = embedded_test_server()->GetURL(
"/web_apps/query_params_in_start_url.html");
GURL start_url = embedded_test_server()->GetURL(
"/web_apps/query_params_in_start_url.html?query_params=in&start=url");
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"launch_query_params": "query_params=in"
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {install_url.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(install_url, app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), start_url);
// We should not duplicate the query param if start_url already has it.
EXPECT_EQ(registrar().GetAppLaunchUrl(app_id), start_url);
// This is required to allow launching to work.
provider().scheduler().SynchronizeOsIntegration(app_id, base::DoNothing());
Browser* app_browser = LaunchWebAppBrowserAndWait(profile(), app_id);
EXPECT_EQ(
app_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
start_url);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
LaunchQueryParamsMultiple) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
GURL start_url = embedded_test_server()->GetURL("/web_apps/basic.html");
GURL launch_url = embedded_test_server()->GetURL(
"/web_apps/basic.html?more=than&one=query&param");
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"launch_query_params": "more=than&one=query&param"
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {start_url.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(start_url, app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), start_url);
EXPECT_EQ(registrar().GetAppLaunchUrl(app_id), launch_url);
// This is required to allow launching to work.
provider().scheduler().SynchronizeOsIntegration(app_id, base::DoNothing());
Browser* app_browser = LaunchWebAppBrowserAndWait(profile(), app_id);
EXPECT_EQ(
app_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
launch_url);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
LaunchQueryParamsComplex) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url = embedded_test_server()->GetURL(
"/web_apps/query_params_in_start_url.html");
GURL start_url = embedded_test_server()->GetURL(
"/web_apps/query_params_in_start_url.html?query_params=in&start=url");
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"launch_query_params": "!@#$$%^*&)("
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {install_url.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(install_url, app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), start_url);
GURL launch_url = embedded_test_server()->GetURL(
"/web_apps/"
"query_params_in_start_url.html?query_params=in&start=url&!@%23$%^*&)(");
EXPECT_EQ(registrar().GetAppLaunchUrl(app_id), launch_url);
provider().scheduler().SynchronizeOsIntegration(app_id, base::DoNothing());
Browser* app_browser = LaunchWebAppBrowserAndWait(profile(), app_id);
EXPECT_EQ(
app_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
launch_url);
}
class PreinstalledWebAppManagerExtensionBrowserTest
: public PreinstalledWebAppManagerBrowserTest {
public:
PreinstalledWebAppManagerExtensionBrowserTest()
: enable_chrome_apps_(
&extensions::testing::g_enable_chrome_apps_for_testing,
true) {}
~PreinstalledWebAppManagerExtensionBrowserTest() override = default;
void SetUpOnMainThread() override {
PreinstalledWebAppManagerBrowserTest::SetUpOnMainThread();
web_app::test::WaitUntilReady(
WebAppProvider::GetForTest(browser()->profile()));
}
void TearDownOnMainThread() override {
ResetInterceptor();
PreinstalledWebAppManagerBrowserTest::TearDownOnMainThread();
}
private:
base::AutoReset<bool> enable_chrome_apps_;
};
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerExtensionBrowserTest,
UninstallAndReplace) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
// Install Chrome app to be replaced.
const char kChromeAppDirectory[] = "app";
const char kChromeAppName[] = "App Test";
const extensions::Extension* app = InstallExtensionWithSourceAndFlags(
test_data_dir_.AppendASCII(kChromeAppDirectory), 1,
extensions::mojom::ManifestLocation::kInternal,
extensions::Extension::NO_FLAGS);
EXPECT_EQ(app->name(), kChromeAppName);
// Start listening for Chrome app uninstall.
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(browser()->profile()));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"uninstall_and_replace": ["$2"]
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {GetAppUrl().spec(), app->id()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
// Chrome app should get uninstalled.
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(app, uninstalled_app.get());
}
#if BUILDFLAG(IS_CHROMEOS)
class PreinstalledWebAppManagerExtensionAlwaysMigrateBrowserTest
: public PreinstalledWebAppManagerExtensionBrowserTest {
public:
PreinstalledWebAppManagerExtensionAlwaysMigrateBrowserTest() = default;
~PreinstalledWebAppManagerExtensionAlwaysMigrateBrowserTest() override =
default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kPreinstalledWebAppAlwaysMigrate};
};
IN_PROC_BROWSER_TEST_F(
PreinstalledWebAppManagerExtensionAlwaysMigrateBrowserTest,
AlwaysUninstallAndReplace) {
GURL app_url = GURL("https://example.org/");
std::optional<extensions::ExtensionId> chrome_app_id;
base::HistogramTester tester;
ScopedTestingPreinstalledAppData scoped_preinstalls;
scoped_preinstalls.apps.push_back([&] {
ExternalInstallOptions options(
app_url, /*user_display_mode=*/mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
options.user_type_allowlist = {"unmanaged"};
options.only_use_app_info_factory = true;
options.app_info_factory = base::BindLambdaForTesting([=] {
auto info = std::make_unique<WebAppInstallInfo>(
GenerateManifestIdFromStartUrlOnly(app_url), app_url);
info->title = u"Test app";
return info;
});
return options;
}());
// Preinstall web app.
EXPECT_EQ(SyncPreinstalledApps().install_results[app_url].code,
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
tester.ExpectUniqueSample("WebApp.Preinstalled.ChromeAppMigrationNeeded",
false, 1);
// Install Chrome app to be replaced after web app is already preinstalled.
const extensions::Extension* app = InstallExtensionWithSourceAndFlags(
test_data_dir_.AppendASCII("app"), 1,
extensions::mojom::ManifestLocation::kInternal,
extensions::Extension::NO_FLAGS);
scoped_preinstalls.apps[0].uninstall_and_replace.push_back(app->id());
// Start listening for Chrome app uninstall.
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(browser()->profile()));
// Trigger preinstall sync again.
EXPECT_EQ(SyncPreinstalledApps().install_results[app_url].code,
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
tester.ExpectBucketCount("WebApp.Preinstalled.ChromeAppMigrationNeeded", true,
1);
// Chrome app should get uninstalled.
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(app, uninstalled_app.get());
}
#endif // BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PreinstalledAppsPrefInstall) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
profile()->GetPrefs()->SetString(prefs::kPreinstalledApps, "install");
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {GetAppUrl().spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PreinstalledAppsPrefNoinstall) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
profile()->GetPrefs()->SetString(prefs::kPreinstalledApps, "noinstall");
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {GetAppUrl().spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config), std::nullopt);
}
const char kOnlyIfPreviouslyPreinstalled_PreviousConfig[] = R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})";
const char kOnlyIfPreviouslyPreinstalled_NextConfig[] = R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_if_previously_preinstalled": true
})";
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PRE_OnlyIfPreviouslyPreinstalled_AppPreserved) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
InitUrlLoaderInterceptor();
std::string prev_app_config = base::ReplaceStringPlaceholders(
kOnlyIfPreviouslyPreinstalled_PreviousConfig, {kSimpleManifestStartUrl},
nullptr);
// The user had the app installed.
EXPECT_EQ(
SyncPreinstalledAppConfig(GURL{kSimpleManifestStartUrl}, prev_app_config),
webapps::InstallResultCode::kSuccessNewInstall);
webapps::AppId app_id = GenerateAppId(/*manifest_id=*/std::nullopt,
GURL{kSimpleManifestStartUrl});
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OnlyIfPreviouslyPreinstalled_AppPreserved) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
InitUrlLoaderInterceptor();
std::string next_app_config =
base::ReplaceStringPlaceholders(kOnlyIfPreviouslyPreinstalled_NextConfig,
{kSimpleManifestStartUrl}, nullptr);
// The user still has the app.
EXPECT_EQ(
SyncPreinstalledAppConfig(GURL{kSimpleManifestStartUrl}, next_app_config),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
webapps::AppId app_id = GenerateAppId(/*manifest_id=*/std::nullopt,
GURL{kSimpleManifestStartUrl});
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PRE_OnlyIfPreviouslyPreinstalled_NoAppPreinstalled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
InitUrlLoaderInterceptor();
std::string prev_app_config = base::ReplaceStringPlaceholders(
kOnlyIfPreviouslyPreinstalled_PreviousConfig,
{kNoManifestTestPageStartUrl}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GURL{kNoManifestTestPageStartUrl},
prev_app_config),
webapps::InstallResultCode::kNotValidManifestForWebApp);
webapps::AppId app_id = GenerateAppId(/*manifest_id=*/std::nullopt,
GURL{kNoManifestTestPageStartUrl});
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OnlyIfPreviouslyPreinstalled_NoAppPreinstalled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
InitUrlLoaderInterceptor();
std::string next_app_config =
base::ReplaceStringPlaceholders(kOnlyIfPreviouslyPreinstalled_NextConfig,
{kNoManifestTestPageStartUrl}, nullptr);
// The user has no the app.
EXPECT_EQ(SyncPreinstalledAppConfig(GURL{kNoManifestTestPageStartUrl},
next_app_config),
std::nullopt);
webapps::AppId app_id = GenerateAppId(/*manifest_id=*/std::nullopt,
GURL{kNoManifestTestPageStartUrl});
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
}
const char kFeatureNameOrInstalledConfig[] = R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"feature_name_or_installed": "test_feature"
})";
// When the "feature_name_or_installed" feature is enabled, the app should be
// installed.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
GateOnFeatureNameOrInstalled_InstallWhenEnabled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
base::AutoReset<bool> enable_feature =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
std::string app_config = base::ReplaceStringPlaceholders(
kFeatureNameOrInstalledConfig, {GetAppUrl().spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
}
// When the "feature_name_or_installed" feature is disabled, the app should not
// be installed.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
GateOnFeatureNameOrInstalled_IgnoreWhenDisabled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
std::string app_config = base::ReplaceStringPlaceholders(
kFeatureNameOrInstalledConfig, {GetAppUrl().spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config), std::nullopt);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
}
// When the "feature_name_or_installed" feature is disabled, any existing
// preinstalled app should not be uninstalled.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
Installed_DoNotUninstallWhenDisabled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
std::string app_config = base::ReplaceStringPlaceholders(
kFeatureNameOrInstalledConfig, {GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
{
base::AutoReset<bool> enable_feature =
SetPreinstalledAppInstallFeatureAlwaysEnabledForTesting();
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
}
{
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
}
}
// Preinstalled apps which are user uninstalled become ignored configs.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
DisableForPreinstalledAppsInConfig) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
const std::string config = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
// Preinstall web app.
{
base::HistogramTester tester;
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
tester.ExpectUniqueSample("WebApp.Preinstalled.DisabledReason",
/*kNotDisabled*/ 0, 1);
}
// Mark web app as user uninstalled without uninstalling it.
// This is an erroneous state that users have gotten into in the past via
// database migration bugs, see crbug.com/1393284 and crbug.com/1363004 for
// past incidents.
{
UserUninstalledPreinstalledWebAppPrefs prefs(profile()->GetPrefs());
prefs.Add(app_id, {GetAppUrl()});
ASSERT_EQ(app_id, prefs.LookUpAppIdByInstallUrl(GetAppUrl()));
}
// Check web app does not get uninstalled by PWAM sync.
{
base::HistogramTester tester;
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), config),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
tester.ExpectUniqueSample("WebApp.Preinstalled.DisabledReason",
/*kIgnorePreviouslyUninstalledByUser*/ 17, 1);
}
// Actually uninstall web app.
{
base::test::TestFuture<webapps::UninstallResultCode> future;
provider().scheduler().RemoveUserUninstallableManagements(
app_id, webapps::WebappUninstallSource::kAppMenu, future.GetCallback());
ASSERT_EQ(future.Get(), webapps::UninstallResultCode::kAppRemoved);
ASSERT_FALSE(registrar().IsInRegistrar(app_id));
}
// Check web app does not get installed by PWAM sync.
{
base::HistogramTester tester;
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), config), std::nullopt);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
tester.ExpectUniqueSample("WebApp.Preinstalled.DisabledReason",
/*kIgnorePreviouslyUninstalledByUser*/ 17, 1);
}
}
// Preinstalled apps which are user uninstalled are included
// in the config passed to the ExternallyManagedAppInstallManager if
// |override_previous_user_uninstall| is true.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PreinstalledAppsUninstallOverride) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
base::AutoReset<bool> override_previous_user_uninstall_config =
PreinstalledWebAppManager::
OverridePreviousUserUninstallConfigForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
ASSERT_FALSE(registrar().IsInRegistrar(app_id));
UserUninstalledPreinstalledWebAppPrefs prefs(profile()->GetPrefs());
prefs.Add(app_id, {GetAppUrl()});
// Verify prefs have the proper data.
EXPECT_EQ(1, prefs.Size());
EXPECT_EQ(app_id, prefs.LookUpAppIdByInstallUrl(GetAppUrl()));
// On sync across configs, app is installed because
// |override_previous_user_uninstall| is true.
const auto& ignore_configs = manager().debug_info()->ignore_configs;
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
EXPECT_EQ(ignore_configs.size(), 0u);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
IgnoreCorruptUserUninstalledPreinstalledWebAppPrefs) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"]
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {GetAppUrl().spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
EXPECT_TRUE(registrar().IsInstalledByDefaultManagement(app_id));
// Simulate the effects of https://crbug.com/1359205 by adding an installed
// preinstalled web app to the "has been uninstalled by the user" pref even
// though the web app is still kDefault installed.
UserUninstalledPreinstalledWebAppPrefs(profile()->GetPrefs())
.Add(app_id, {GetAppUrl()});
// Check that the PreinstalledWebAppManager doesn't uninstall the web app
// just because the prefs say it's uninstalled.
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
EXPECT_TRUE(registrar().IsInstalledByDefaultManagement(app_id));
}
// The offline manifest JSON config functionality is only available on Chrome
// OS.
#if BUILDFLAG(IS_CHROMEOS)
// Check that offline fallback installs work offline.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OfflineFallbackManifestSiteOffline) {
constexpr char kAppInstallUrl[] = "https://offline-site.com/install.html";
constexpr char kAppName[] = "Offline app name";
constexpr char kAppStartUrl[] = "https://offline-site.com/start.html";
constexpr char kAppScope[] = "https://offline-site.com/";
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GURL(kAppStartUrl));
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {kAppInstallUrl, kAppName, kAppStartUrl, kAppScope},
nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GURL(kAppInstallUrl), app_config),
webapps::InstallResultCode::kSuccessOfflineFallbackInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), kAppStartUrl);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), kAppScope);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetAppDisplayMode(app_id), DisplayMode::kMinimalUi);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(
IconManagerReadAppIconPixel(icon_manager(), app_id, /*size_px=*/192),
SK_ColorBLUE);
}
// Check that offline fallback installs attempt fetching the install_url.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OfflineFallbackManifestSiteOnline) {
ASSERT_TRUE(embedded_test_server()->Start());
// This install_url serves a manifest with different values to what we specify
// in the offline_manifest. Check that it gets used instead of the
// offline_manifest.
GURL install_url = embedded_test_server()->GetURL("/web_apps/basic.html");
GURL offline_start_url = embedded_test_server()->GetURL(
"/web_apps/offline-only-start-url-that-does-not-exist.html");
GURL scope = embedded_test_server()->GetURL("/web_apps/");
webapps::AppId offline_app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, offline_start_url);
EXPECT_FALSE(registrar().IsInRegistrar(offline_app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"offline_manifest": {
"name": "Offline only app name",
"start_url": "$2",
"scope": "$3",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate,
{install_url.spec(), offline_start_url.spec(), scope.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(install_url, app_config),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_FALSE(registrar().IsInRegistrar(offline_app_id));
// basic.html's manifest start_url is basic.html.
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, install_url);
EXPECT_EQ(registrar().GetInstallState(app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_EQ(registrar().GetAppShortName(app_id), "Basic web app");
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), install_url);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), scope);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetAppDisplayMode(app_id), DisplayMode::kStandalone);
}
// Check that offline only installs work offline.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OfflineOnlyManifestSiteOffline) {
constexpr char kAppInstallUrl[] = "https://offline-site.com/install.html";
constexpr char kAppName[] = "Offline app name";
constexpr char kAppStartUrl[] = "https://offline-site.com/start.html";
constexpr char kAppScope[] = "https://offline-site.com/";
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GURL(kAppStartUrl));
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {kAppInstallUrl, kAppName, kAppStartUrl, kAppScope},
nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GURL(kAppInstallUrl), app_config),
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), kAppStartUrl);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), kAppScope);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetAppDisplayMode(app_id), DisplayMode::kMinimalUi);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(
IconManagerReadAppIconPixel(icon_manager(), app_id, /*size_px=*/192),
SK_ColorBLUE);
}
// Check that offline only installs don't fetch from the install_url.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OfflineOnlyManifestSiteOnline) {
ASSERT_TRUE(embedded_test_server()->Start());
// This install_url serves a manifest with different values to what we specify
// in the offline_manifest. Check that it doesn't get used.
GURL install_url = GetAppUrl();
const char kAppName[] = "Offline only app name";
GURL start_url = embedded_test_server()->GetURL(
"/web_apps/offline-only-start-url-that-does-not-exist.html");
GURL scope = embedded_test_server()->GetURL("/web_apps/");
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, start_url);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate,
{install_url.spec(), kAppName, start_url.spec(), scope.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(install_url, app_config),
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppStartUrl(app_id).spec(), start_url);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), scope);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetAppDisplayMode(app_id), DisplayMode::kMinimalUi);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(
IconManagerReadAppIconPixel(icon_manager(), app_id, /*size_px=*/192),
SK_ColorBLUE);
}
const char kOnlyForNewUsersInstallUrl[] = "https://example.org/";
const char kOnlyForNewUsersConfig[] = R"({
"app_url": "https://example.org/",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_for_new_users": true,
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "Test",
"start_url": "https://example.org/",
"scope": "https://example.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
})";
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PRE_OnlyForNewUsersWithNewUser) {
// Install a policy app first to check that it doesn't interfere.
{
base::RunLoop run_loop;
WebAppPolicyManager& policy_manager =
WebAppProvider::GetForTest(profile())->policy_manager();
policy_manager.SetOnAppsSynchronizedCompletedCallbackForTesting(
run_loop.QuitClosure());
const char kWebAppPolicy[] = R"([{
"url": "https://policy-example.org/",
"default_launch_container": "window"
}])";
profile()->GetPrefs()->Set(prefs::kWebAppInstallForceList,
base::JSONReader::Read(kWebAppPolicy).value());
run_loop.Run();
}
// New user should have the app installed.
EXPECT_EQ(SyncPreinstalledAppConfig(GURL(kOnlyForNewUsersInstallUrl),
kOnlyForNewUsersConfig),
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OnlyForNewUsersWithNewUser) {
// App should persist after user stops being a new user.
EXPECT_EQ(SyncPreinstalledAppConfig(GURL(kOnlyForNewUsersInstallUrl),
kOnlyForNewUsersConfig),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
PRE_OnlyForNewUsersWithOldUser) {
// Simulate running Chrome without the configs present.
SyncEmptyConfigs();
}
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OnlyForNewUsersWithOldUser) {
// This instance of Chrome should be considered not a new user after the
// previous PRE_ launch and sync.
EXPECT_EQ(SyncPreinstalledAppConfig(GURL(kOnlyForNewUsersInstallUrl),
kOnlyForNewUsersConfig),
std::nullopt);
}
#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest, OemInstalled) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(),
base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"oem_installed": true,
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr)),
webapps::InstallResultCode::kSuccessNewInstall);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
EXPECT_TRUE(registrar().WasInstalledByOem(app_id));
// Wait for app service to see the newly installed app.
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile());
apps::InstallReason install_reason = apps::InstallReason::kUnknown;
proxy->AppRegistryCache().ForOneApp(app_id,
[&](const apps::AppUpdate& update) {
install_reason = update.InstallReason();
});
EXPECT_EQ(install_reason, apps::InstallReason::kOem);
}
#endif // BUILDFLAG(IS_CHROMEOS)
namespace {
ui::TouchscreenDevice CreateTouchDevice(ui::InputDeviceType type,
bool stylus_support) {
ui::TouchscreenDevice touch_device = ui::TouchscreenDevice();
touch_device.type = type;
touch_device.has_stylus = stylus_support;
return touch_device;
}
} // namespace
IN_PROC_BROWSER_TEST_F(
PreinstalledWebAppManagerBrowserTest,
DisableIfTouchscreenWithStylusNotSupported_NoStylusSupport) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
{CreateTouchDevice(ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
/*stylus_support=*/false)});
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"disable_if_touchscreen_with_stylus_not_supported": true,
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
const auto& ignore_configs = manager().debug_info()->ignore_configs;
constexpr char kErrorMessage[] =
" ignore because the device does not have a built-in touchscreen with "
"stylus support.";
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest), std::nullopt);
EXPECT_FALSE(registrar().IsInRegistrar(app_id));
EXPECT_EQ(ignore_configs.size(), 1u);
EXPECT_EQ(ignore_configs.back().second, GetAppUrl().spec() + kErrorMessage);
}
IN_PROC_BROWSER_TEST_F(
PreinstalledWebAppManagerBrowserTest,
DisableIfTouchscreenWithStylusNotSupported_HasStylusSupport) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
{CreateTouchDevice(ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
/*stylus_support=*/true)});
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"disable_if_touchscreen_with_stylus_not_supported": true,
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
}
// Verify that stylus detection works even if DeviceDataManager is slow to
// initialize.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
DisableIfTouchscreenWithStylusStartupDelay) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"disable_if_touchscreen_with_stylus_not_supported": true,
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
// Clear out the device list and re-initialize it after a delay. Web app
// installation should wait for this to be ready.
ui::DeviceDataManager::GetInstance()->ResetDeviceListsForTest();
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindLambdaForTesting([]() {
// Create a built-in touchscreen device with stylus support
// and add it to the device.
ui::DeviceDataManagerTestApi().SetTouchscreenDevices({CreateTouchDevice(
ui::InputDeviceType::INPUT_DEVICE_INTERNAL, true)});
ui::DeviceDataManagerTestApi().OnDeviceListsComplete();
}),
base::Milliseconds(500));
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessNewInstall);
}
#if BUILDFLAG(IS_CHROMEOS)
// Disabled due to test flakiness. https://crbug.com/1267164.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
DISABLED_UninstallFromTwoItemAppListFolder) {
GURL preinstalled_app_start_url("https://example.org/");
GURL user_app_start_url("https://test.org/");
apps::AppServiceProxy* proxy =
apps::AppServiceProxyFactory::GetForProfile(profile());
AppListClientImpl::GetInstance()->UpdateProfile();
ash::AppListTestApi app_list_test_api;
app_list::AppListSyncableService* app_list_syncable_service =
app_list::AppListSyncableServiceFactory::GetForProfile(profile());
// Install default app.
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "Test default app",
"display": "standalone",
"start_url": "$1",
"scope": "$1",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate, {preinstalled_app_start_url.spec()}, nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(preinstalled_app_start_url, app_config),
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
webapps::AppId preinstalled_app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, preinstalled_app_start_url);
// Install user app.
auto install_info =
WebAppInstallInfo::CreateWithStartUrlForTesting(user_app_start_url);
install_info->title = u"Test user app";
webapps::AppId user_app_id =
web_app::test::InstallWebApp(profile(), std::move(install_info));
// Put apps in app list folder.
std::string folder_id = app_list_test_api.CreateFolderWithApps(
{preinstalled_app_id, user_app_id});
EXPECT_EQ(
app_list_syncable_service->GetSyncItem(preinstalled_app_id)->parent_id,
folder_id);
EXPECT_EQ(app_list_syncable_service->GetSyncItem(user_app_id)->parent_id,
folder_id);
// Uninstall default app.
proxy->UninstallSilently(preinstalled_app_id,
apps::UninstallSource::kUnknown);
// Default app should be removed from local app list but remain in sync list.
EXPECT_FALSE(registrar().IsInRegistrar(preinstalled_app_id));
EXPECT_EQ(registrar().GetInstallState(user_app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_FALSE(app_list_test_api.HasApp(preinstalled_app_id));
EXPECT_TRUE(app_list_test_api.HasApp(user_app_id));
EXPECT_EQ(
app_list_syncable_service->GetSyncItem(preinstalled_app_id)->parent_id,
"");
EXPECT_EQ(app_list_syncable_service->GetSyncItem(user_app_id)->parent_id, "");
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Check that offline only installs don't overwrite fresh online manifest
// obtained via sync install.
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerBrowserTest,
OfflineOnlyManifest_SiteAlreadyInstalledFromSync) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url = GetAppUrl();
GURL start_url = install_url;
GURL scope = embedded_test_server()->GetURL("/web_apps/");
const webapps::AppId app_id = InstallWebAppFromPage(browser(), install_url);
const WebApp* web_app = registrar().GetAppById(app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->IsSynced());
EXPECT_FALSE(web_app->IsPreinstalledApp());
{
SCOPED_TRACE("Expect initial manifest fields from basic.html web app.");
ExpectInitialManifestFieldsFromBasicWebApp(icon_manager(), web_app,
start_url, scope);
}
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "tab",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate,
{install_url.spec(), "Overwrite app name", start_url.spec(),
"https://overwrite.scope/"},
nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(install_url, app_config),
webapps::InstallResultCode::kSuccessOfflineOnlyInstall);
EXPECT_EQ(web_app, registrar().GetAppById(app_id));
EXPECT_TRUE(web_app->IsSynced());
EXPECT_TRUE(web_app->IsPreinstalledApp());
{
SCOPED_TRACE(
"Expect same manifest fields from basic.html web app, no overwrites.");
ExpectInitialManifestFieldsFromBasicWebApp(icon_manager(), web_app,
start_url, scope);
}
}
class PreinstalledWebAppManagerWithCloudGamingBrowserTest
: public PreinstalledWebAppManagerBrowserTest {
public:
PreinstalledWebAppManagerWithCloudGamingBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(
chromeos::features::kCloudGamingDevice);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(PreinstalledWebAppManagerWithCloudGamingBrowserTest,
GateOnCloudGamingFeature) {
ASSERT_TRUE(embedded_test_server()->Start());
constexpr char kAppConfigTemplate[] =
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"feature_name": "$2"
})";
std::string app_config = base::ReplaceStringPlaceholders(
kAppConfigTemplate,
{GetAppUrl().spec(), chromeos::features::kCloudGamingDevice.name},
nullptr);
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), app_config),
webapps::InstallResultCode::kSuccessNewInstall);
}
#endif // BUILDFLAG(IS_CHROMEOS)
class PreinstalledWebAppManagerPreferredAppForSupportedLinksBrowserTest
: public PreinstalledWebAppManagerBrowserTest,
public ::testing::WithParamInterface<
/*is_preferred_app_for_supported_links=*/bool> {
public:
bool IsPreferredAppForSupportedLinks() const { return GetParam(); }
void RemoveSupportedLinksPreference(const webapps::AppId& app_id) {
apps_util::RemoveSupportedLinksPreferenceAndWait(profile(), app_id);
}
void WaitForSupportedLinksPreference(const webapps::AppId& app_id,
bool is_preferred_app) {
apps_util::PreferredAppUpdateWaiter(
apps::AppServiceProxyFactory::GetForProfile(profile())
->PreferredAppsList(),
app_id, is_preferred_app)
.Wait();
}
};
INSTANTIATE_TEST_SUITE_P(
All,
PreinstalledWebAppManagerPreferredAppForSupportedLinksBrowserTest,
/*is_preferred_app_for_supported_links=*/::testing::Bool());
IN_PROC_BROWSER_TEST_P(
PreinstalledWebAppManagerPreferredAppForSupportedLinksBrowserTest,
MaybeSetPreferredAppForSupportedLinks) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"is_preferred_app_for_supported_links": $2,
"launch_container": "window",
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec(), base::ToString(IsPreferredAppForSupportedLinks())},
nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, GetAppUrl());
// Install the app for the first time.
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
// Verify that the app is the preferred app if requested in the manifest.
WaitForSupportedLinksPreference(app_id, IsPreferredAppForSupportedLinks());
// Clear the preferred app.
RemoveSupportedLinksPreference(app_id);
// Reinstall the app.
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessAlreadyInstalled);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
// Verify that the app is *not* the preferred app after re-installation as the
// user may have already updated their preference.
WaitForSupportedLinksPreference(app_id, /*is_preferred_app=*/false);
}
#if !BUILDFLAG(IS_CHROMEOS)
// State denoting whether preinstalled apps are capturing links by default or
// not based on the `kPreinstalledBrowserTabWebAppsCaptureOnDefault` flag.
enum class PreinstalledAppCaptureState {
kForcedOn,
kForcedOff,
};
// State denoting whether the safety flag to prevent preinstalled apps from
// capturing is enabled or not.
enum class PreinstalledAppSafetyFlagStatus {
kSwitchedOn,
kSwitchedOff,
};
class PreinstalledWebAppNavigationCapturing
: public PreinstalledWebAppManagerBrowserTest,
public testing::WithParamInterface<
std::tuple<PreinstalledAppCaptureState,
PreinstalledAppSafetyFlagStatus,
apps::test::LinkCapturingFeatureVersion>> {
public:
PreinstalledWebAppNavigationCapturing() {
std::vector<base::test::FeatureRefAndParams> enabled_features =
apps::test::GetFeaturesToEnableLinkCapturingUX(
std::get<apps::test::LinkCapturingFeatureVersion>(GetParam()));
std::vector<base::test::FeatureRef> disabled_features;
// Setup whether preinstalled apps should capture links by default.
if (ArePreinstalledAppsCapturingByDefault()) {
enabled_features.emplace_back(
kPreinstalledBrowserTabWebAppsCaptureOnDefault,
base::FieldTrialParams());
} else {
disabled_features.emplace_back(
kPreinstalledBrowserTabWebAppsCaptureOnDefault);
}
// Setup whether the safety flag to prevent preinstalled apps from capturing
// links has been set.
if (ShouldForceStopPreinstalledAppsCapture()) {
enabled_features.emplace_back(
kPreinstalledBrowserTabWebAppsForcedDefaultCaptureOff,
base::FieldTrialParams());
} else {
disabled_features.emplace_back(
kPreinstalledBrowserTabWebAppsForcedDefaultCaptureOff);
}
nav_capturing_on_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
}
bool ArePreinstalledAppsCapturingByDefault() {
return std::get<PreinstalledAppCaptureState>(GetParam()) ==
PreinstalledAppCaptureState::kForcedOn;
}
bool ShouldForceStopPreinstalledAppsCapture() {
return std::get<PreinstalledAppSafetyFlagStatus>(GetParam()) ==
PreinstalledAppSafetyFlagStatus::kSwitchedOn;
}
bool ShouldCaptureLinksByDefault() {
return std::get<apps::test::LinkCapturingFeatureVersion>(GetParam()) ==
apps::test::LinkCapturingFeatureVersion::kV2DefaultOn;
}
// Capture links for preinstalled apps iff:
// 1. The safety flag `kPreinstalledBrowserTabWebAppsForcedDefaultCaptureOff`
// is not set.
// 2. If either the whole reimplementation version is set to capture links by
// default, or in the absence of that,
// `kPreinstalledBrowserTabWebAppsCaptureOnDefault` is set only for
// preinstalled apps.
bool ShouldCaptureLinks() {
return !ShouldForceStopPreinstalledAppsCapture() &&
(ArePreinstalledAppsCapturingByDefault() ||
ShouldCaptureLinksByDefault());
}
private:
base::test::ScopedFeatureList nav_capturing_on_;
};
IN_PROC_BROWSER_TEST_P(PreinstalledWebAppNavigationCapturing,
PreinstalledAppsCaptureLinks) {
base::AutoReset<bool> bypass_offline_manifest_requirement =
PreinstalledWebAppManager::BypassOfflineManifestRequirementForTesting();
ASSERT_TRUE(embedded_test_server()->Start());
const auto manifest = base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"is_preferred_app_for_supported_links": false,
"launch_container": "tab",
"user_type": ["unmanaged"]
})",
{GetAppUrl().spec()}, nullptr);
webapps::AppId app_id =
GenerateAppId(/*manifest_id_path=*/std::nullopt, GetAppUrl());
// Install the app for the first time.
EXPECT_EQ(SyncPreinstalledAppConfig(GetAppUrl(), manifest),
webapps::InstallResultCode::kSuccessNewInstall);
EXPECT_EQ(registrar().GetInstallState(app_id),
#if BUILDFLAG(IS_CHROMEOS)
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION
#else
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION
#endif
);
#if !BUILDFLAG(IS_CHROMEOS)
EXPECT_EQ(ShouldCaptureLinks(), registrar().CapturesLinksInScope(app_id));
#endif
}
INSTANTIATE_TEST_SUITE_P(
,
PreinstalledWebAppNavigationCapturing,
testing::Combine(
testing::Values(PreinstalledAppCaptureState::kForcedOn,
PreinstalledAppCaptureState::kForcedOff),
testing::Values(PreinstalledAppSafetyFlagStatus::kSwitchedOn,
PreinstalledAppSafetyFlagStatus::kSwitchedOff),
testing::Values(apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
apps::test::LinkCapturingFeatureVersion::kV2DefaultOn)),
[](const auto& param_info) {
std::string test_name;
test_name.append(apps::test::ToString(
std::get<apps::test::LinkCapturingFeatureVersion>(param_info.param)));
test_name.append("_");
switch (std::get<PreinstalledAppCaptureState>(param_info.param)) {
case PreinstalledAppCaptureState::kForcedOn:
test_name.append("PreinstalledCaptureOn");
break;
case PreinstalledAppCaptureState::kForcedOff:
test_name.append("PreinstalledCaptureOff");
break;
}
test_name.append("_");
switch (std::get<PreinstalledAppSafetyFlagStatus>(param_info.param)) {
case PreinstalledAppSafetyFlagStatus::kSwitchedOn:
test_name.append("SafetyFlagSwitchedOn");
break;
case PreinstalledAppSafetyFlagStatus::kSwitchedOff:
test_name.append("SafetyFlagSwitchedOff");
break;
}
return test_name;
});
#endif
} // namespace web_app