blob: f41fa8c3b1ed22b9933c1e1a3cf3901ea8a0d838 [file] [log] [blame]
// 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 "ash/constants/web_app_id_constants.h"
#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/build_config.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_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.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/scope_extension_info.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_icon_generator.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_management_type.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_data_type_local_change_processor.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "components/webapps/common/web_app_id.h"
#include "components/webapps/isolated_web_apps/types/storage_location.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)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/test_helper.h"
#include "components/user_manager/user_names.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->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
// Set an OS integration state (with shortcuts) to prevent migration to a
// partially installed status.
proto::os_state::WebAppOsIntegration os_state;
os_state.mutable_shortcut();
web_app->SetCurrentOsIntegrationStates(os_state);
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::IsEmpty;
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::kAppRemoved);
}
}
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::kAppRemoved);
}
private:
std::unique_ptr<FakeWebAppDatabaseFactory> database_factory_;
testing::NiceMock<syncer::MockDataTypeLocalChangeProcessor> 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_FALSE(registrar().IsInRegistrar(webapps::AppId()));
EXPECT_EQ(std::nullopt, registrar().GetInstallState(webapps::AppId()));
EXPECT_FALSE(registrar().IsInRegistrar(webapps::AppId()));
EXPECT_EQ(nullptr, 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::kUserInstalled);
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().IsInRegistrar(app_id));
EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
registrar().GetInstallState(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().IsInRegistrar(app_id));
EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
registrar().GetInstallState(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().IsInRegistrar(app_id));
EXPECT_EQ(std::nullopt, registrar().GetInstallState(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().IsInRegistrar(app_id2));
EXPECT_EQ(proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION,
registrar().GetInstallState(app_id2));
EXPECT_EQ(app_id2, app2->app_id());
Uninstall(app_id2);
EXPECT_FALSE(registrar().IsInRegistrar(app_id2));
EXPECT_EQ(std::nullopt, registrar().GetInstallState(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();
EXPECT_THAT(histogram_tester.GetAllSamples("WebApp.InstalledCount.ByUser"),
base::BucketsAre(base::Bucket(/*min=*/10,
/*count=*/1)));
EXPECT_THAT(histogram_tester.GetAllSamples(
"WebApp.InstalledCount.ByUserNotLocallyInstalled"),
base::BucketsAre(base::Bucket(/*min=*/0,
/*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"));
web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
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->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
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::kBrowser,
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_TRUE(registrar().IsInRegistrar(app_id));
EXPECT_EQ(proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE,
registrar().GetInstallState(app_id));
EXPECT_FALSE(registrar().IsInRegistrar("unknown"));
EXPECT_EQ(std::nullopt, registrar().GetInstallState("unknown"));
base::test::TestFuture<void> future;
fake_provider().scheduler().InstallAppLocally(app_id, future.GetCallback());
ASSERT_TRUE(future.Wait());
EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
registrar().GetInstallState(app_id));
EXPECT_EQ(mojom::UserDisplayMode::kStandalone,
registrar().GetAppUserDisplayMode(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().FindAllAppsNestedInUrl(
origin_scope,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_EQ(0u, in_scope.size());
// TODO(crbug.com/340952100): Evaluate call sites of DoesScopeContainAnyApp
// for correctness (note: multiple instances within this function).
EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
origin_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
app3_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
auto app1 = test::CreateWebApp(app1_scope);
app1->SetScope(app1_scope);
RegisterAppUnsafe(std::move(app1));
in_scope = registrar().FindAllAppsNestedInUrl(
origin_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
origin_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
app3_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
in_scope = registrar().FindAllAppsNestedInUrl(
app1_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
app1_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
auto app2 = test::CreateWebApp(app2_scope);
app2->SetScope(app2_scope);
RegisterAppUnsafe(std::move(app2));
in_scope = registrar().FindAllAppsNestedInUrl(
origin_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
origin_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
EXPECT_FALSE(registrar().DoesScopeContainAnyApp(
app3_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
in_scope = registrar().FindAllAppsNestedInUrl(
app1_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
app1_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
in_scope = registrar().FindAllAppsNestedInUrl(
app2_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app2_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
app2_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
auto app3 = test::CreateWebApp(app3_scope);
app3->SetScope(app3_scope);
RegisterAppUnsafe(std::move(app3));
in_scope = registrar().FindAllAppsNestedInUrl(
origin_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app1_id, app2_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
origin_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
in_scope = registrar().FindAllAppsNestedInUrl(
app3_scope, web_app::WebAppFilter::InstalledInChrome());
EXPECT_THAT(in_scope, testing::UnorderedElementsAre(app3_id));
EXPECT_TRUE(registrar().DoesScopeContainAnyApp(
app3_scope, {proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION}));
}
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().FindBestAppWithUrlInScope(
app2_scope, web_app::WebAppFilter::InstalledInChrome());
DCHECK(app2_match);
EXPECT_EQ(*app2_match, app1_id);
std::optional<webapps::AppId> app3_match =
registrar().FindBestAppWithUrlInScope(
app3_scope,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_FALSE(app3_match);
std::optional<webapps::AppId> app4_match =
registrar().FindBestAppWithUrlInScope(
app4_scope,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
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().FindBestAppWithUrlInScope(
origin_scope,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_FALSE(origin_match);
std::optional<webapps::AppId> app1_match =
registrar().FindBestAppWithUrlInScope(
app1_scope, web_app::WebAppFilter::InstalledInChrome());
DCHECK(app1_match);
EXPECT_EQ(*app1_match, app1_id);
app2_match = registrar().FindBestAppWithUrlInScope(
app2_scope, web_app::WebAppFilter::InstalledInChrome());
DCHECK(app2_match);
EXPECT_EQ(*app2_match, app2_id);
app3_match = registrar().FindBestAppWithUrlInScope(
app3_scope, web_app::WebAppFilter::InstalledInChrome());
DCHECK(app3_match);
EXPECT_EQ(*app3_match, app3_id);
// Apps in the process of uninstalling are ignored.
app4_match = registrar().FindBestAppWithUrlInScope(
app4_scope,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_FALSE(app4_match);
}
TEST_F(WebAppRegistrarTest, FindPwaBasedOnStartUrlIfScopeIsEmpty) {
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 GURL app3_launch("https://example.com/app/specific/launch3");
const webapps::AppId app3_id =
GenerateAppId(/*manifest_id=*/std::nullopt, app3_launch);
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().FindBestAppWithUrlInScope(
app2_page, web_app::WebAppFilter::InstalledInChrome());
ASSERT_TRUE(app2_match);
EXPECT_EQ(app2_match, std::optional<webapps::AppId>(app3_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::kUserInstalled`
// - `WebAppManagement::kWebAppStore`
// - `WebAppManagement::kOneDriveIntegration`
// - `WebAppManagement::kIwaUserInstalled`
EXPECT_EQ(5, 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(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
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(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
isolated_web_app->SetInstallState(
proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE);
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(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
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->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
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) {
base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
StartWebAppProvider();
std::unique_ptr<WebApp> web_app =
test::CreateWebApp(GURL("isolated-app://random_name"));
const webapps::AppId app_id = web_app->app_id();
web_app->SetDisplayMode(DisplayMode::kStandalone);
web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
web_app->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
web_app->SetIsolationData(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
RegisterAppUnsafe(std::move(web_app));
EXPECT_EQ(DisplayMode::kBrowser,
registrar().GetAppEffectiveDisplayMode(app_id));
}
TEST_F(WebAppRegistrarTest,
IsolatedWebAppsGetDisplayModeStandaloneRegardlessOfUserSettings) {
base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
StartWebAppProvider();
std::unique_ptr<WebApp> web_app =
test::CreateWebApp(GURL("isolated-app://random_name"));
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->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
web_app->SetIsolationData(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
RegisterAppUnsafe(std::move(web_app));
EXPECT_EQ(DisplayMode::kStandalone,
registrar().GetAppEffectiveDisplayMode(app_id));
}
TEST_F(WebAppRegistrarTest,
IsolatedWebAppsGetDisplayModeBorderlessRegardlessOfUserSettings) {
base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
StartWebAppProvider();
std::unique_ptr<WebApp> web_app =
test::CreateWebApp(GURL("isolated-app://random_name"));
const webapps::AppId app_id = web_app->app_id();
web_app->SetDisplayMode(DisplayMode::kBorderless);
web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
web_app->SetIsolationData(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
RegisterAppUnsafe(std::move(web_app));
EXPECT_EQ(DisplayMode::kBorderless,
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->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
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->SetInstallState(proto::SUGGESTED_FROM_ANOTHER_DEVICE);
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));
}
// This test uses SetLinkCapturingUserPreference, which is not appropriate for
// ChromeOS because link capturing preferences & overlapping scopes have custom
// behavior on CrOS.
#if !BUILDFLAG(IS_CHROMEOS)
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::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);
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();
// Limit scope to https://example.app/extended_scope
auto associate_url = GURL("https://example.app");
auto associate_extended_scope = GURL(associate_url.spec() + "extended_scope");
// Manifest entry {"origin": "https://*.example.co"}.
auto associate_url2 = GURL("https://example.co");
// Full scope
auto associate_url3 = GURL("https://example.co.uk");
// Scope with query and fragment
auto associate_url4 =
GURL("https://example.com.jp/search?q=asdf+text#fragment");
auto associate_extended_scope4 = GURL(associate_url4.spec());
auto associate_url5 = GURL("https://example.com/index.html/#fragment");
auto associate_extended_scope5 = GURL(associate_url5.spec());
web_app->SetValidatedScopeExtensions(
{ScopeExtensionInfo::CreateForScope(associate_extended_scope),
ScopeExtensionInfo::CreateForScope(associate_url2,
/*has_origin_wildcard=*/true),
ScopeExtensionInfo::CreateForScope(associate_url3),
ScopeExtensionInfo::CreateForScope(associate_extended_scope4),
ScopeExtensionInfo::CreateForScope(associate_extended_scope5)});
RegisterAppUnsafe(std::move(web_app));
EXPECT_EQ(
registrar().GetAppExtendedScopeScore(GURL("https://test.com"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.com/path"), app_id),
0);
// 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),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://test.app.example.co"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.co/path"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.app/extended_scope"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(GURL("https://example.co.uk"),
app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.com.jp/search"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.com.jp/search?q=something"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.com/index.html"), app_id),
0);
EXPECT_GT(registrar().GetAppExtendedScopeScore(
GURL("https://example.com/index.html#asdfragment"), app_id),
0);
// 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),
0);
EXPECT_EQ(registrar().GetAppExtendedScopeScore(
GURL("https://other.origin.com"), app_id),
0);
EXPECT_EQ(registrar().GetAppExtendedScopeScore(GURL("https://testexample.co"),
app_id),
0);
EXPECT_EQ(registrar().GetAppExtendedScopeScore(
GURL("https://app.example.com"), app_id),
0);
}
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);
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
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/"));
outer_web_app->SetInstallState(
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
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"));
inner_web_app->SetInstallState(
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
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")));
}
TEST_F(WebAppRegistrarTest, GetTrustedIconsIfPopulatedSingleNoSize) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
apps::IconInfo trusted_icon;
trusted_icon.purpose = apps::IconInfo::Purpose::kAny;
trusted_icon.square_size_px = 128;
trusted_icon.url = GURL("https://abc.com/icon.jpg");
web_app->SetTrustedIcons({trusted_icon});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
ElementsAre(trusted_icon));
EXPECT_EQ(trusted_icon,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/512));
}
TEST_F(WebAppRegistrarTest, EmptyTrustedOrManifestIcons) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
// Explicitly ensure that there are no manifest or trusted icons.
web_app->SetManifestIcons({});
web_app->SetTrustedIcons({});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id), IsEmpty());
EXPECT_EQ(std::nullopt,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/128));
}
TEST_F(WebAppRegistrarTest, NoTrustedIconsFallbackToManifest) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
// Explicitly ensure that there are no trusted icons, but manifest icons are
// populated.
web_app->SetTrustedIcons({});
apps::IconInfo manifest_icon;
manifest_icon.purpose = apps::IconInfo::Purpose::kAny;
manifest_icon.square_size_px = 128;
manifest_icon.url = GURL("https://abc.com/icon.jpg");
web_app->SetManifestIcons({manifest_icon});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
// There are no trusted app icons, but `manifest_icon` is used as the
// fallback.
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
ElementsAre(manifest_icon));
EXPECT_EQ(manifest_icon,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/64));
}
TEST_F(WebAppRegistrarTest, NoTrustedIconsFallbackToManifestMultipleIcons) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
// Explicitly ensure that there are no trusted icons, but manifest icons are
// populated.
web_app->SetTrustedIcons({});
apps::IconInfo manifest_icon1;
manifest_icon1.purpose = apps::IconInfo::Purpose::kAny;
manifest_icon1.square_size_px = 128;
manifest_icon1.url = GURL("https://abc.com/icon.jpg");
apps::IconInfo manifest_icon2;
manifest_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
manifest_icon2.square_size_px = 256;
manifest_icon2.url = GURL("https://abc.com/icon2.jpg");
web_app->SetManifestIcons({manifest_icon1, manifest_icon2});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
// There are no trusted app icons, but `manifest_icon2` is used as the
// fallback, since that is closest to the input_size.
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
ElementsAre(manifest_icon1, manifest_icon2));
EXPECT_EQ(manifest_icon2,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/512));
}
TEST_F(WebAppRegistrarTest, MultipleTrustedIconsUseBiggestClosestToSize) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
web_app->SetManifestIcons({});
apps::IconInfo trusted_icon1;
trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
trusted_icon1.square_size_px = 256;
trusted_icon1.url = GURL("https://abc.com/icon.jpg");
apps::IconInfo trusted_icon2;
trusted_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
trusted_icon2.square_size_px = 512;
trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
apps::IconInfo trusted_icon3;
trusted_icon3.purpose = apps::IconInfo::Purpose::kAny;
trusted_icon3.square_size_px = 96;
trusted_icon3.url = GURL("https://abc.com/icon3.jpg");
web_app->SetTrustedIcons({trusted_icon1, trusted_icon2, trusted_icon3});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
ElementsAre(trusted_icon1, trusted_icon2, trusted_icon3));
// `trusted_icon1` is used, since it is the biggest icon with size closer to
// the input size.
EXPECT_EQ(trusted_icon1,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/128));
}
TEST_F(WebAppRegistrarTest, MultipleTrustedIconsUseSmallerCloserToSize) {
StartWebAppProvider();
auto web_app = test::CreateWebApp(GURL("https://abc.com"),
WebAppManagement::kUserInstalled);
web_app->SetName("ABC");
web_app->SetScope(GURL("https://abc.com/"));
web_app->SetInstallState(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
web_app->SetManifestIcons({});
apps::IconInfo trusted_icon1;
trusted_icon1.purpose = apps::IconInfo::Purpose::kAny;
trusted_icon1.square_size_px = 128;
trusted_icon1.url = GURL("https://abc.com/icon.jpg");
apps::IconInfo trusted_icon2;
trusted_icon2.purpose = apps::IconInfo::Purpose::kMaskable;
trusted_icon2.square_size_px = 256;
trusted_icon2.url = GURL("https://abc.com/icon2.jpg");
apps::IconInfo trusted_icon3;
trusted_icon3.purpose = apps::IconInfo::Purpose::kAny;
trusted_icon3.square_size_px = 96;
trusted_icon3.url = GURL("https://abc.com/icon3.jpg");
web_app->SetTrustedIcons({trusted_icon1, trusted_icon2, trusted_icon3});
const webapps::AppId app_id = web_app->app_id();
RegisterAppUnsafe(std::move(web_app));
EXPECT_THAT(registrar().GetTrustedAppIconsMetadata(app_id),
ElementsAre(trusted_icon1, trusted_icon2, trusted_icon3));
// `trusted_icon2` is used, since it is the largest icon with size closer to
// the input size but smaller than the input size.
EXPECT_EQ(trusted_icon2,
registrar().GetSingleTrustedAppIconForSecuritySurfaces(
app_id, /*input_size=*/512));
}
TEST_F(WebAppRegistrarTest, TrustedIconMetrics) {
base::HistogramTester histogram_tester;
// Set up the registry with 10 apps, and set trusted icons on 5 of them.
Registry test_registry =
CreateRegistryForTesting("https://example.com/path", 10);
int i = 0;
apps::IconInfo icon_info(GURL("https://www.example.com/icon.png"),
icon_size::k48);
for (auto& apps : test_registry) {
if (i % 2 == 0) {
apps.second->SetTrustedIcons({icon_info});
}
i++;
}
PopulateRegistry(std::move(test_registry));
StartWebAppProvider();
EXPECT_THAT(
histogram_tester.GetAllSamples("WebApp.InstalledCount.HasTrustedIcons"),
base::BucketsAre(base::Bucket(/*min=*/5,
/*count=*/1)));
EXPECT_THAT(
histogram_tester.GetAllSamples("WebApp.InstalledCount.HasNoTrustedIcons"),
base::BucketsAre(base::Bucket(/*min=*/5,
/*count=*/1)));
}
#if BUILDFLAG(IS_CHROMEOS)
class WebAppRegistrarAshTest : public WebAppTest {
public:
void SetUp() override {
// TODO(crbug.com/40275387): 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));
fake_user_manager->AddUser(user_manager::StubAccountId());
fake_user_manager->UserLoggedIn(
user_manager::StubAccountId(),
user_manager::TestHelper::GetFakeUsernameHash(
user_manager::StubAccountId()));
// 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();
}
WebAppRegistrarAshTest() = default;
~WebAppRegistrarAshTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
};
TEST_F(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));
}
EXPECT_EQ(registrar.CountUserInstalledApps(), 1);
EXPECT_EQ(CountApps(registrar.GetApps()), 2);
EXPECT_EQ(registrar.FindBestAppWithUrlInScope(
example_url, web_app::WebAppFilter::InstalledInChrome()),
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.FindBestAppWithUrlInScope(
swa_url, web_app::WebAppFilter::InstalledInChrome()),
swa_id);
EXPECT_EQ(registrar.GetAppScope(swa_id), GURL("chrome://swa/"));
EXPECT_TRUE(registrar.GetAppUserDisplayMode(swa_id).has_value());
EXPECT_FALSE(
registrar
.FindBestAppWithUrlInScope(uninstalling_url,
web_app::WebAppFilter::InstalledInChrome())
.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)
class WebAppRegistrarDisplayModeTest
: public WebAppRegistrarTest,
public testing::WithParamInterface<DisplayMode> {
public:
const webapps::AppId CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode user_display_mode,
std::vector<DisplayMode> display_mode_overrides,
bool is_isolated = false) {
GURL start_url = GURL("https://example.com/start");
if (is_isolated) {
constexpr char kIwaHostname[] =
"berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
start_url =
GURL(base::StrCat({chrome::kIsolatedAppScheme,
url::kStandardSchemeSeparator, kIwaHostname}));
}
auto web_app = test::CreateWebApp(start_url);
const webapps::AppId app_id = web_app->app_id();
// Get the display mode from the parameterized inputs.
web_app->SetDisplayMode(GetParam());
web_app->SetUserDisplayMode(user_display_mode);
web_app->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
web_app->SetDisplayModeOverride(std::move(display_mode_overrides));
if (is_isolated) {
web_app->SetIsolationData(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
}
RegisterAppUnsafe(std::move(web_app));
return app_id;
}
// When user_display_mode indicates a user preference for opening in
// a standalone window, we open in a minimal-ui window (for app_display_mode
// 'browser' or 'minimal-ui') or a standalone window (for app_display_mode
// 'standalone' or 'fullscreen'). For all other display modes, keep the
// display modes as they're specified.
DisplayMode GetResolvedDisplayModeForStandaloneUDM() {
switch (GetParam()) {
case DisplayMode::kBrowser:
case DisplayMode::kMinimalUi:
return DisplayMode::kMinimalUi;
case DisplayMode::kStandalone:
case DisplayMode::kFullscreen:
return DisplayMode::kStandalone;
case DisplayMode::kBorderless:
return DisplayMode::kBorderless;
case DisplayMode::kWindowControlsOverlay:
return DisplayMode::kWindowControlsOverlay;
case DisplayMode::kTabbed:
return DisplayMode::kTabbed;
case DisplayMode::kUndefined:
case DisplayMode::kPictureInPicture:
NOTREACHED();
}
}
// Same as `GetResolvedDisplayModeForStandaloneUDM()`, except minimal-ui does
// not exist for IWAs.
DisplayMode GetResolvedDisplayModeForStandaloneUDMIsolated() {
switch (GetParam()) {
case DisplayMode::kBrowser:
case DisplayMode::kMinimalUi:
case DisplayMode::kStandalone:
case DisplayMode::kFullscreen:
case DisplayMode::kTabbed:
return DisplayMode::kStandalone;
case DisplayMode::kBorderless:
return DisplayMode::kBorderless;
case DisplayMode::kWindowControlsOverlay:
return DisplayMode::kWindowControlsOverlay;
case DisplayMode::kUndefined:
case DisplayMode::kPictureInPicture:
NOTREACHED();
}
}
private:
base::test::ScopedFeatureList feature_list_{features::kIsolatedWebApps};
};
// When user_display_mode indicates a user preference for opening in a browser
// tab, we open in a browser tab.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsBrowser) {
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kBrowser, {});
EXPECT_EQ(DisplayMode::kBrowser,
registrar().GetAppEffectiveDisplayMode(app_id));
}
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandalone) {
if (GetParam() == DisplayMode::kPictureInPicture) {
GTEST_SKIP()
<< "PictureInPicture not supported for standalone display modes";
}
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kStandalone, {});
EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDM(),
registrar().GetAppEffectiveDisplayMode(app_id));
}
// When user_display_mode indicates a user preference for opening in a browser
// tab, we open in a browser tab, even if display_overrides are specified.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsBrowserStandaloneOverride) {
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kBrowser, {DisplayMode::kStandalone});
EXPECT_EQ(DisplayMode::kBrowser,
registrar().GetAppEffectiveDisplayMode(app_id));
}
// When user_display_mode indicates a user preference for opening in
// a standalone window, and the only display modes provided for
// display_overrides contain only 'fullscreen' or 'browser', open in a
// minimal-ui window (for app_display_mode 'browser' or 'minimal-ui') or a
// standalone window (for app_display_mode 'standalone' or 'fullscreen').
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandaloneFullScreenOverride) {
if (GetParam() == DisplayMode::kPictureInPicture) {
GTEST_SKIP()
<< "PictureInPicture not supported for standalone display modes";
}
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kStandalone, {DisplayMode::kFullscreen});
EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDM(),
registrar().GetAppEffectiveDisplayMode(app_id));
}
// When user_display_mode indicates a user preference for opening in
// a standalone window, and return the first entry that is either
// 'standalone' or 'minimal-ui' in display_override.
TEST_P(WebAppRegistrarDisplayModeTest, UserWantsStandaloneMultipleOverrides) {
if (GetParam() == DisplayMode::kPictureInPicture) {
GTEST_SKIP()
<< "PictureInPicture not supported for standalone display modes";
}
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kStandalone,
{DisplayMode::kFullscreen, DisplayMode::kBrowser,
DisplayMode::kStandalone});
EXPECT_EQ(DisplayMode::kStandalone,
registrar().GetAppEffectiveDisplayMode(app_id));
}
TEST_P(WebAppRegistrarDisplayModeTest, UserStandaloneNoOverrideIsolated) {
if (GetParam() == DisplayMode::kPictureInPicture) {
GTEST_SKIP()
<< "PictureInPicture not supported for standalone display modes";
}
StartWebAppProvider();
const webapps::AppId app_id =
CreateAppInRegistryWithUserDisplayModeAndOverrides(
mojom::UserDisplayMode::kStandalone, {}, /*is_isolated=*/true);
EXPECT_EQ(GetResolvedDisplayModeForStandaloneUDMIsolated(),
registrar().GetAppEffectiveDisplayMode(app_id));
}
INSTANTIATE_TEST_SUITE_P(All,
WebAppRegistrarDisplayModeTest,
testing::Values(DisplayMode::kBrowser,
DisplayMode::kMinimalUi,
DisplayMode::kStandalone,
DisplayMode::kFullscreen,
DisplayMode::kBorderless,
DisplayMode::kPictureInPicture,
DisplayMode::kWindowControlsOverlay,
DisplayMode::kTabbed),
[](const testing::TestParamInfo<DisplayMode>& info) {
switch (info.param) {
case DisplayMode::kBrowser:
return "Browser";
case DisplayMode::kMinimalUi:
return "MinimalUi";
case DisplayMode::kStandalone:
return "Standalone";
case DisplayMode::kFullscreen:
return "Fullscreen";
case DisplayMode::kBorderless:
return "Borderless";
case DisplayMode::kPictureInPicture:
return "PictureInPicture";
case DisplayMode::kWindowControlsOverlay:
return "WindowControlsOverlay";
case DisplayMode::kTabbed:
return "Tabbed";
case DisplayMode::kUndefined:
NOTREACHED();
}
});
class WebAppRegistrarParameterizedTest
: public WebAppRegistrarTest,
public testing::WithParamInterface<
apps::test::LinkCapturingFeatureVersion> {
public:
WebAppRegistrarParameterizedTest() {
link_capturing_feature_list_.InitWithFeaturesAndParameters(
apps::test::GetFeaturesToEnableLinkCapturingUX(GetParam()), {});
}
private:
base::test::ScopedFeatureList link_capturing_feature_list_;
};
// These tests use SetLinkCapturingUserPreference, which is not appropriate for
// ChromeOS because link capturing preferences & overlapping scopes have custom
// behavior on CrOS.
#if !BUILDFLAG(IS_CHROMEOS)
TEST_P(WebAppRegistrarParameterizedTest, AppsOverlapIfSharesScope) {
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::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);
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_P(WebAppRegistrarParameterizedTest, Filter_OpensInBrowserTab) {
StartWebAppProvider();
GURL app_url_1 = GURL("https://example.com/1/");
GURL app_url_2 = GURL("https://example.com/2/");
auto web_app_1 =
test::CreateWebApp(app_url_1, WebAppManagement::kUserInstalled);
auto web_app_2 =
test::CreateWebApp(app_url_2, WebAppManagement::kUserInstalled);
const webapps::AppId app_id_browser_tab = web_app_1->app_id();
const webapps::AppId app_id_standalone = web_app_2->app_id();
web_app_1->SetDisplayMode(DisplayMode::kBrowser);
web_app_1->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
web_app_1->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
web_app_1->SetLinkCapturingUserPreference(
proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);
web_app_2->SetDisplayMode(DisplayMode::kStandalone);
web_app_2->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
web_app_2->SetInstallState(proto::INSTALLED_WITH_OS_INTEGRATION);
web_app_2->SetLinkCapturingUserPreference(
proto::NAVIGATION_CAPTURING_PREFERENCE_CAPTURE);
RegisterAppUnsafe(std::move(web_app_1));
RegisterAppUnsafe(std::move(web_app_2));
EXPECT_TRUE(registrar().AppMatches(app_id_browser_tab,
WebAppFilter::OpensInBrowserTab()));
EXPECT_FALSE(registrar().AppMatches(app_id_browser_tab,
WebAppFilter::OpensInDedicatedWindow()));
EXPECT_EQ(app_id_browser_tab,
registrar().FindBestAppWithUrlInScope(
app_url_1, WebAppFilter::OpensInBrowserTab()));
EXPECT_EQ(std::nullopt,
registrar().FindBestAppWithUrlInScope(
app_url_1, WebAppFilter::OpensInDedicatedWindow()));
EXPECT_TRUE(registrar().AppMatches(app_id_standalone,
WebAppFilter::OpensInDedicatedWindow()));
EXPECT_FALSE(registrar().AppMatches(app_id_standalone,
WebAppFilter::OpensInBrowserTab()));
EXPECT_EQ(app_id_standalone,
registrar().FindBestAppWithUrlInScope(
app_url_2, WebAppFilter::OpensInDedicatedWindow()));
EXPECT_EQ(std::nullopt, registrar().FindBestAppWithUrlInScope(
app_url_2, WebAppFilter::OpensInBrowserTab()));
}
#endif // !BUILDFLAG(IS_CHROMEOS)
TEST_P(WebAppRegistrarParameterizedTest, Filter_IsIsolatedApp) {
base::test::ScopedFeatureList scoped_feature_list(features::kIsolatedWebApps);
StartWebAppProvider();
constexpr char kIwaHostname[] =
"berugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic";
GURL app_url(base::StrCat({chrome::kIsolatedAppScheme,
url::kStandardSchemeSeparator, kIwaHostname}));
auto isolated_web_app = test::CreateWebApp(app_url);
const webapps::AppId app_id = isolated_web_app->app_id();
isolated_web_app->SetScope(isolated_web_app->start_url());
isolated_web_app->SetIsolationData(
IsolationData::Builder(
IwaStorageOwnedBundle{"random_name", /*dev_mode=*/false},
base::Version("1.0.0"))
.Build());
isolated_web_app->SetDisplayMode(DisplayMode::kBrowser);
isolated_web_app->SetUserDisplayMode(mojom::UserDisplayMode::kBrowser);
RegisterAppUnsafe(std::move(isolated_web_app));
EXPECT_TRUE(registrar().AppMatches(app_id, WebAppFilter::IsIsolatedApp()));
EXPECT_EQ(app_id, registrar().FindBestAppWithUrlInScope(
app_url, WebAppFilter::IsIsolatedApp()));
}
INSTANTIATE_TEST_SUITE_P(
,
WebAppRegistrarParameterizedTest,
#if BUILDFLAG(IS_CHROMEOS)
testing::Values(apps::test::LinkCapturingFeatureVersion::kV1DefaultOff,
apps::test::LinkCapturingFeatureVersion::kV2DefaultOff)
#else
testing::Values(apps::test::LinkCapturingFeatureVersion::kV2DefaultOff,
apps::test::LinkCapturingFeatureVersion::kV2DefaultOn)
#endif // BUILDFLAG(IS_CHROMEOS)
,
apps::test::LinkCapturingVersionToString);
} // namespace web_app