| // 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¶m"); |
| 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¶m" |
| })"; |
| 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 |