| // Copyright 2018 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/web_app_registrar.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.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 "build/chromeos_buildflags.h" |
| #include "chrome/browser/apps/link_capturing/link_capturing_feature_test_support.h" |
| #include "chrome/browser/web_applications/commands/run_on_os_login_command.h" |
| #include "chrome/browser/web_applications/commands/web_app_uninstall_command.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.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/proto/web_app_proto_package.pb.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_database_factory.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_provider.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_test.h" |
| #include "chrome/browser/web_applications/test/web_app_test_utils.h" |
| #include "chrome/browser/web_applications/web_app.h" |
| #include "chrome/browser/web_applications/web_app_command_manager.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_id_constants.h" |
| #include "chrome/browser/web_applications/web_app_install_finalizer.h" |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/sync/test/mock_model_type_change_processor.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/storage_partition_config.h" |
| #include "content/public/common/content_features.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/constants/ash_features.h" |
| #include "chrome/browser/ash/crosapi/browser_util.h" |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/web_applications/test/with_crosapi_param.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #include "components/user_manager/user_names.h" |
| |
| using web_app::test::CrosapiParam; |
| using web_app::test::WithCrosapiParam; |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| #include "chromeos/crosapi/mojom/crosapi.mojom.h" |
| #include "chromeos/lacros/lacros_service.h" |
| #include "chromeos/startup/browser_init_params.h" |
| #endif |
| |
| namespace web_app { |
| |
| namespace { |
| |
| Registry CreateRegistryForTesting(const std::string& base_url, int num_apps) { |
| Registry registry; |
| |
| for (int i = 0; i < num_apps; ++i) { |
| const auto url = base_url + base::NumberToString(i); |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, GURL(url)); |
| |
| auto web_app = std::make_unique<WebApp>(app_id); |
| web_app->AddSource(WebAppManagement::kSync); |
| web_app->SetStartUrl(GURL(url)); |
| web_app->SetName("Name" + base::NumberToString(i)); |
| web_app->SetDisplayMode(DisplayMode::kBrowser); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| web_app->SetIsLocallyInstalled(true); |
| |
| registry.emplace(app_id, std::move(web_app)); |
| } |
| |
| return registry; |
| } |
| |
| int CountApps(const WebAppRegistrar::AppSet& app_set) { |
| int count = 0; |
| for (const auto& web_app : app_set) { |
| EXPECT_FALSE(web_app.is_uninstalling()); |
| ++count; |
| } |
| return count; |
| } |
| |
| } // namespace |
| |
| using ::testing::ElementsAre; |
| using ::testing::Pair; |
| |
| // TODO(dmurph): Make this test run from the default FakeWebAppProvider like all |
| // other unittests. |
| class WebAppRegistrarTest : public WebAppTest { |
| public: |
| void SetUp() override { |
| WebAppTest::SetUp(); |
| web_app::FakeWebAppProvider::Get(profile())->SetDatabaseFactory( |
| std::make_unique<FakeWebAppDatabaseFactory>()); |
| } |
| |
| void TearDown() override { |
| WebAppTest::TearDown(); |
| } |
| |
| protected: |
| void StartWebAppProvider() { |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| |
| base::flat_set<webapps::AppId> PopulateRegistry(const Registry& registry) { |
| base::flat_set<webapps::AppId> app_ids; |
| for (auto& kv : registry) { |
| app_ids.insert(kv.second->app_id()); |
| } |
| |
| database_factory().WriteRegistry(registry); |
| |
| return app_ids; |
| } |
| |
| void PopulateRegistryWithApp(std::unique_ptr<WebApp> app) { |
| Registry registry; |
| webapps::AppId app_id = app->app_id(); |
| registry[app_id] = std::move(app); |
| PopulateRegistry(std::move(registry)); |
| } |
| |
| base::flat_set<webapps::AppId> PopulateRegistryWithApps( |
| const std::string& base_url, |
| int num_apps) { |
| return PopulateRegistry(CreateRegistryForTesting(base_url, num_apps)); |
| } |
| |
| FakeWebAppDatabaseFactory& database_factory() const { |
| return static_cast<FakeWebAppDatabaseFactory&>( |
| fake_provider().GetDatabaseFactory()); |
| } |
| |
| WebAppRegistrar& registrar() const { |
| return fake_provider().registrar_unsafe(); |
| } |
| |
| WebAppSyncBridge& sync_bridge() const { |
| return fake_provider().sync_bridge_unsafe(); |
| } |
| |
| // Do not copy/paste this, and instead use normal installation commands to |
| // install an app. |
| void RegisterAppUnsafe(std::unique_ptr<WebApp> app) { |
| ScopedRegistryUpdate update = |
| fake_provider().sync_bridge_unsafe().BeginUpdate(); |
| update->CreateApp(std::move(app)); |
| } |
| |
| void Uninstall(const webapps::AppId& app_id) { |
| // There is no longer a universal uninstall, so just remove each management. |
| WebAppManagementTypes managements = |
| registrar().GetAppById(app_id)->GetSources(); |
| for (WebAppManagement::Type type : managements) { |
| base::test::TestFuture<webapps::UninstallResultCode> future; |
| fake_provider().scheduler().RemoveInstallManagementMaybeUninstall( |
| app_id, type, webapps::WebappUninstallSource::kTestCleanup, |
| future.GetCallback()); |
| EXPECT_TRUE(future.Wait()); |
| EXPECT_EQ(future.Get<webapps::UninstallResultCode>(), |
| webapps::UninstallResultCode::kSuccess); |
| } |
| } |
| |
| void UninstallViaRemoveSource( |
| const webapps::AppId& app_id, |
| web_app::WebAppManagement::Type management_type) { |
| base::test::TestFuture<webapps::UninstallResultCode> future; |
| fake_provider().scheduler().RemoveInstallManagementMaybeUninstall( |
| app_id, management_type, webapps::WebappUninstallSource::kTestCleanup, |
| future.GetCallback()); |
| EXPECT_TRUE(future.Wait()); |
| EXPECT_EQ(future.Get<webapps::UninstallResultCode>(), |
| webapps::UninstallResultCode::kSuccess); |
| } |
| |
| private: |
| std::unique_ptr<FakeWebAppDatabaseFactory> database_factory_; |
| testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_; |
| }; |
| |
| class WebAppRegistrarTest_TabStrip : public WebAppRegistrarTest { |
| public: |
| WebAppRegistrarTest_TabStrip() = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{ |
| blink::features::kDesktopPWAsTabStrip}; |
| }; |
| |
| TEST_F(WebAppRegistrarTest, EmptyRegistrar) { |
| StartWebAppProvider(); |
| EXPECT_TRUE(registrar().is_empty()); |
| EXPECT_EQ(nullptr, registrar().GetAppById(webapps::AppId())); |
| EXPECT_FALSE(registrar().GetAppById(webapps::AppId())); |
| EXPECT_EQ(std::string(), registrar().GetAppShortName(webapps::AppId())); |
| EXPECT_EQ(GURL(), registrar().GetAppStartUrl(webapps::AppId())); |
| } |
| |
| TEST_F(WebAppRegistrarTest, InitWithApps) { |
| const GURL start_url = GURL("https://example.com/path"); |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, start_url); |
| const std::string name = "Name"; |
| const std::string description = "Description"; |
| const GURL scope = GURL("https://example.com/scope"); |
| const std::optional<SkColor> theme_color = 0xAABBCCDD; |
| |
| const GURL start_url2 = GURL("https://example.com/path2"); |
| const webapps::AppId app_id2 = |
| GenerateAppId(/*manifest_id=*/std::nullopt, start_url2); |
| |
| auto web_app = std::make_unique<WebApp>(app_id); |
| auto web_app2 = std::make_unique<WebApp>(app_id2); |
| |
| web_app->AddSource(WebAppManagement::kSync); |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetName(name); |
| web_app->SetDescription(description); |
| web_app->SetStartUrl(start_url); |
| web_app->SetScope(scope); |
| web_app->SetThemeColor(theme_color); |
| |
| web_app2->AddSource(WebAppManagement::kDefault); |
| web_app2->SetDisplayMode(DisplayMode::kBrowser); |
| web_app2->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| web_app2->SetStartUrl(start_url2); |
| web_app2->SetName(name); |
| |
| Registry registry; |
| registry[app_id] = std::move(web_app); |
| registry[app_id2] = std::move(web_app2); |
| PopulateRegistry(std::move(registry)); |
| |
| StartWebAppProvider(); |
| |
| EXPECT_TRUE(registrar().IsInstalled(app_id)); |
| const WebApp* app = registrar().GetAppById(app_id); |
| |
| EXPECT_EQ(app_id, app->app_id()); |
| EXPECT_EQ(name, app->untranslated_name()); |
| EXPECT_EQ(description, app->untranslated_description()); |
| EXPECT_EQ(start_url, app->start_url()); |
| EXPECT_EQ(scope, app->scope()); |
| EXPECT_EQ(theme_color, app->theme_color()); |
| |
| EXPECT_FALSE(registrar().is_empty()); |
| |
| EXPECT_TRUE(registrar().IsInstalled(app_id2)); |
| const WebApp* app2 = registrar().GetAppById(app_id2); |
| EXPECT_EQ(app_id2, app2->app_id()); |
| EXPECT_FALSE(registrar().is_empty()); |
| EXPECT_EQ(CountApps(registrar().GetApps()), 2); |
| |
| Uninstall(app_id); |
| EXPECT_FALSE(registrar().IsInstalled(app_id)); |
| EXPECT_EQ(nullptr, registrar().GetAppById(app_id)); |
| EXPECT_FALSE(registrar().is_empty()); |
| |
| // Check that app2 is still registered. |
| app2 = registrar().GetAppById(app_id2); |
| EXPECT_TRUE(registrar().IsInstalled(app_id2)); |
| EXPECT_EQ(app_id2, app2->app_id()); |
| |
| Uninstall(app_id2); |
| EXPECT_FALSE(registrar().IsInstalled(app_id2)); |
| EXPECT_EQ(nullptr, registrar().GetAppById(app_id2)); |
| EXPECT_TRUE(registrar().is_empty()); |
| EXPECT_EQ(CountApps(registrar().GetApps()), 0); |
| } |
| |
| TEST_F(WebAppRegistrarTest, InitRegistrarAndDoForEachApp) { |
| base::flat_set<webapps::AppId> ids = PopulateRegistry( |
| CreateRegistryForTesting("https://example.com/path", 20)); |
| StartWebAppProvider(); |
| |
| for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) { |
| const size_t num_removed = ids.erase(web_app.app_id()); |
| EXPECT_EQ(1U, num_removed); |
| } |
| |
| EXPECT_TRUE(ids.empty()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, DoForEachAndUnregisterAllApps) { |
| Registry registry = CreateRegistryForTesting("https://example.com/path", 20); |
| auto ids = PopulateRegistry(std::move(registry)); |
| EXPECT_EQ(20UL, ids.size()); |
| |
| StartWebAppProvider(); |
| |
| for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) { |
| const size_t num_removed = ids.erase(web_app.app_id()); |
| EXPECT_EQ(1U, num_removed); |
| } |
| EXPECT_TRUE(ids.empty()); |
| |
| EXPECT_FALSE(registrar().is_empty()); |
| test::UninstallAllWebApps(profile()); |
| EXPECT_TRUE(registrar().is_empty()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, AppsInstalledByUserMetric) { |
| base::HistogramTester histogram_tester; |
| |
| // All of these apps are marked as 'not locally installed'. |
| PopulateRegistry(CreateRegistryForTesting("https://example.com/path", 10)); |
| StartWebAppProvider(); |
| |
| histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser", |
| /*sample=*/10, |
| /*expected_bucket_count=*/1); |
| histogram_tester.ExpectUniqueSample( |
| "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/0, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(WebAppRegistrarTest, AppsNonUserInstalledMetric) { |
| base::HistogramTester histogram_tester; |
| |
| auto web_app = std::make_unique<WebApp>("app_id"); |
| web_app->AddSource(WebAppManagement::kPolicy); |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetName("name"); |
| web_app->SetStartUrl(GURL("https://example.com/path")); |
| PopulateRegistryWithApp(std::move(web_app)); |
| StartWebAppProvider(); |
| |
| histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser", |
| /*sample=*/0, |
| /*expected_bucket_count=*/1); |
| histogram_tester.ExpectUniqueSample( |
| "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/0, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(WebAppRegistrarTest, AppsNotLocallyInstalledMetric) { |
| base::HistogramTester histogram_tester; |
| |
| auto web_app = std::make_unique<WebApp>("app_id"); |
| web_app->AddSource(WebAppManagement::kSync); |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetName("name"); |
| web_app->SetStartUrl(GURL("https://example.com/path")); |
| PopulateRegistryWithApp(std::move(web_app)); |
| StartWebAppProvider(); |
| |
| histogram_tester.ExpectUniqueSample("WebApp.InstalledCount.ByUser", |
| /*sample=*/0, |
| /*expected_bucket_count=*/1); |
| histogram_tester.ExpectUniqueSample( |
| "WebApp.InstalledCount.ByUserNotLocallyInstalled", /*sample=*/1, |
| /*expected_bucket_count=*/1); |
| } |
| |
| TEST_F(WebAppRegistrarTest, GetApps) { |
| base::flat_set<webapps::AppId> ids = |
| PopulateRegistryWithApps("https://example.com/path", 10); |
| StartWebAppProvider(); |
| |
| int not_in_sync_install_count = 0; |
| for (const WebApp& web_app : registrar().GetApps()) { |
| ++not_in_sync_install_count; |
| EXPECT_TRUE(base::Contains(ids, web_app.app_id())); |
| } |
| EXPECT_EQ(10, not_in_sync_install_count); |
| |
| auto web_app_in_sync1 = test::CreateWebApp(GURL("https://example.org/sync1")); |
| web_app_in_sync1->SetIsFromSyncAndPendingInstallation(true); |
| const webapps::AppId web_app_id_in_sync1 = web_app_in_sync1->app_id(); |
| RegisterAppUnsafe(std::move(web_app_in_sync1)); |
| |
| auto web_app_in_sync2 = test::CreateWebApp(GURL("https://example.org/sync2")); |
| web_app_in_sync2->SetIsFromSyncAndPendingInstallation(true); |
| const webapps::AppId web_app_id_in_sync2 = web_app_in_sync2->app_id(); |
| RegisterAppUnsafe(std::move(web_app_in_sync2)); |
| |
| int all_apps_count = 0; |
| for ([[maybe_unused]] const WebApp& web_app : |
| registrar().GetAppsIncludingStubs()) { |
| ++all_apps_count; |
| } |
| EXPECT_EQ(12, all_apps_count); |
| |
| for (const WebApp& web_app : registrar().GetApps()) { |
| EXPECT_NE(web_app_id_in_sync1, web_app.app_id()); |
| EXPECT_NE(web_app_id_in_sync2, web_app.app_id()); |
| |
| const size_t num_removed = ids.erase(web_app.app_id()); |
| EXPECT_EQ(1U, num_removed); |
| } |
| EXPECT_TRUE(ids.empty()); |
| |
| Uninstall(web_app_id_in_sync1); |
| Uninstall(web_app_id_in_sync2); |
| |
| not_in_sync_install_count = 0; |
| for ([[maybe_unused]] const WebApp& web_app : registrar().GetApps()) { |
| ++not_in_sync_install_count; |
| } |
| EXPECT_EQ(10, not_in_sync_install_count); |
| } |
| |
| TEST_F(WebAppRegistrarTest, GetAppDataFields) { |
| |
| const GURL start_url = GURL("https://example.com/path"); |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, start_url); |
| const std::string name = "Name"; |
| const std::string description = "Description"; |
| const std::optional<SkColor> theme_color = 0xAABBCCDD; |
| const auto display_mode = DisplayMode::kMinimalUi; |
| const auto user_display_mode = mojom::UserDisplayMode::kStandalone; |
| std::vector<DisplayMode> display_mode_override; |
| |
| auto web_app = std::make_unique<WebApp>(app_id); |
| |
| display_mode_override.push_back(DisplayMode::kMinimalUi); |
| display_mode_override.push_back(DisplayMode::kStandalone); |
| |
| web_app->AddSource(WebAppManagement::kSync); |
| web_app->SetName(name); |
| web_app->SetDescription(description); |
| web_app->SetThemeColor(theme_color); |
| web_app->SetStartUrl(start_url); |
| web_app->SetDisplayMode(display_mode); |
| web_app->SetUserDisplayMode(user_display_mode); |
| web_app->SetDisplayModeOverride(display_mode_override); |
| web_app->SetIsLocallyInstalled(/*is_locally_installed*/ false); |
| |
| PopulateRegistryWithApp(std::move(web_app)); |
| StartWebAppProvider(); |
| |
| EXPECT_EQ(name, registrar().GetAppShortName(app_id)); |
| EXPECT_EQ(description, registrar().GetAppDescription(app_id)); |
| EXPECT_EQ(theme_color, registrar().GetAppThemeColor(app_id)); |
| EXPECT_EQ(start_url, registrar().GetAppStartUrl(app_id)); |
| EXPECT_EQ(mojom::UserDisplayMode::kStandalone, |
| registrar().GetAppUserDisplayMode(app_id)); |
| |
| { |
| std::vector<DisplayMode> app_display_mode_override = |
| registrar().GetAppDisplayModeOverride(app_id); |
| ASSERT_EQ(2u, app_display_mode_override.size()); |
| EXPECT_EQ(DisplayMode::kMinimalUi, app_display_mode_override[0]); |
| EXPECT_EQ(DisplayMode::kStandalone, app_display_mode_override[1]); |
| } |
| |
| { |
| EXPECT_FALSE(registrar().IsLocallyInstalled(app_id)); |
| EXPECT_FALSE(registrar().IsActivelyInstalled(app_id)); |
| |
| EXPECT_FALSE(registrar().IsLocallyInstalled("unknown")); |
| { |
| ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); |
| update->UpdateApp(app_id)->SetIsLocallyInstalled( |
| /*is_locally_installed*/ true); |
| } |
| EXPECT_TRUE(registrar().IsLocallyInstalled(app_id)); |
| EXPECT_TRUE(registrar().IsActivelyInstalled(app_id)); |
| } |
| |
| { |
| EXPECT_FALSE(registrar().GetAppUserDisplayMode("unknown").has_value()); |
| { |
| ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); |
| update->UpdateApp(app_id)->SetUserDisplayMode( |
| mojom::UserDisplayMode::kBrowser); |
| } |
| EXPECT_EQ(mojom::UserDisplayMode::kBrowser, |
| registrar().GetAppUserDisplayMode(app_id)); |
| |
| fake_provider().sync_bridge_unsafe().SetAppUserDisplayModeForTesting( |
| app_id, mojom::UserDisplayMode::kStandalone); |
| EXPECT_EQ(mojom::UserDisplayMode::kStandalone, |
| registrar().GetAppUserDisplayMode(app_id)); |
| EXPECT_EQ(DisplayMode::kMinimalUi, registrar().GetAppDisplayMode(app_id)); |
| |
| ASSERT_EQ(2u, registrar().GetAppDisplayModeOverride(app_id).size()); |
| EXPECT_EQ(DisplayMode::kMinimalUi, |
| registrar().GetAppDisplayModeOverride(app_id)[0]); |
| EXPECT_EQ(DisplayMode::kStandalone, |
| registrar().GetAppDisplayModeOverride(app_id)[1]); |
| } |
| } |
| |
| TEST_F(WebAppRegistrarTest, CanFindAppsInScope) { |
| StartWebAppProvider(); |
| |
| const GURL origin_scope("https://example.com/"); |
| |
| const GURL app1_scope("https://example.com/app"); |
| const GURL app2_scope("https://example.com/app-two"); |
| const GURL app3_scope("https://not-example.com/app"); |
| |
| const webapps::AppId app1_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app1_scope); |
| const webapps::AppId app2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope); |
| const webapps::AppId app3_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope); |
| |
| std::vector<webapps::AppId> in_scope = |
| registrar().FindAppsInScope(origin_scope); |
| EXPECT_EQ(0u, in_scope.size()); |
| EXPECT_FALSE(registrar().DoesScopeContainAnyApp(origin_scope)); |
| EXPECT_FALSE(registrar().DoesScopeContainAnyApp(app3_scope)); |
| |
| auto app1 = test::CreateWebApp(app1_scope); |
| app1->SetScope(app1_scope); |
| RegisterAppUnsafe(std::move(app1)); |
| |
| in_scope = registrar().FindAppsInScope(origin_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(origin_scope)); |
| EXPECT_FALSE(registrar().DoesScopeContainAnyApp(app3_scope)); |
| |
| in_scope = registrar().FindAppsInScope(app1_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(app1_scope)); |
| |
| auto app2 = test::CreateWebApp(app2_scope); |
| app2->SetScope(app2_scope); |
| RegisterAppUnsafe(std::move(app2)); |
| |
| in_scope = registrar().FindAppsInScope(origin_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(origin_scope)); |
| EXPECT_FALSE(registrar().DoesScopeContainAnyApp(app3_scope)); |
| |
| in_scope = registrar().FindAppsInScope(app1_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(app1_scope)); |
| |
| in_scope = registrar().FindAppsInScope(app2_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app2_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(app2_scope)); |
| |
| auto app3 = test::CreateWebApp(app3_scope); |
| app3->SetScope(app3_scope); |
| RegisterAppUnsafe(std::move(app3)); |
| |
| in_scope = registrar().FindAppsInScope(origin_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(origin_scope)); |
| |
| in_scope = registrar().FindAppsInScope(app3_scope); |
| EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app3_id)); |
| EXPECT_TRUE(registrar().DoesScopeContainAnyApp(app3_scope)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CanFindAppWithUrlInScope) { |
| StartWebAppProvider(); |
| |
| const GURL origin_scope("https://example.com/"); |
| |
| const GURL app1_scope("https://example.com/app"); |
| const GURL app2_scope("https://example.com/app-two"); |
| const GURL app3_scope("https://not-example.com/app"); |
| const GURL app4_scope("https://app-four.com/"); |
| |
| const webapps::AppId app1_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app1_scope); |
| const webapps::AppId app2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope); |
| const webapps::AppId app3_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope); |
| const webapps::AppId app4_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope); |
| |
| auto app1 = test::CreateWebApp(app1_scope); |
| app1->SetScope(app1_scope); |
| RegisterAppUnsafe(std::move(app1)); |
| |
| std::optional<webapps::AppId> app2_match = |
| registrar().FindAppWithUrlInScope(app2_scope); |
| DCHECK(app2_match); |
| EXPECT_EQ(*app2_match, app1_id); |
| |
| std::optional<webapps::AppId> app3_match = |
| registrar().FindAppWithUrlInScope(app3_scope); |
| EXPECT_FALSE(app3_match); |
| |
| std::optional<webapps::AppId> app4_match = |
| registrar().FindAppWithUrlInScope(app4_scope); |
| EXPECT_FALSE(app4_match); |
| |
| auto app2 = test::CreateWebApp(app2_scope); |
| app2->SetScope(app2_scope); |
| RegisterAppUnsafe(std::move(app2)); |
| |
| auto app3 = test::CreateWebApp(app3_scope); |
| app3->SetScope(app3_scope); |
| RegisterAppUnsafe(std::move(app3)); |
| |
| auto app4 = test::CreateWebApp(app4_scope); |
| app4->SetScope(app4_scope); |
| app4->SetIsUninstalling(true); |
| RegisterAppUnsafe(std::move(app4)); |
| |
| std::optional<webapps::AppId> origin_match = |
| registrar().FindAppWithUrlInScope(origin_scope); |
| EXPECT_FALSE(origin_match); |
| |
| std::optional<webapps::AppId> app1_match = |
| registrar().FindAppWithUrlInScope(app1_scope); |
| DCHECK(app1_match); |
| EXPECT_EQ(*app1_match, app1_id); |
| |
| app2_match = registrar().FindAppWithUrlInScope(app2_scope); |
| DCHECK(app2_match); |
| EXPECT_EQ(*app2_match, app2_id); |
| |
| app3_match = registrar().FindAppWithUrlInScope(app3_scope); |
| DCHECK(app3_match); |
| EXPECT_EQ(*app3_match, app3_id); |
| |
| // Apps in the process of uninstalling are ignored. |
| app4_match = registrar().FindAppWithUrlInScope(app4_scope); |
| EXPECT_FALSE(app4_match); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CanFindShortcutWithUrlInScope) { |
| // Behaviour when Shortstand enabled is covered in |
| // WebAppRegistrarTest_Shortstand.CannotFindShortcutWithUrlInScope. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndDisableFeature( |
| chromeos::features::kCrosShortstand); |
| #elif BUILDFLAG(IS_CHROMEOS_LACROS) |
| crosapi::mojom::BrowserInitParamsPtr init_params = |
| chromeos::BrowserInitParams::GetForTests()->Clone(); |
| init_params->is_cros_shortstand_enabled = false; |
| chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params)); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| StartWebAppProvider(); |
| |
| const GURL app1_page("https://example.com/app/page"); |
| const GURL app2_page("https://example.com/app-two/page"); |
| const GURL app3_page("https://not-example.com/app/page"); |
| |
| const GURL app1_launch("https://example.com/app/launch"); |
| const GURL app2_launch("https://example.com/app-two/launch"); |
| const GURL app3_launch("https://not-example.com/app/launch"); |
| |
| const webapps::AppId app1_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app1_launch); |
| const webapps::AppId app2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app2_launch); |
| const webapps::AppId app3_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_launch); |
| |
| // Implicit scope "https://example.com/app/" |
| auto app1 = test::CreateWebApp(app1_launch); |
| RegisterAppUnsafe(std::move(app1)); |
| |
| std::optional<webapps::AppId> app2_match = |
| registrar().FindAppWithUrlInScope(app2_page); |
| EXPECT_FALSE(app2_match); |
| |
| std::optional<webapps::AppId> app3_match = |
| registrar().FindAppWithUrlInScope(app3_page); |
| EXPECT_FALSE(app3_match); |
| |
| auto app2 = test::CreateWebApp(app2_launch); |
| RegisterAppUnsafe(std::move(app2)); |
| |
| auto app3 = test::CreateWebApp(app3_launch); |
| RegisterAppUnsafe(std::move(app3)); |
| |
| std::optional<webapps::AppId> app1_match = |
| registrar().FindAppWithUrlInScope(app1_page); |
| DCHECK(app1_match); |
| EXPECT_EQ(app1_match, std::optional<webapps::AppId>(app1_id)); |
| |
| app2_match = registrar().FindAppWithUrlInScope(app2_page); |
| DCHECK(app2_match); |
| EXPECT_EQ(app2_match, std::optional<webapps::AppId>(app2_id)); |
| |
| app3_match = registrar().FindAppWithUrlInScope(app3_page); |
| DCHECK(app3_match); |
| EXPECT_EQ(app3_match, std::optional<webapps::AppId>(app3_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, FindPwaOverShortcut) { |
| StartWebAppProvider(); |
| |
| const GURL app1_launch("https://example.com/app/specific/launch1"); |
| |
| const GURL app2_scope("https://example.com/app"); |
| const GURL app2_page("https://example.com/app/specific/page2"); |
| const webapps::AppId app2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope); |
| |
| const GURL app3_launch("https://example.com/app/specific/launch3"); |
| |
| auto app1 = test::CreateWebApp(app1_launch); |
| RegisterAppUnsafe(std::move(app1)); |
| |
| auto app2 = test::CreateWebApp(app2_scope); |
| app2->SetScope(app2_scope); |
| RegisterAppUnsafe(std::move(app2)); |
| |
| auto app3 = test::CreateWebApp(app3_launch); |
| RegisterAppUnsafe(std::move(app3)); |
| |
| std::optional<webapps::AppId> app2_match = |
| registrar().FindAppWithUrlInScope(app2_page); |
| DCHECK(app2_match); |
| EXPECT_EQ(app2_match, std::optional<webapps::AppId>(app2_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, BeginAndCommitUpdate) { |
| base::flat_set<webapps::AppId> ids = |
| PopulateRegistryWithApps("https://example.com/path", 10); |
| StartWebAppProvider(); |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| fake_provider().sync_bridge_unsafe().BeginUpdate(future.GetCallback()); |
| |
| for (auto& app_id : ids) { |
| WebApp* app = update->UpdateApp(app_id); |
| EXPECT_TRUE(app); |
| app->SetName("New Name"); |
| } |
| |
| // Acquire each app second time to make sure update requests get merged. |
| for (auto& app_id : ids) { |
| WebApp* app = update->UpdateApp(app_id); |
| EXPECT_TRUE(app); |
| app->SetDisplayMode(DisplayMode::kStandalone); |
| } |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| // Make sure that all app ids were written to the database. |
| auto registry_written = database_factory().ReadRegistry(); |
| EXPECT_EQ(ids.size(), registry_written.size()); |
| |
| for (auto& kv : registry_written) { |
| EXPECT_EQ("New Name", kv.second->untranslated_name()); |
| ids.erase(kv.second->app_id()); |
| } |
| |
| EXPECT_TRUE(ids.empty()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CommitEmptyUpdate) { |
| base::flat_set<webapps::AppId> ids = |
| PopulateRegistryWithApps("https://example.com/path", 10); |
| StartWebAppProvider(); |
| const auto initial_registry = database_factory().ReadRegistry(); |
| |
| { |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| auto registry = database_factory().ReadRegistry(); |
| EXPECT_TRUE(IsRegistryEqual(initial_registry, registry)); |
| } |
| |
| { |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| WebApp* app = update->UpdateApp("unknown"); |
| EXPECT_FALSE(app); |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| auto registry = database_factory().ReadRegistry(); |
| EXPECT_TRUE(IsRegistryEqual(initial_registry, registry)); |
| } |
| } |
| |
| TEST_F(WebAppRegistrarTest, ScopedRegistryUpdate) { |
| base::flat_set<webapps::AppId> ids = |
| PopulateRegistryWithApps("https://example.com/path", 10); |
| StartWebAppProvider(); |
| const auto initial_registry = database_factory().ReadRegistry(); |
| |
| // Test empty update first. |
| { ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); } |
| EXPECT_TRUE( |
| IsRegistryEqual(initial_registry, database_factory().ReadRegistry())); |
| |
| { |
| ScopedRegistryUpdate update = sync_bridge().BeginUpdate(); |
| |
| for (auto& app_id : ids) { |
| WebApp* app = update->UpdateApp(app_id); |
| EXPECT_TRUE(app); |
| app->SetDescription("New Description"); |
| } |
| } |
| |
| // Make sure that all app ids were written to the database. |
| auto updated_registry = database_factory().ReadRegistry(); |
| EXPECT_EQ(ids.size(), updated_registry.size()); |
| |
| for (auto& kv : updated_registry) { |
| EXPECT_EQ(kv.second->untranslated_description(), "New Description"); |
| ids.erase(kv.second->app_id()); |
| } |
| |
| EXPECT_TRUE(ids.empty()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CopyOnWrite) { |
| StartWebAppProvider(); |
| |
| const GURL start_url("https://example.com"); |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, start_url); |
| const WebApp* app = nullptr; |
| { |
| auto new_app = test::CreateWebApp(start_url); |
| app = new_app.get(); |
| RegisterAppUnsafe(std::move(new_app)); |
| } |
| |
| base::test::TestFuture<bool> future; |
| { |
| ScopedRegistryUpdate update = |
| sync_bridge().BeginUpdate(future.GetCallback()); |
| |
| WebApp* app_copy = update->UpdateApp(app_id); |
| EXPECT_TRUE(app_copy); |
| EXPECT_NE(app_copy, app); |
| |
| app_copy->SetName("New Name"); |
| EXPECT_EQ(app_copy->untranslated_name(), "New Name"); |
| EXPECT_EQ(app->untranslated_name(), "Name"); |
| |
| app_copy->AddSource(WebAppManagement::kPolicy); |
| app_copy->RemoveSource(WebAppManagement::kSync); |
| |
| EXPECT_FALSE(app_copy->IsSynced()); |
| EXPECT_TRUE(app_copy->HasAnySources()); |
| |
| EXPECT_TRUE(app->IsSynced()); |
| EXPECT_TRUE(app->HasAnySources()); |
| } |
| EXPECT_TRUE(future.Take()); |
| |
| // Pointer value stays the same. |
| EXPECT_EQ(app, registrar().GetAppById(app_id)); |
| |
| EXPECT_EQ(app->untranslated_name(), "New Name"); |
| EXPECT_FALSE(app->IsSynced()); |
| EXPECT_TRUE(app->HasAnySources()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CountUserInstalledApps) { |
| StartWebAppProvider(); |
| |
| const std::string base_url{"https://example.com/path"}; |
| |
| for (WebAppManagement::Type type : WebAppManagementTypes::All()) { |
| int i = static_cast<int>(type); |
| auto web_app = |
| test::CreateWebApp(GURL(base_url + base::NumberToString(i)), type); |
| RegisterAppUnsafe(std::move(web_app)); |
| } |
| |
| // User-installed apps have one of the following types: |
| // - `WebAppManagement::kSync` |
| // - `WebAppManagement::kWebAppStore` |
| // - `WebAppManagement::kOneDriveIntegration` |
| // - `WebAppManagement::kIwaUserInstalled` |
| EXPECT_EQ(4, registrar().CountUserInstalledApps()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, CountUserInstalledAppsDiy) { |
| StartWebAppProvider(); |
| |
| int i = 1; |
| const std::string base_url{"https://example.com/path"}; |
| |
| // Sync installed non-DIY. |
| auto web_app1 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)), |
| WebAppManagement::kSync); |
| web_app1->SetIsDiyApp(/*is_diy_app=*/false); |
| RegisterAppUnsafe(std::move(web_app1)); |
| ++i; |
| |
| // Sync installed DIY. |
| auto web_app2 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)), |
| WebAppManagement::kSync); |
| web_app2->SetIsDiyApp(/*is_diy_app=*/true); |
| RegisterAppUnsafe(std::move(web_app2)); |
| ++i; |
| |
| // Policy installed DIY (not counted as part of user installed) |
| auto web_app3 = test::CreateWebApp(GURL(base_url + base::NumberToString(i)), |
| WebAppManagement::kPolicy); |
| web_app3->SetIsDiyApp(/*is_diy_app=*/true); |
| RegisterAppUnsafe(std::move(web_app3)); |
| ++i; |
| |
| EXPECT_EQ(2, registrar().CountUserInstalledApps()); |
| EXPECT_EQ(1, registrar().CountUserInstalledDiyApps()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, GetAllIsolatedWebAppStoragePartitionConfigs) { |
| base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps); |
| StartWebAppProvider(); |
| |
| constexpr char kIwaHostname[] = |
| "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic"; |
| constexpr char kExpectedIwaStoragePartitionDomain[] = |
| "i1kr80qqyjuuVC4UFPN7ovBngVoA2HbXGtTXtmQn6/H4="; |
| GURL start_url(base::StrCat({chrome::kIsolatedAppScheme, |
| url::kStandardSchemeSeparator, kIwaHostname})); |
| auto isolated_web_app = test::CreateWebApp(start_url); |
| const webapps::AppId app_id = isolated_web_app->app_id(); |
| |
| isolated_web_app->SetScope(isolated_web_app->start_url()); |
| isolated_web_app->SetIsolationData(WebApp::IsolationData( |
| IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false}, |
| base::Version("1.0.0"))); |
| RegisterAppUnsafe(std::move(isolated_web_app)); |
| |
| std::vector<content::StoragePartitionConfig> storage_partition_configs = |
| registrar().GetIsolatedWebAppStoragePartitionConfigs(app_id); |
| |
| auto expected_config = content::StoragePartitionConfig::Create( |
| profile(), kExpectedIwaStoragePartitionDomain, |
| /*partition_name=*/"", /*in_memory=*/false); |
| ASSERT_EQ(1UL, storage_partition_configs.size()); |
| EXPECT_EQ(expected_config, storage_partition_configs[0]); |
| } |
| |
| TEST_F( |
| WebAppRegistrarTest, |
| GetAllIsolatedWebAppStoragePartitionConfigsEmptyWhenNotLocallyInstalled) { |
| base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps); |
| StartWebAppProvider(); |
| |
| GURL start_url( |
| "isolated-app://" |
| "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic"); |
| auto isolated_web_app = test::CreateWebApp(start_url); |
| const webapps::AppId app_id = isolated_web_app->app_id(); |
| |
| isolated_web_app->SetScope(isolated_web_app->start_url()); |
| isolated_web_app->SetIsolationData(WebApp::IsolationData( |
| IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false}, |
| base::Version("1.0.0"))); |
| isolated_web_app->SetIsLocallyInstalled(false); |
| RegisterAppUnsafe(std::move(isolated_web_app)); |
| |
| std::vector<content::StoragePartitionConfig> storage_partition_configs = |
| registrar().GetIsolatedWebAppStoragePartitionConfigs(app_id); |
| |
| EXPECT_TRUE(storage_partition_configs.empty()); |
| } |
| |
| TEST_F(WebAppRegistrarTest, SaveAndGetInMemoryControlledFramePartitionConfig) { |
| base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps); |
| StartWebAppProvider(); |
| |
| constexpr char kIwaHostname[] = |
| "berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic"; |
| constexpr char kExpectedIwaStoragePartitionDomain[] = |
| "i1kr80qqyjuuVC4UFPN7ovBngVoA2HbXGtTXtmQn6/H4="; |
| GURL start_url(base::StrCat({chrome::kIsolatedAppScheme, |
| url::kStandardSchemeSeparator, kIwaHostname})); |
| auto isolated_web_app = test::CreateWebApp(start_url); |
| const webapps::AppId app_id = isolated_web_app->app_id(); |
| auto url_info = IsolatedWebAppUrlInfo::Create(start_url); |
| ASSERT_TRUE(url_info.has_value()); |
| |
| isolated_web_app->SetScope(isolated_web_app->start_url()); |
| isolated_web_app->SetIsolationData(WebApp::IsolationData( |
| IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false}, |
| base::Version("1.0.0"))); |
| RegisterAppUnsafe(std::move(isolated_web_app)); |
| |
| auto output_config = |
| registrar().SaveAndGetInMemoryControlledFramePartitionConfig( |
| url_info.value(), "partition_1"); |
| |
| auto expected_config_cf_1 = content::StoragePartitionConfig::Create( |
| profile(), kExpectedIwaStoragePartitionDomain, |
| /*partition_name=*/"partition_1", /*in_memory=*/true); |
| |
| EXPECT_EQ(expected_config_cf_1, output_config); |
| } |
| |
| TEST_F(WebAppRegistrarTest, |
| AppsFromSyncAndPendingInstallationExcludedFromGetAppIds) { |
| PopulateRegistryWithApps("https://example.com/path/", 20); |
| StartWebAppProvider(); |
| |
| EXPECT_EQ(20u, registrar().GetAppIds().size()); |
| |
| std::unique_ptr<WebApp> web_app_in_sync_install = |
| test::CreateWebApp(GURL("https://example.org/")); |
| web_app_in_sync_install->SetIsFromSyncAndPendingInstallation(true); |
| |
| const webapps::AppId web_app_in_sync_install_id = |
| web_app_in_sync_install->app_id(); |
| RegisterAppUnsafe(std::move(web_app_in_sync_install)); |
| |
| // Tests that GetAppIds() excludes web app in sync install: |
| std::vector<webapps::AppId> ids = registrar().GetAppIds(); |
| EXPECT_EQ(20u, ids.size()); |
| for (const webapps::AppId& app_id : ids) { |
| EXPECT_NE(app_id, web_app_in_sync_install_id); |
| } |
| |
| // Tests that GetAppsIncludingStubs() returns a web app which is either in |
| // GetAppIds() set or it is the web app in sync install: |
| bool web_app_in_sync_install_found = false; |
| for (const WebApp& web_app : registrar().GetAppsIncludingStubs()) { |
| if (web_app.app_id() == web_app_in_sync_install_id) { |
| web_app_in_sync_install_found = true; |
| } else { |
| EXPECT_TRUE(base::Contains(ids, web_app.app_id())); |
| } |
| } |
| EXPECT_TRUE(web_app_in_sync_install_found); |
| } |
| |
| TEST_F(WebAppRegistrarTest, NotLocallyInstalledAppGetsDisplayModeBrowser) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetIsLocallyInstalled(false); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(DisplayMode::kBrowser, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| |
| base::test::TestFuture<void> future; |
| fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback()); |
| ASSERT_TRUE(future.Wait()); |
| |
| EXPECT_EQ(DisplayMode::kStandalone, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, |
| NotLocallyInstalledAppGetsDisplayModeBrowserEvenForIsolatedWebApps) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetIsLocallyInstalled(false); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(DisplayMode::kBrowser, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, |
| IsolatedWebAppsGetDisplayModeStandaloneRegardlessOfUserSettings) { |
| StartWebAppProvider(); |
| |
| std::unique_ptr<WebApp> web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| |
| // Valid manifest must have standalone display mode |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| web_app->SetIsLocallyInstalled(true); |
| web_app->SetIsolationData(WebApp::IsolationData( |
| IwaStorageProxy{url::Origin::Create(GURL("http://127.0.0.1:8080"))}, |
| base::Version("1.0.0"))); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(DisplayMode::kStandalone, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, NotLocallyInstalledAppGetsDisplayModeOverride) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| std::vector<DisplayMode> display_mode_overrides; |
| display_mode_overrides.push_back(DisplayMode::kFullscreen); |
| display_mode_overrides.push_back(DisplayMode::kMinimalUi); |
| |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetDisplayModeOverride(display_mode_overrides); |
| web_app->SetIsLocallyInstalled(false); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(DisplayMode::kBrowser, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| |
| base::test::TestFuture<void> future; |
| fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback()); |
| ASSERT_TRUE(future.Wait()); |
| |
| EXPECT_EQ(DisplayMode::kMinimalUi, |
| registrar().GetAppEffectiveDisplayMode(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, |
| CheckDisplayOverrideFromGetEffectiveDisplayModeFromManifest) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| std::vector<DisplayMode> display_mode_overrides; |
| display_mode_overrides.push_back(DisplayMode::kFullscreen); |
| display_mode_overrides.push_back(DisplayMode::kMinimalUi); |
| |
| web_app->SetDisplayMode(DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetDisplayModeOverride(display_mode_overrides); |
| web_app->SetIsLocallyInstalled(false); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(DisplayMode::kFullscreen, |
| registrar().GetEffectiveDisplayModeFromManifest(app_id)); |
| |
| base::test::TestFuture<void> future; |
| fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback()); |
| ASSERT_TRUE(future.Wait()); |
| |
| EXPECT_EQ(DisplayMode::kFullscreen, |
| registrar().GetEffectiveDisplayModeFromManifest(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, WindowControlsOverlay) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(); |
| const webapps::AppId app_id = web_app->app_id(); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(false, registrar().GetWindowControlsOverlayEnabled(app_id)); |
| |
| sync_bridge().SetAppWindowControlsOverlayEnabled(app_id, true); |
| EXPECT_EQ(true, registrar().GetWindowControlsOverlayEnabled(app_id)); |
| |
| sync_bridge().SetAppWindowControlsOverlayEnabled(app_id, false); |
| EXPECT_EQ(false, registrar().GetWindowControlsOverlayEnabled(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, IsRegisteredLaunchProtocol) { |
| StartWebAppProvider(); |
| |
| apps::ProtocolHandlerInfo protocol_handler_info1; |
| protocol_handler_info1.protocol = "web+test"; |
| protocol_handler_info1.url = GURL("http://example.com/test=%s"); |
| |
| apps::ProtocolHandlerInfo protocol_handler_info2; |
| protocol_handler_info2.protocol = "web+test2"; |
| protocol_handler_info2.url = GURL("http://example.com/test2=%s"); |
| |
| auto web_app = test::CreateWebApp(GURL("https://example.com/path")); |
| const webapps::AppId app_id = web_app->app_id(); |
| web_app->SetProtocolHandlers( |
| {protocol_handler_info1, protocol_handler_info2}); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_TRUE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test")); |
| EXPECT_TRUE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test2")); |
| EXPECT_FALSE(registrar().IsRegisteredLaunchProtocol(app_id, "web+test3")); |
| EXPECT_FALSE(registrar().IsRegisteredLaunchProtocol(app_id, "mailto")); |
| } |
| |
| TEST_F(WebAppRegistrarTest, TestIsDefaultManagementInstalled) { |
| StartWebAppProvider(); |
| |
| auto web_app1 = |
| test::CreateWebApp(GURL("https://start.com"), WebAppManagement::kDefault); |
| auto web_app2 = test::CreateWebApp(GURL("https://starter.com"), |
| WebAppManagement::kPolicy); |
| const webapps::AppId app_id1 = web_app1->app_id(); |
| const webapps::AppId app_id2 = web_app2->app_id(); |
| RegisterAppUnsafe(std::move(web_app1)); |
| RegisterAppUnsafe(std::move(web_app2)); |
| |
| // Currently default installed. |
| EXPECT_TRUE(registrar().IsInstalledByDefaultManagement(app_id1)); |
| // Currently installed by source other than installed. |
| EXPECT_FALSE(registrar().IsInstalledByDefaultManagement(app_id2)); |
| |
| // Uninstalling the previously default installed app. |
| Uninstall(app_id1); |
| EXPECT_FALSE(registrar().IsInstalledByDefaultManagement(app_id1)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, DefaultNotActivelyInstalled) { |
| std::unique_ptr<WebApp> default_app = test::CreateWebApp( |
| GURL("https://example.com/path"), WebAppManagement::kDefault); |
| default_app->SetDisplayMode(DisplayMode::kStandalone); |
| default_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| |
| const webapps::AppId app_id = default_app->app_id(); |
| const GURL external_app_url("https://example.com/path/default"); |
| |
| Registry registry; |
| registry.emplace(app_id, std::move(default_app)); |
| PopulateRegistry(registry); |
| StartWebAppProvider(); |
| |
| EXPECT_FALSE(registrar().IsActivelyInstalled(app_id)); |
| } |
| |
| // Link capturing preferences & overlapping scopes have custom behavior on CrOS. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| TEST_F(WebAppRegistrarTest, AppsOverlapIfSharesScope) { |
| base::test::ScopedFeatureList enable_link_capturing; |
| enable_link_capturing.InitWithFeaturesAndParameters( |
| apps::test::GetFeaturesToEnableLinkCapturingUX(), {}); |
| StartWebAppProvider(); |
| |
| // Initialize 2 apps, both having the same scope, and set the second |
| // app to capture links. If app1 is passed as an input, then |
| // app2 is returned as an overlapping app that matches the scope and |
| // is set by the user to handle links. |
| auto web_app1 = |
| test::CreateWebApp(GURL("https://example.com"), WebAppManagement::kSync); |
| web_app1->SetScope(GURL("https://example_scope.com")); |
| |
| auto web_app2 = test::CreateWebApp(GURL("https://example.com/def"), |
| WebAppManagement::kDefault); |
| web_app2->SetScope(GURL("https://example_scope.com")); |
| web_app2->SetLinkCapturingUserPreference( |
| proto::LinkCapturingUserPreference::CAPTURE_SUPPORTED_LINKS); |
| |
| const webapps::AppId app_id1 = web_app1->app_id(); |
| const webapps::AppId app_id2 = web_app2->app_id(); |
| RegisterAppUnsafe(std::move(web_app1)); |
| RegisterAppUnsafe(std::move(web_app2)); |
| |
| EXPECT_THAT(registrar().GetOverlappingAppsMatchingScope(app_id1), |
| ElementsAre(app_id2)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, AppsDoNotOverlapIfNestedScope) { |
| StartWebAppProvider(); |
| |
| // Initialize 2 apps, with app2 having a scope with the same origin as app1 |
| // but is nested. If app1 is passed as an input, then app2 is not returned as |
| // an overlapping app since nested scopes are not considered overlapping. |
| auto web_app1 = |
| test::CreateWebApp(GURL("https://example.com"), WebAppManagement::kSync); |
| web_app1->SetScope(GURL("https://example_scope.com")); |
| |
| auto web_app2 = test::CreateWebApp(GURL("https://example.com/def"), |
| WebAppManagement::kDefault); |
| web_app2->SetScope(GURL("https://example_scope.com/nested")); |
| web_app2->SetLinkCapturingUserPreference( |
| proto::LinkCapturingUserPreference::CAPTURE_SUPPORTED_LINKS); |
| |
| const webapps::AppId app_id1 = web_app1->app_id(); |
| const webapps::AppId app_id2 = web_app2->app_id(); |
| RegisterAppUnsafe(std::move(web_app1)); |
| RegisterAppUnsafe(std::move(web_app2)); |
| |
| EXPECT_TRUE(registrar().GetOverlappingAppsMatchingScope(app_id1).empty()); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| class WebAppRegistrarTest_ScopeExtensions : public WebAppRegistrarTest { |
| public: |
| WebAppRegistrarTest_ScopeExtensions() = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_{ |
| blink::features::kWebAppEnableScopeExtensions}; |
| }; |
| |
| TEST_F(WebAppRegistrarTest_ScopeExtensions, IsUrlInAppExtendedScope) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(GURL("https://example.com/start")); |
| webapps::AppId app_id = web_app->app_id(); |
| |
| auto extended_scope_url = GURL("https://example.app"); |
| auto extended_scope_origin = url::Origin::Create(extended_scope_url); |
| |
| // Manifest entry {"origin": "https://*.example.co"}. |
| auto extended_scope_url2 = GURL("https://example.co"); |
| auto extended_scope_origin2 = url::Origin::Create(extended_scope_url2); |
| |
| web_app->SetValidatedScopeExtensions( |
| {ScopeExtensionInfo(extended_scope_origin), |
| ScopeExtensionInfo(extended_scope_origin2, |
| /*has_origin_wildcard=*/true)}); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ( |
| registrar().GetAppExtendedScopeScore(GURL("https://test.com"), app_id), |
| 0u); |
| |
| EXPECT_GT(registrar().GetAppExtendedScopeScore( |
| GURL("https://example.com/path"), app_id), |
| 0u); |
| |
| // Scope is extended to all sub-domains of example.co with the wildcard |
| // prefix. |
| EXPECT_GT(registrar().GetAppExtendedScopeScore(GURL("https://app.example.co"), |
| app_id), |
| 0u); |
| EXPECT_GT(registrar().GetAppExtendedScopeScore( |
| GURL("https://test.app.example.co"), app_id), |
| 0u); |
| EXPECT_GT(registrar().GetAppExtendedScopeScore( |
| GURL("https://example.co/path"), app_id), |
| 0u); |
| |
| EXPECT_GT(registrar().GetAppExtendedScopeScore( |
| GURL("https://example.app/start"), app_id), |
| 0u); |
| |
| // Scope is extended to the example.app domain but not to the sub-domain |
| // test.example.app as there was no wildcard prefix. |
| EXPECT_EQ(registrar().GetAppExtendedScopeScore( |
| GURL("https://test.example.app"), app_id), |
| 0u); |
| |
| EXPECT_EQ(registrar().GetAppExtendedScopeScore( |
| GURL("https://other.origin.com"), app_id), |
| 0u); |
| EXPECT_EQ(registrar().GetAppExtendedScopeScore(GURL("https://testexample.co"), |
| app_id), |
| 0u); |
| EXPECT_EQ(registrar().GetAppExtendedScopeScore( |
| GURL("https://app.example.com"), app_id), |
| 0u); |
| } |
| |
| TEST_F(WebAppRegistrarTest_TabStrip, TabbedAppNewTabUrl) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(GURL("https://example.com/path")); |
| webapps::AppId app_id = web_app->app_id(); |
| GURL new_tab_url = GURL("https://example.com/path/newtab"); |
| |
| blink::Manifest::NewTabButtonParams new_tab_button_params; |
| new_tab_button_params.url = new_tab_url; |
| TabStrip tab_strip; |
| tab_strip.new_tab_button = new_tab_button_params; |
| |
| web_app->SetDisplayMode(DisplayMode::kTabbed); |
| web_app->SetTabStrip(tab_strip); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(registrar().GetAppNewTabUrl(app_id), new_tab_url); |
| } |
| |
| TEST_F(WebAppRegistrarTest_TabStrip, TabbedAppAutoNewTabUrl) { |
| StartWebAppProvider(); |
| |
| auto web_app = test::CreateWebApp(GURL("https://example.com/path")); |
| webapps::AppId app_id = web_app->app_id(); |
| |
| web_app->SetDisplayMode(DisplayMode::kTabbed); |
| RegisterAppUnsafe(std::move(web_app)); |
| |
| EXPECT_EQ(registrar().GetAppNewTabUrl(app_id), |
| registrar().GetAppStartUrl(app_id)); |
| } |
| |
| TEST_F(WebAppRegistrarTest, VerifyPlaceholderFinderBehavior) { |
| // Please note, this is a bad state done to test crbug.com/1427340. |
| // This should not occur once crbug.com/1434692 is implemented. |
| StartWebAppProvider(); |
| |
| // Add first app with install_url in the registry as a non-placeholder app, |
| // verify that the app is not a placeholder. |
| GURL install_url("https://start_install.com/"); |
| auto web_app1 = |
| test::CreateWebApp(GURL("https://start1.com"), WebAppManagement::kPolicy); |
| const webapps::AppId app_id1 = web_app1->app_id(); |
| RegisterAppUnsafe(std::move(web_app1)); |
| test::AddInstallUrlAndPlaceholderData( |
| profile()->GetPrefs(), &sync_bridge(), app_id1, install_url, |
| ExternalInstallSource::kExternalPolicy, /*is_placeholder=*/false); |
| EXPECT_FALSE( |
| registrar() |
| .LookupPlaceholderAppId(install_url, WebAppManagement::kPolicy) |
| .has_value()); |
| |
| // Add second app with same install_url in the registrar as a placeholder, |
| // verify that app shows up as a placeholder. |
| auto web_app2 = |
| test::CreateWebApp(GURL("https://start2.com"), WebAppManagement::kPolicy); |
| const webapps::AppId app_id2 = web_app2->app_id(); |
| RegisterAppUnsafe(std::move(web_app2)); |
| test::AddInstallUrlAndPlaceholderData( |
| profile()->GetPrefs(), &sync_bridge(), app_id2, install_url, |
| ExternalInstallSource::kExternalPolicy, /*is_placeholder=*/true); |
| auto placeholder_id = registrar().LookupPlaceholderAppId( |
| install_url, WebAppManagement::kPolicy); |
| |
| // This will fail if the fix for crbug.com/1427340 is reverted. |
| EXPECT_TRUE(placeholder_id.has_value()); |
| EXPECT_EQ(placeholder_id.value(), app_id2); |
| } |
| |
| TEST_F(WebAppRegistrarTest, InnerAndOuterScopeIntentPicker) { |
| StartWebAppProvider(); |
| const GURL document_url("https://abc.com/inner/abc.html"); |
| |
| auto outer_web_app = |
| test::CreateWebApp(GURL("https://abc.com"), WebAppManagement::kPolicy); |
| outer_web_app->SetName("ABC_Outer"); |
| outer_web_app->SetScope(GURL("https://abc.com/")); |
| const webapps::AppId outer_app_id = outer_web_app->app_id(); |
| RegisterAppUnsafe(std::move(outer_web_app)); |
| |
| auto inner_web_app = test::CreateWebApp(GURL("https://abc.com/inner"), |
| WebAppManagement::kDefault); |
| inner_web_app->SetName("ABC_Inner"); |
| inner_web_app->SetScope(GURL("https://abc.com/inner")); |
| const webapps::AppId inner_app_id = inner_web_app->app_id(); |
| RegisterAppUnsafe(std::move(inner_web_app)); |
| |
| // This should not be considered since the scopes do not match the visited |
| // URL. |
| auto no_match_scope_app = |
| test::CreateWebApp(GURL("https://def.com/"), WebAppManagement::kSync); |
| no_match_scope_app->SetName("App_No_Match"); |
| no_match_scope_app->SetScope(GURL("https://def.com/")); |
| const webapps::AppId no_match_scope_app_id = no_match_scope_app->app_id(); |
| RegisterAppUnsafe(std::move(no_match_scope_app)); |
| |
| // This should not be considered since this app is not set to open in a new |
| // window. |
| auto browser_mode_app = test::CreateWebApp( |
| GURL("https://abc.com/inner/outer"), WebAppManagement::kSync); |
| browser_mode_app->SetName("App_Browser"); |
| browser_mode_app->SetScope(GURL("https://abc.com/inner/")); |
| browser_mode_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| const webapps::AppId browser_mode_app_id = browser_mode_app->app_id(); |
| RegisterAppUnsafe(std::move(browser_mode_app)); |
| |
| EXPECT_THAT(registrar().GetAllAppsControllingUrl(document_url), |
| ElementsAre(Pair(inner_app_id, "ABC_Inner"), |
| Pair(outer_app_id, "ABC_Outer"))); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| class WebAppRegistrarAshTest : public WebAppTest, public WithCrosapiParam { |
| public: |
| void SetUp() override { |
| // Set up user manager to so that Lacros mode can be enabled. |
| // TODO(crbug.com/1463865): Consider setting up a fake user in all Ash web |
| // app tests. |
| auto user_manager = std::make_unique<ash::FakeChromeUserManager>(); |
| auto* fake_user_manager = user_manager.get(); |
| scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( |
| std::move(user_manager)); |
| auto* user = fake_user_manager->AddUser(user_manager::StubAccountId()); |
| fake_user_manager->UserLoggedIn(user_manager::StubAccountId(), |
| user->username_hash(), |
| /*browser_restart=*/false, |
| /*is_child=*/false); |
| // Need to run the WebAppTest::SetUp() after the fake user manager set up |
| // so that the scoped_user_manager can be destructed in the correct order. |
| WebAppTest::SetUp(); |
| |
| VerifyLacrosStatus(); |
| } |
| WebAppRegistrarAshTest() = default; |
| ~WebAppRegistrarAshTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; |
| }; |
| |
| TEST_P(WebAppRegistrarAshTest, SourceSupported) { |
| const GURL example_url("https://example.com/my-app/start"); |
| const GURL swa_url("chrome://swa/start"); |
| const GURL uninstalling_url("https://example.com/uninstalling/start"); |
| |
| webapps::AppId example_id; |
| webapps::AppId swa_id; |
| webapps::AppId uninstalling_id; |
| WebAppRegistrarMutable registrar(profile()); |
| { |
| Registry registry; |
| |
| auto example_app = test::CreateWebApp(example_url); |
| example_id = example_app->app_id(); |
| registry.emplace(example_id, std::move(example_app)); |
| |
| auto swa_app = test::CreateWebApp(swa_url, WebAppManagement::Type::kSystem); |
| swa_id = swa_app->app_id(); |
| registry.emplace(swa_id, std::move(swa_app)); |
| |
| auto uninstalling_app = |
| test::CreateWebApp(uninstalling_url, WebAppManagement::Type::kSystem); |
| uninstalling_app->SetIsUninstalling(true); |
| uninstalling_id = uninstalling_app->app_id(); |
| registry.emplace(uninstalling_id, std::move(uninstalling_app)); |
| |
| registrar.InitRegistry(std::move(registry)); |
| } |
| |
| if (GetParam() == CrosapiParam::kEnabled) { |
| // Non-system web apps are managed by Lacros, excluded in Ash |
| // WebAppRegistrar. |
| EXPECT_EQ(registrar.CountUserInstalledApps(), 0); |
| EXPECT_EQ(CountApps(registrar.GetApps()), 1); |
| |
| EXPECT_FALSE(registrar.FindAppWithUrlInScope(example_url).has_value()); |
| EXPECT_TRUE(registrar.GetAppScope(example_id).is_empty()); |
| EXPECT_FALSE(registrar.GetAppUserDisplayMode(example_id).has_value()); |
| } else { |
| EXPECT_EQ(registrar.CountUserInstalledApps(), 1); |
| EXPECT_EQ(CountApps(registrar.GetApps()), 2); |
| |
| EXPECT_EQ(registrar.FindAppWithUrlInScope(example_url), example_id); |
| EXPECT_EQ(registrar.GetAppScope(example_id), |
| GURL("https://example.com/my-app/")); |
| EXPECT_TRUE(registrar.GetAppUserDisplayMode(example_id).has_value()); |
| } |
| |
| EXPECT_EQ(registrar.FindAppWithUrlInScope(swa_url), swa_id); |
| EXPECT_EQ(registrar.GetAppScope(swa_id), GURL("chrome://swa/")); |
| EXPECT_TRUE(registrar.GetAppUserDisplayMode(swa_id).has_value()); |
| |
| EXPECT_FALSE(registrar.FindAppWithUrlInScope(uninstalling_url).has_value()); |
| EXPECT_EQ(registrar.GetAppScope(uninstalling_id), |
| GURL("https://example.com/uninstalling/")); |
| EXPECT_TRUE(registrar.GetAppUserDisplayMode(uninstalling_id).has_value()); |
| EXPECT_FALSE(base::Contains(registrar.GetAppIds(), uninstalling_id)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| WebAppRegistrarAshTest, |
| ::testing::Values(CrosapiParam::kEnabled, |
| CrosapiParam::kDisabled), |
| WithCrosapiParam::ParamToString); |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| using WebAppRegistrarLacrosTest = WebAppTest; |
| |
| TEST_F(WebAppRegistrarLacrosTest, SwaSourceNotSupported) { |
| const GURL example_url("https://example.com/my-app/start"); |
| const GURL swa_url("chrome://swa/start"); |
| const GURL uninstalling_url("https://example.com/uninstalling/start"); |
| |
| webapps::AppId example_id; |
| webapps::AppId swa_id; |
| webapps::AppId uninstalling_id; |
| WebAppRegistrarMutable registrar(profile()); |
| { |
| Registry registry; |
| |
| auto example_app = test::CreateWebApp(example_url); |
| example_id = example_app->app_id(); |
| registry.emplace(example_id, std::move(example_app)); |
| |
| auto swa_app = test::CreateWebApp(swa_url, WebAppManagement::Type::kSystem); |
| swa_id = swa_app->app_id(); |
| registry.emplace(swa_id, std::move(swa_app)); |
| |
| auto uninstalling_app = test::CreateWebApp(uninstalling_url); |
| uninstalling_app->SetIsUninstalling(true); |
| uninstalling_id = uninstalling_app->app_id(); |
| registry.emplace(uninstalling_id, std::move(uninstalling_app)); |
| |
| registrar.InitRegistry(std::move(registry)); |
| } |
| |
| EXPECT_EQ(registrar.FindAppWithUrlInScope(example_url), example_id); |
| EXPECT_EQ(registrar.GetAppScope(example_id), |
| GURL("https://example.com/my-app/")); |
| EXPECT_TRUE(registrar.GetAppUserDisplayMode(example_id).has_value()); |
| EXPECT_EQ(registrar.CountUserInstalledApps(), 1); |
| |
| // System web apps are managed by Ash, excluded in Lacros |
| // WebAppRegistrar. |
| EXPECT_EQ(CountApps(registrar.GetApps()), 1); |
| |
| EXPECT_FALSE(registrar.FindAppWithUrlInScope(swa_url).has_value()); |
| EXPECT_TRUE(registrar.GetAppScope(swa_id).is_empty()); |
| EXPECT_FALSE(registrar.GetAppUserDisplayMode(swa_id).has_value()); |
| |
| EXPECT_FALSE(registrar.FindAppWithUrlInScope(uninstalling_url).has_value()); |
| EXPECT_EQ(registrar.GetAppScope(uninstalling_id), |
| GURL("https://example.com/uninstalling/")); |
| EXPECT_TRUE(registrar.GetAppUserDisplayMode(uninstalling_id).has_value()); |
| EXPECT_FALSE(base::Contains(registrar.GetAppIds(), uninstalling_id)); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| class WebAppRegistrarTest_Shortstand : public WebAppRegistrarTest { |
| public: |
| WebAppRegistrarTest_Shortstand() { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| scoped_feature_list_.InitAndEnableFeature( |
| chromeos::features::kCrosShortstand); |
| #elif BUILDFLAG(IS_CHROMEOS_LACROS) |
| crosapi::mojom::BrowserInitParamsPtr init_params = |
| chromeos::BrowserInitParams::GetForTests()->Clone(); |
| init_params->is_cros_shortstand_enabled = true; |
| chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params)); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(WebAppRegistrarTest_Shortstand, IsShortcut) { |
| StartWebAppProvider(); |
| |
| { |
| // Verify that Docs, Sheets and Slides apps respects existing user display |
| // mode setting. |
| auto docs_web_app = test::CreateWebApp( |
| GURL("https://docs.google.com/document/?usp=installed_webapp"), |
| WebAppManagement::Type::kDefault); |
| docs_web_app->SetScope(GURL("https://docs.google.com/document/")); |
| docs_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| ASSERT_EQ(docs_web_app->app_id(), kGoogleDocsAppId); |
| |
| RegisterAppUnsafe(std::move(docs_web_app)); |
| EXPECT_TRUE(registrar().IsShortcutApp(kGoogleDocsAppId)); |
| |
| auto sheets_web_app = test::CreateWebApp( |
| GURL("https://docs.google.com/spreadsheets/?usp=installed_webapp"), |
| WebAppManagement::Type::kDefault); |
| sheets_web_app->SetScope(GURL("https://docs.google.com/spreadsheets/")); |
| sheets_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| ASSERT_EQ(sheets_web_app->app_id(), kGoogleSheetsAppId); |
| |
| RegisterAppUnsafe(std::move(sheets_web_app)); |
| EXPECT_TRUE(registrar().IsShortcutApp(kGoogleSheetsAppId)); |
| |
| auto slides_web_app = test::CreateWebApp( |
| GURL("https://docs.google.com/presentation/?usp=installed_webapp"), |
| WebAppManagement::Type::kDefault); |
| slides_web_app->SetScope(GURL("https://docs.google.com/presentation/")); |
| slides_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| ASSERT_EQ(slides_web_app->app_id(), kGoogleSlidesAppId); |
| |
| RegisterAppUnsafe(std::move(slides_web_app)); |
| EXPECT_TRUE(registrar().IsShortcutApp(kGoogleSlidesAppId)); |
| |
| Uninstall(kGoogleDocsAppId); |
| Uninstall(kGoogleSheetsAppId); |
| Uninstall(kGoogleSlidesAppId); |
| } |
| |
| // TODO(b/304660867): Test policy installed apps. |
| const GURL start_url = GURL("https://example.com/path"); |
| const GURL scope = GURL("https://example.com/scope"); |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| { |
| // Verify that system web app is not shortcut. |
| auto web_app = |
| test::CreateWebApp(start_url, WebAppManagement::Type::kSystem); |
| const webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| UninstallViaRemoveSource(web_app_id, WebAppManagement::Type::kSystem); |
| } |
| #endif |
| // Verify that user installed app with a scope is always not a shortcut. |
| { |
| auto web_app = test::CreateWebApp(start_url, WebAppManagement::Type::kSync); |
| web_app->SetScope(scope); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that playstore installed app with a scope is always not a shortcut |
| { |
| auto web_app = |
| test::CreateWebApp(start_url, WebAppManagement::Type::kWebAppStore); |
| web_app->SetScope(scope); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that default installed app is not a shortcut |
| { |
| auto web_app = |
| test::CreateWebApp(start_url, WebAppManagement::Type::kDefault); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that default installed app is not a shortcut |
| { |
| auto web_app = test::CreateWebApp(start_url, WebAppManagement::Type::kOem); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that default installed app is not a shortcut |
| { |
| auto web_app = |
| test::CreateWebApp(start_url, WebAppManagement::Type::kApsDefault); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| |
| UninstallViaRemoveSource(web_app_id, WebAppManagement::Type::kApsDefault); |
| } |
| |
| // Verify that default installed app with user install is not a shortcut |
| { |
| auto web_app = |
| test::CreateWebApp(start_url, WebAppManagement::Type::kDefault); |
| web_app->AddSource(WebAppManagement::kSync); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that user installed app with no scope is a shortcut if display mode |
| // is browser |
| { |
| auto web_app = test::CreateWebApp(start_url, WebAppManagement::Type::kSync); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_TRUE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that user installed app with no scope is not a shortcut if display |
| // mode is not browser |
| { |
| auto web_app = test::CreateWebApp(start_url, WebAppManagement::Type::kSync); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| |
| // Verify that user installed app with no scope is not a shortcut if display |
| // mode is not browser |
| { |
| auto web_app = test::CreateWebApp(start_url, WebAppManagement::Type::kSync); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kTabbed); |
| webapps::AppId web_app_id = web_app->app_id(); |
| |
| RegisterAppUnsafe(std::move(web_app)); |
| EXPECT_FALSE(registrar().IsShortcutApp(web_app_id)); |
| Uninstall(web_app_id); |
| } |
| } |
| |
| TEST_F(WebAppRegistrarTest_Shortstand, CannotFindShortcutWithUrlInScope) { |
| StartWebAppProvider(); |
| |
| const GURL shortcut1_page("https://example.com/shortcut/page"); |
| const GURL shortcut2_page("https://example.com/shortcut-two/page"); |
| const GURL shortcut3_page("https://not-example.com/shortcut/page"); |
| |
| const GURL shortcut1_launch("https://example.com/shortcut/launch"); |
| const GURL shortcut2_launch("https://example.com/shortcut-two/launch"); |
| const GURL shortcut3_launch("https://not-example.com/shortcut/launch"); |
| |
| const webapps::AppId shortcut1_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, shortcut1_launch); |
| const webapps::AppId shortcut2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, shortcut2_launch); |
| const webapps::AppId shortcut3_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, shortcut3_launch); |
| |
| // Implicit scope "https://example.com/shortcut/" |
| auto shortcut1 = test::CreateWebApp(shortcut1_launch); |
| shortcut1->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| RegisterAppUnsafe(std::move(shortcut1)); |
| |
| std::optional<webapps::AppId> shortcut2_match = |
| registrar().FindAppWithUrlInScope(shortcut2_page); |
| EXPECT_FALSE(shortcut2_match); |
| |
| std::optional<webapps::AppId> shortcut3_match = |
| registrar().FindAppWithUrlInScope(shortcut3_page); |
| EXPECT_FALSE(shortcut3_match); |
| |
| auto shortcut2 = test::CreateWebApp(shortcut2_launch); |
| shortcut2->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| RegisterAppUnsafe(std::move(shortcut2)); |
| |
| auto shortcut3 = test::CreateWebApp(shortcut3_launch); |
| shortcut3->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser); |
| RegisterAppUnsafe(std::move(shortcut3)); |
| |
| std::optional<webapps::AppId> shortcut1_match = |
| registrar().FindAppWithUrlInScope(shortcut1_page); |
| EXPECT_FALSE(shortcut1_match); |
| |
| shortcut2_match = registrar().FindAppWithUrlInScope(shortcut2_page); |
| EXPECT_FALSE(shortcut2_match); |
| |
| shortcut3_match = registrar().FindAppWithUrlInScope(shortcut3_page); |
| EXPECT_FALSE(shortcut3_match); |
| } |
| |
| // TODO(b/315263875): Parameterize this test (and other tests) to |
| // run in all platforms with shortstand on and off. |
| TEST_F(WebAppRegistrarTest_Shortstand, CanFindAppWithUrlInScope) { |
| StartWebAppProvider(); |
| |
| const GURL origin_scope("https://example.com/"); |
| |
| const GURL app1_scope("https://example.com/app"); |
| const GURL app2_scope("https://example.com/app-two"); |
| const GURL app3_scope("https://not-example.com/app"); |
| const GURL app4_scope("https://app-four.com/"); |
| |
| const webapps::AppId app1_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app1_scope); |
| const webapps::AppId app2_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app2_scope); |
| const webapps::AppId app3_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope); |
| const webapps::AppId app4_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, app3_scope); |
| |
| auto app1 = test::CreateWebApp(app1_scope); |
| app1->SetScope(app1_scope); |
| RegisterAppUnsafe(std::move(app1)); |
| |
| std::optional<webapps::AppId> app2_match = |
| registrar().FindAppWithUrlInScope(app2_scope); |
| DCHECK(app2_match); |
| EXPECT_EQ(*app2_match, app1_id); |
| |
| std::optional<webapps::AppId> app3_match = |
| registrar().FindAppWithUrlInScope(app3_scope); |
| EXPECT_FALSE(app3_match); |
| |
| std::optional<webapps::AppId> app4_match = |
| registrar().FindAppWithUrlInScope(app4_scope); |
| EXPECT_FALSE(app4_match); |
| |
| auto app2 = test::CreateWebApp(app2_scope); |
| app2->SetScope(app2_scope); |
| RegisterAppUnsafe(std::move(app2)); |
| |
| auto app3 = test::CreateWebApp(app3_scope); |
| app3->SetScope(app3_scope); |
| RegisterAppUnsafe(std::move(app3)); |
| |
| auto app4 = test::CreateWebApp(app4_scope); |
| app4->SetScope(app4_scope); |
| app4->SetIsUninstalling(true); |
| RegisterAppUnsafe(std::move(app4)); |
| |
| std::optional<webapps::AppId> origin_match = |
| registrar().FindAppWithUrlInScope(origin_scope); |
| EXPECT_FALSE(origin_match); |
| |
| std::optional<webapps::AppId> app1_match = |
| registrar().FindAppWithUrlInScope(app1_scope); |
| DCHECK(app1_match); |
| EXPECT_EQ(*app1_match, app1_id); |
| |
| app2_match = registrar().FindAppWithUrlInScope(app2_scope); |
| DCHECK(app2_match); |
| EXPECT_EQ(*app2_match, app2_id); |
| |
| app3_match = registrar().FindAppWithUrlInScope(app3_scope); |
| DCHECK(app3_match); |
| EXPECT_EQ(*app3_match, app3_id); |
| |
| // Apps in the process of uninstalling are ignored. |
| app4_match = registrar().FindAppWithUrlInScope(app4_scope); |
| EXPECT_FALSE(app4_match); |
| } |
| #endif |
| } // namespace web_app |