blob: 483035af242188d59f8e82a57a5c45a5018cde0f [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/externally_managed_app_manager.h"
#include <algorithm>
#include <iterator>
#include <optional>
#include <sstream>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/publishers/app_publisher.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/externally_managed_app_manager.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom-shared.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/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.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_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_management_type.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/common/chrome_features.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_page_metadata.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace web_app {
namespace {
int GetInstallCountsFromResult(
const std::map<GURL, ExternallyManagedAppManager::InstallResult>&
install_results,
bool include_duplicates = false) {
int count = 0;
for (const auto& result : install_results) {
if (result.second.code == webapps::InstallResultCode::kSuccessNewInstall ||
(include_duplicates &&
result.second.code ==
webapps::InstallResultCode::kSuccessAlreadyInstalled)) {
count++;
}
}
return count;
}
} // namespace
class ExternallyManagedAppManagerTest : public WebAppTest {
public:
ExternallyManagedAppManagerTest() = default;
protected:
void SetUp() override {
WebAppTest::SetUp();
provider_ = web_app::FakeWebAppProvider::Get(profile());
web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
}
void ForceSystemShutdown() { provider_->Shutdown(); }
void Sync(const std::vector<GURL>& urls, bool include_duplicates = false) {
ResetCounts();
std::vector<ExternalInstallOptions> install_options_list;
install_options_list.reserve(urls.size());
for (const auto& url : urls) {
install_options_list.emplace_back(
url, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
}
base::RunLoop run_loop;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
std::move(install_options_list),
ExternalInstallSource::kInternalDefault,
base::BindLambdaForTesting(
[&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode>
uninstall_results) {
install_count_ = GetInstallCountsFromResult(install_results,
include_duplicates);
uninstall_count_ = uninstall_results.size();
run_loop.Quit();
}));
// Wait for SynchronizeInstalledApps to finish.
run_loop.Run();
}
void Expect(int install_count,
int uninstall_count,
const std::vector<GURL>& installed_app_urls) {
EXPECT_EQ(install_count, install_count_);
EXPECT_EQ(uninstall_count, uninstall_count_);
base::flat_map<webapps::AppId, base::flat_set<GURL>> apps =
app_registrar().GetExternallyInstalledApps(
ExternalInstallSource::kInternalDefault);
std::vector<GURL> urls;
for (const auto& it : apps) {
std::ranges::copy(it.second, std::back_inserter(urls));
}
std::sort(urls.begin(), urls.end());
EXPECT_EQ(installed_app_urls, urls);
}
void ResetCounts() {
install_count_ = 0;
uninstall_count_ = 0;
}
WebAppProvider& provider() { return *provider_; }
WebAppRegistrar& app_registrar() { return provider().registrar_unsafe(); }
private:
int install_count_ = 0;
int uninstall_count_ = 0;
raw_ptr<FakeWebAppProvider, DanglingUntriaged> provider_ = nullptr;
};
// Test that destroying ExternallyManagedAppManager during a synchronize call
// that installs an app doesn't crash. Regression test for
// https://crbug.com/962808
TEST_F(ExternallyManagedAppManagerTest, DestroyDuringInstallInSynchronize) {
std::vector<ExternalInstallOptions> install_options_list;
install_options_list.emplace_back(GURL("https://foo.example"),
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
install_options_list.emplace_back(GURL("https://bar.example"),
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
provider().externally_managed_app_manager().SynchronizeInstalledApps(
std::move(install_options_list), ExternalInstallSource::kInternalDefault,
// ExternallyManagedAppManager gives no guarantees about whether its
// pending callbacks will be run or not when it gets destroyed.
base::DoNothing());
ForceSystemShutdown();
base::RunLoop().RunUntilIdle();
}
// Test that destroying ExternallyManagedAppManager during a synchronize call
// that uninstalls an app doesn't crash. Regression test for
// https://crbug.com/962808
TEST_F(ExternallyManagedAppManagerTest, DestroyDuringUninstallInSynchronize) {
// Install an app that will be uninstalled next.
{
std::vector<ExternalInstallOptions> install_options_list;
install_options_list.emplace_back(GURL("https://foo.example"),
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
base::RunLoop run_loop;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
std::move(install_options_list),
ExternalInstallSource::kInternalDefault,
base::BindLambdaForTesting(
[&](std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode>
uninstall_results) { run_loop.Quit(); }));
run_loop.Run();
}
provider().externally_managed_app_manager().SynchronizeInstalledApps(
std::vector<ExternalInstallOptions>(),
ExternalInstallSource::kInternalDefault,
// ExternallyManagedAppManager gives no guarantees about whether its
// pending callbacks will be run or not when it gets destroyed.
base::DoNothing());
ForceSystemShutdown();
base::RunLoop().RunUntilIdle();
}
TEST_F(ExternallyManagedAppManagerTest, SynchronizeInstalledApps) {
GURL a("https://a.example.com/");
GURL b("https://b.example.com/");
GURL c("https://c.example.com/");
GURL d("https://d.example.com/");
GURL e("https://e.example.com/");
FakeWebContentsManager& web_contents_manager =
static_cast<FakeWebContentsManager&>(provider().web_contents_manager());
web_contents_manager.CreateBasicInstallPageState(a, a, a);
web_contents_manager.CreateBasicInstallPageState(b, b, b);
web_contents_manager.CreateBasicInstallPageState(c, c, c);
web_contents_manager.CreateBasicInstallPageState(d, d, d);
web_contents_manager.CreateBasicInstallPageState(e, e, e);
Sync(std::vector<GURL>{a, b, d});
Expect(3, 0, std::vector<GURL>{a, b, d});
Sync(std::vector<GURL>{b, e});
Expect(1, 2, std::vector<GURL>{b, e});
Sync(std::vector<GURL>{e});
Expect(0, 1, std::vector<GURL>{e});
Sync(std::vector<GURL>{c});
Expect(1, 1, std::vector<GURL>{c});
Sync(std::vector<GURL>{e, a, d});
Expect(3, 1, std::vector<GURL>{a, d, e});
Sync(std::vector<GURL>{c, a, b, d, e});
Expect(2, 0, std::vector<GURL>{a, b, c, d, e});
Sync(std::vector<GURL>{});
Expect(0, 5, std::vector<GURL>{});
// The remaining code tests duplicate inputs.
Sync(std::vector<GURL>{b, a, b, c}, /*include_duplicates=*/true);
Expect(3, 0, std::vector<GURL>{a, b, c});
Sync(std::vector<GURL>{e, a, e, e, e, a}, /*include_duplicates=*/true);
Expect(2, 2, std::vector<GURL>{a, e});
Sync(std::vector<GURL>{b, c, d}, /*include_duplicates=*/true);
Expect(3, 2, std::vector<GURL>{b, c, d});
Sync(std::vector<GURL>{a, a, a, a, a, a}, /*include_duplicates=*/true);
Expect(1, 3, std::vector<GURL>{a});
Sync(std::vector<GURL>{}, /*include_duplicates=*/true);
Expect(0, 1, std::vector<GURL>{});
}
namespace {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::UnorderedElementsAre;
std::unique_ptr<WebAppInstallInfo> GetWebAppInstallInfo(const GURL& url) {
std::unique_ptr<WebAppInstallInfo> info =
WebAppInstallInfo::CreateWithStartUrlForTesting(url);
info->scope = url.GetWithoutFilename();
info->title = u"Web App";
return info;
}
// Test harness that keep the system as real as possible.
class ExternallyAppManagerTest : public WebAppTest {
public:
using InstallResults = std::map<GURL /*install_url*/,
ExternallyManagedAppManager::InstallResult>;
using UninstallResults =
std::map<GURL /*install_url*/, webapps::UninstallResultCode>;
using SynchronizeFuture =
base::test::TestFuture<InstallResults, UninstallResults>;
using InstallNowFuture =
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>;
ExternallyAppManagerTest() = default;
void SetUp() override {
WebAppTest::SetUp();
// TODO(http://b/278922549): Disable the external management apps so we
// don't compete with the policy app manager for our installs /
// synchronization.
web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
}
std::vector<ExternalInstallOptions> CreateExternalInstallOptionsFromTemplate(
std::vector<GURL> install_urls,
ExternalInstallSource source,
std::optional<ExternalInstallOptions> template_options = std::nullopt) {
std::vector<ExternalInstallOptions> output;
std::ranges::transform(
install_urls, std::back_inserter(output),
[source, &template_options](const GURL& install_url) {
ExternalInstallOptions options = template_options.value_or(
ExternalInstallOptions(install_url, std::nullopt, source));
options.install_url = install_url;
options.install_source = source;
return options;
});
return output;
}
ExternalInstallOptions CreateExternalInstallOptionsWithAppInfo(
GURL install_url,
ExternalInstallSource source) {
ExternalInstallOptions options(install_url,
mojom::UserDisplayMode::kBrowser, source);
options.app_info_factory =
base::BindRepeating(&GetWebAppInstallInfo, install_url);
return options;
}
WebAppProvider& provider() { return *WebAppProvider::GetForTest(profile()); }
WebAppRegistrar& app_registrar() { return provider().registrar_unsafe(); }
ExternallyManagedAppManager& external_manager() {
return provider().externally_managed_app_manager();
}
FakeWebContentsManager& web_contents_manager() {
return static_cast<FakeWebContentsManager&>(
provider().web_contents_manager());
}
};
TEST_F(ExternallyAppManagerTest, NoNetworkNoPlaceholder) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
// Not populating the `FakeWebContentsManager` means it treats the network as
// non-functional / not available.
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty uninstall results.
EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());
// Install should have failed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kInstallURLLoadFailed))));
}
TEST_F(ExternallyAppManagerTest, SimpleInstall) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty uninstall results.
EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_SimpleInstallWebAppInfo DISABLED_SimpleInstallWebAppInfo
#else
#define MAYBE_SimpleInstallWebAppInfo SimpleInstallWebAppInfo
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_SimpleInstallWebAppInfo) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
InstallNowFuture future;
external_manager().InstallNow(
CreateExternalInstallOptionsWithAppInfo(
kInstallUrl, ExternalInstallSource::kExternalPolicy),
future.GetCallback());
ASSERT_TRUE(future.Wait());
// Install should succeed.
ExternallyManagedAppManager::InstallResult result =
future.Get<ExternallyManagedAppManager::InstallResult>();
EXPECT_EQ(result,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_MultipleInstallWebAppInfoInstallUrl \
DISABLED_MultipleInstallWebAppInfoInstallUrl
#else
#define MAYBE_MultipleInstallWebAppInfoInstallUrl \
MultipleInstallWebAppInfoInstallUrl
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_MultipleInstallWebAppInfoInstallUrl) {
const GURL kStartUrl1 = GURL("https://www.example1.com/index.html");
const GURL kInstallUrl1 =
GURL("https://www.example1.com/nested/install_url.html");
const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json");
const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
const GURL kInstallUrl2 =
GURL("https://www.example2.com/nested/install_url.html");
const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl1, kStartUrl1);
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl2, kStartUrl2);
InstallNowFuture future1;
InstallNowFuture future2;
external_manager().InstallNow(
CreateExternalInstallOptionsWithAppInfo(
kInstallUrl1, ExternalInstallSource::kExternalDefault),
future1.GetCallback());
external_manager().InstallNow(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0],
future2.GetCallback());
ASSERT_TRUE(future1.Wait());
ASSERT_TRUE(future2.Wait());
// Both installs should succeed.
ExternallyManagedAppManager::InstallResult result1 =
future1.Get<ExternallyManagedAppManager::InstallResult>();
ExternallyManagedAppManager::InstallResult result2 =
future2.Get<ExternallyManagedAppManager::InstallResult>();
EXPECT_EQ(result1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id1));
EXPECT_EQ(result2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id2));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \
DISABLED_InstallWebAppInfoTwiceAlreadyInstalled
#else
#define MAYBE_InstallWebAppInfoTwiceAlreadyInstalled \
InstallWebAppInfoTwiceAlreadyInstalled
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallWebAppInfoTwiceAlreadyInstalled) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
InstallNowFuture future1;
InstallNowFuture future2;
external_manager().Install(
CreateExternalInstallOptionsWithAppInfo(
kInstallUrl, ExternalInstallSource::kExternalDefault),
future1.GetCallback());
external_manager().Install(
CreateExternalInstallOptionsWithAppInfo(
kInstallUrl, ExternalInstallSource::kExternalDefault),
future2.GetCallback());
ASSERT_TRUE(future1.Wait());
ASSERT_TRUE(future2.Wait());
// Both installs should succeed, with the 2nd one returning a
// `kSuccessAlreadyInstalled` result.
ExternallyManagedAppManager::InstallResult result1 =
future1.Get<ExternallyManagedAppManager::InstallResult>();
ExternallyManagedAppManager::InstallResult result2 =
future2.Get<ExternallyManagedAppManager::InstallResult>();
EXPECT_EQ(result1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id));
EXPECT_EQ(result2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessAlreadyInstalled, app_id));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallOptionsTwiceForReinstall \
DISABLED_InstallOptionsTwiceForReinstall
#else
#define MAYBE_InstallOptionsTwiceForReinstall InstallOptionsTwiceForReinstall
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallOptionsTwiceForReinstall) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
ExternalInstallOptions options = CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy)[0];
options.force_reinstall = true;
InstallNowFuture future1;
InstallNowFuture future2;
external_manager().Install(options, future1.GetCallback());
external_manager().Install(options, future2.GetCallback());
ASSERT_TRUE(future1.Wait());
ASSERT_TRUE(future2.Wait());
// Both installs should succeed, with the 2nd one returning a
// `kSuccessNewInstall` result because of the `force_reinstall` flag.
ExternallyManagedAppManager::InstallResult result1 =
future1.Get<ExternallyManagedAppManager::InstallResult>();
ExternallyManagedAppManager::InstallResult result2 =
future2.Get<ExternallyManagedAppManager::InstallResult>();
EXPECT_EQ(result1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id));
EXPECT_EQ(result2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_InstallReentrantCallback DISABLED_InstallReentrantCallback
#else
#define MAYBE_InstallReentrantCallback InstallReentrantCallback
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_InstallReentrantCallback) {
const GURL kStartUrl1 = GURL("https://www.example1.com/index.html");
const GURL kInstallUrl1 =
GURL("https://www.example1.com/nested/install_url.html");
const GURL kManifestUrl1 = GURL("https://www.example1.com/manifest.json");
const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
const GURL kInstallUrl2 =
GURL("https://www.example2.com/nested/install_url.html");
const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl1, kStartUrl1);
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl2, kStartUrl2);
InstallNowFuture inside_future;
ExternallyManagedAppManager::InstallResult external_result;
external_manager().InstallNow(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1}, ExternalInstallSource::kExternalPolicy)[0],
base::BindLambdaForTesting(
[&](const GURL& url,
ExternallyManagedAppManager::InstallResult result) {
// Verify the first installation has succeeded.
EXPECT_EQ(
result,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id1));
external_manager().InstallNow(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl2}, ExternalInstallSource::kExternalPolicy)[0],
inside_future.GetCallback());
}));
ASSERT_TRUE(inside_future.Wait());
// The intermediary install inside the callback should succeed.
ExternallyManagedAppManager::InstallResult result =
inside_future.Get<ExternallyManagedAppManager::InstallResult>();
EXPECT_EQ(result,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id2));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_MultipleAppsInstallSerial DISABLED_MultipleAppsInstallSerial
#else
#define MAYBE_MultipleAppsInstallSerial MultipleAppsInstallSerial
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_MultipleAppsInstallSerial) {
const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
const GURL kInstallUrl1 =
GURL("https://www.example.com/nested/install_url1.html");
const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl1, kStartUrl1);
SynchronizeFuture result1;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result1.GetCallback());
ASSERT_TRUE(result1.Wait());
// Empty uninstall results.
EXPECT_THAT(result1.Get<UninstallResults>(), IsEmpty());
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results1 =
result1.Get<InstallResults>();
EXPECT_THAT(
install_results1,
ElementsAre(std::make_pair(
kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id1))));
const GURL kStartUrl2 = GURL("https://www.example2.com/index.html");
const GURL kInstallUrl2 =
GURL("https://www.example2.com/nested/install_url.html");
const GURL kManifestUrl2 = GURL("https://www.example2.com/manifest.json");
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl2, kStartUrl2);
SynchronizeFuture result2;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result2.GetCallback());
ASSERT_TRUE(result2.Wait());
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results2 =
result2.Get<InstallResults>();
EXPECT_THAT(
install_results2,
ElementsAre(std::make_pair(
kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id2))));
}
// TODO(crbug.com/405912587): Investigate and enable on Linux TSAN bots.
#if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
#define MAYBE_PlaceholderAppWindowsClosed DISABLED_PlaceholderAppWindowsClosed
#else
#define MAYBE_PlaceholderAppWindowsClosed PlaceholderAppWindowsClosed
#endif // BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER)
TEST_F(ExternallyAppManagerTest, MAYBE_PlaceholderAppWindowsClosed) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// The webapps::AppId should be created from the install url.
webapps::AppId app_id =
GenerateAppId(/*manifest_id_path=*/std::nullopt, kInstallUrl);
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
const WebApp* app = provider().registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/true,
/*install_urls=*/{kInstallUrl},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, TwoInstallUrlsSameApp) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl1 =
GURL("https://www.example.com/nested/install_url.html");
const GURL kInstallUrl2 =
GURL("https://www.example.com/nested/install_url2.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl, kStartUrl);
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl, kStartUrl);
EXPECT_EQ(app_id, app_id2);
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty uninstall results.
EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());
// Installs should have both succeeded.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
UnorderedElementsAre(
std::make_pair(
kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id)),
std::make_pair(
kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
const WebApp* app = app_registrar().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(
WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl1, kInstallUrl2},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, RemovingInstallUrlsFromSource) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl1 =
GURL("https://www.example.com/nested/install_url.html");
const GURL kInstallUrl2 =
GURL("https://www.example.com/nested/install_url2.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl, kStartUrl);
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl, kStartUrl);
EXPECT_EQ(app_id, app_id2);
// Synchronize with 2 install URLs.
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1, kInstallUrl2},
ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty uninstall results.
EXPECT_THAT(result.Get<UninstallResults>(), IsEmpty());
// Installs should have both succeeded.
EXPECT_THAT(
result.Get<InstallResults>(),
UnorderedElementsAre(
std::make_pair(
kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id)),
std::make_pair(
kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
const WebApp* app = app_registrar().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(
WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl1, kInstallUrl2},
/*additional_policy_ids=*/{}))));
}
// Synchronize with 1 install URL.
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty install results.
EXPECT_THAT(result.Get<InstallResults>(),
UnorderedElementsAre(std::make_pair(
kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessAlreadyInstalled,
app_id))));
// One install URL uninstalled.
EXPECT_THAT(
result.Get<UninstallResults>(),
UnorderedElementsAre(std::make_pair(
kInstallUrl2, webapps::UninstallResultCode::kInstallUrlRemoved)));
EXPECT_EQ(app_registrar().GetAppIds().size(), 1ul);
const WebApp* app = app_registrar().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl1},
/*additional_policy_ids=*/{}))));
}
// Synchronize with 0 install URLs.
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Empty install results.
EXPECT_THAT(result.Get<InstallResults>(), IsEmpty());
// One install URL uninstalled.
EXPECT_THAT(result.Get<UninstallResults>(),
UnorderedElementsAre(std::make_pair(
kInstallUrl1, webapps::UninstallResultCode::kAppRemoved)));
// App should be cleaned up.
EXPECT_EQ(app_registrar().GetAppIds().size(), 0ul);
const WebApp* app = app_registrar().GetAppById(app_id);
ASSERT_FALSE(app);
}
}
TEST_F(ExternallyAppManagerTest, InstallUrlChanges) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kInstallUrl2 =
GURL("https://www.example.com/nested/install_url2.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl, kStartUrl);
EXPECT_EQ(app_id, app_id2);
// First synchronize will install the app.
{
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
}
// Second synchronize with a different install url should succeed and update
// the install urls correctly.
{
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl2}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
ASSERT_THAT(result.Get<UninstallResults>(),
testing::UnorderedElementsAre(std::make_pair(
kInstallUrl, webapps::UninstallResultCode::kAppRemoved)));
}
const WebApp* app = provider().registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl2},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, PolicyAppOverridesUserInstalledApp) {
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kInstallUrl =
GURL("https://www.example.com/nested/install_url.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
{
// Install user app
auto& install_page_state =
web_contents_manager().GetOrCreatePageState(kInstallUrl);
install_page_state.manifest_before_default_processing->short_name =
u"Test user app";
auto install_info =
WebAppInstallInfo::CreateWithStartUrlForTesting(kStartUrl);
install_info->title = u"Test user app";
std::optional<webapps::AppId> user_app_id =
test::InstallWebApp(profile(), std::move(install_info));
ASSERT_TRUE(user_app_id.has_value());
ASSERT_EQ(user_app_id.value(), app_id);
ASSERT_TRUE(app_registrar().WasInstalledByUser(app_id));
ASSERT_FALSE(app_registrar().HasExternalApp(app_id));
ASSERT_EQ("Test user app", app_registrar().GetAppShortName(app_id));
}
{
// Install policy app
auto& install_page_state =
web_contents_manager().GetOrCreatePageState(kInstallUrl);
install_page_state.manifest_before_default_processing->short_name =
u"Test policy app";
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
ASSERT_EQ("Test policy app", app_registrar().GetAppShortName(app_id));
}
}
TEST_F(ExternallyAppManagerTest, NoNetworkWithPlaceholder) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// The webapps::AppId should be created from the install url.
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
const WebApp* app = provider().registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/true,
/*install_urls=*/{kInstallUrl},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, RedirectInstallUrlPlaceholder) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kRedirectToUrl =
GURL("https://www.otherorigin.com/redirected.html");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
// Verify that a redirection causes a placeholder app to be installed.
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
page_state.redirection_url = kRedirectToUrl;
SynchronizeFuture result;
external_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// The webapps::AppId should be created from teh install url.
webapps::AppId app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
ElementsAre(std::make_pair(
kInstallUrl,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall, app_id))));
const WebApp* app = provider().registrar_unsafe().GetAppById(app_id);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/true,
/*install_urls=*/{kInstallUrl},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromSynchronize) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kRedirectToUrl =
GURL("https://www.otherorigin.com/redirected.html");
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
page_state.redirection_url = kRedirectToUrl;
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
}
webapps::AppId placeholder_app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id));
// Replace the redirect with an app that resolves.
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
// The placeholder app should be uninstalled & the real one installed.
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
}
app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id));
}
TEST_F(ExternallyAppManagerTest, PlaceholderResolvedFromInstallNow) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kRedirectToUrl =
GURL("https://www.otherorigin.com/redirected.html");
const GURL kStartUrl = GURL("https://www.example.com/index.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest.json");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl);
page_state.redirection_url = kRedirectToUrl;
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
}
webapps::AppId placeholder_app_id =
GenerateAppId(/*manifest_id=*/std::nullopt, kInstallUrl);
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(placeholder_app_id));
// Replace the redirect with an app that resolves.
webapps::AppId app_id = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl, kStartUrl);
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl;
options.placeholder_resolution_behavior =
PlaceholderResolutionBehavior::kClose;
InstallNowFuture install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id));
}
TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlSameSourceInstallNow) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl1, kStartUrl1);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
}
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id1));
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl2, kStartUrl2);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl;
InstallNowFuture install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
}
// TODO(crbug.com/40264854): This keeps the original app, but perhaps
// should install app_id2.
app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id1));
}
TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesInstallNow) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl1, kStartUrl1);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
}
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id1));
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl2, kStartUrl2);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl;
options.install_source = ExternalInstallSource::kInternalDefault;
InstallNowFuture install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
}
// TODO(crbug.com/40264854): Currently, this keeps the original app,
// but we should eventually resolve all apps to app_id2.
app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id1));
}
TEST_F(ExternallyAppManagerTest, TwoAppsSameInstallUrlTwoSourcesSynchronize) {
const GURL kInstallUrl = GURL("https://www.example.com/install_url.html");
const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
const GURL kStartUrl2 = GURL("https://www.example.com/index2.html");
const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");
ExternalInstallOptions template_options(
kInstallUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
webapps::AppId app_id1 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl1, kStartUrl1);
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
}
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, ElementsAre(app_id1));
webapps::AppId app_id2 = web_contents_manager().CreateBasicInstallPageState(
kInstallUrl, kManifestUrl2, kStartUrl2);
{
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl}, ExternalInstallSource::kInternalDefault,
template_options),
ExternalInstallSource::kInternalDefault, result.GetCallback());
ASSERT_TRUE(result.Wait());
}
// TODO(crbug.com/40264854): Currently this resolves to app_id1, but
// should probably eventually resolve to app_id2.
app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids, UnorderedElementsAre(app_id1));
}
TEST_F(ExternallyAppManagerTest, PlaceholderFixedBySecondInstallUrlInstallNow) {
const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json");
ExternalInstallOptions template_options(
kInstallUrl1, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
// The first install creates a placeholder at kInstallUrl1, as that redirects.
// The second install doesn't redirect, and the app at kInstallUrl2 points to
// kInstallUrl1 as it's start_url (basically - it's identity conflicts with
// the placeholder app's default identity).
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
page_state.redirection_url =
GURL("https://www.otherorigin.com/redirect.html");
webapps::AppId app_at_install_url =
web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl, kInstallUrl1);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl1;
InstallNowFuture install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
EXPECT_THAT(
install_future.Get(),
Eq(std::make_tuple(kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url))));
}
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl2;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
EXPECT_THAT(
install_future.Get(),
Eq(std::make_tuple(kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url))));
}
// The current implementation records placeholder information per-source, so
// when the second install succeeds, it overrides the `is_placeholder` to
// `false`, and thus the first install is considered fully installed now too.
// This is in contrast to the behavior if the two installs are different
// sources, which will (correctly?) evaluate the manifest served by the
// install url of the first install, which could be a different app identity.
const WebApp* app =
provider().registrar_unsafe().GetAppById(app_at_install_url);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(
WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl1, kInstallUrl2},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest,
PlaceholderFixedBySecondInstallUrlSynchronize) {
const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
const GURL kManifestUrl = GURL("https://www.example.com/manifest1.json");
// The first install creates a placeholder at kInstallUrl1, as that redirects.
// The second install doesn't redirect, and the app at kInstallUrl2 points to
// kInstallUrl1 as it's start_url (basically - it's identity conflicts with
// the placeholder app's default identity).
ExternalInstallOptions template_options(
kInstallUrl1, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
page_state.redirection_url =
GURL("https://www.otherorigin.com/redirect.html");
webapps::AppId app_at_install_url =
web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl, /*start_url=*/kInstallUrl1);
SynchronizeFuture result;
provider().externally_managed_app_manager().SynchronizeInstalledApps(
CreateExternalInstallOptionsFromTemplate(
{kInstallUrl1, kInstallUrl2}, ExternalInstallSource::kExternalPolicy,
template_options),
ExternalInstallSource::kExternalPolicy, result.GetCallback());
ASSERT_TRUE(result.Wait());
// Install should succeed.
std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results =
result.Get<InstallResults>();
EXPECT_THAT(
install_results,
UnorderedElementsAre(
std::make_pair(kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url)),
std::make_pair(kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url))));
// The current implementation records placeholder information per-source, so
// when the second install succeeds, it overrides the `is_placeholder` to
// `false`, and thus the first install is considered fully installed now too.
const WebApp* app =
provider().registrar_unsafe().GetAppById(app_at_install_url);
ASSERT_TRUE(app);
EXPECT_THAT(app->management_to_external_config_map(),
ElementsAre(std::make_pair(
WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl1, kInstallUrl2},
/*additional_policy_ids=*/{}))));
}
TEST_F(ExternallyAppManagerTest, PlaceholderFullInstallConflictCanUpdate) {
// This test exists to test what happens when a placeholder install conflicts
// with a another install from a different source, and then resolves to a
// separate app entirely after the placeholder state is fixed.
// Phase 1 configuration with redirect:
// - kInstallUrl1 will redirect & generate a placeholder
// - kInstallUrl2 will serve a manifest with kInstallUrl1 as the start_url
// Phase 2 configuration - redirect removed
// - kInstallUrl1 will serve a manifest with kStartUrl as the start_url.
// What happens when resolving the placeholder at kInstallUrl1? Currently this
// updates so that kStartUrl1 is installed.
const GURL kInstallUrl1 = GURL("https://www.example.com/install_url1.html");
const GURL kInstallUrl2 = GURL("https://www.example.com/install_url2.html");
const GURL kStartUrl1 = GURL("https://www.example.com/index1.html");
const GURL kManifestUrl1 = GURL("https://www.example.com/manifest1.json");
const GURL kManifestUrl2 = GURL("https://www.example.com/manifest2.json");
ExternalInstallOptions template_options(
kInstallUrl1, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
template_options.install_placeholder = true;
// Phase 1 state
auto& page_state = web_contents_manager().GetOrCreatePageState(kInstallUrl1);
page_state.redirection_url =
GURL("https://www.otherorigin.com/redirect.html");
webapps::AppId app_at_install_url =
web_contents_manager().CreateBasicInstallPageState(
kInstallUrl2, kManifestUrl1, kInstallUrl1);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl1;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
EXPECT_THAT(
install_future.Get(),
Eq(std::make_tuple(kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url))));
}
{
ExternalInstallOptions options(kInstallUrl2,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
EXPECT_THAT(
install_future.Get(),
Eq(std::make_tuple(kInstallUrl2,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_install_url))));
}
const WebApp* app =
provider().registrar_unsafe().GetAppById(app_at_install_url);
ASSERT_TRUE(app);
EXPECT_THAT(
app->management_to_external_config_map(),
UnorderedElementsAre(std::make_pair(WebAppManagement::kPolicy,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/true,
/*install_urls=*/{kInstallUrl1},
/*additional_policy_ids=*/{})),
std::make_pair(WebAppManagement::kDefault,
WebApp::ExternalManagementConfig(
/*is_placeholder=*/false,
/*install_urls=*/{kInstallUrl2},
/*additional_policy_ids=*/{}))));
// Phase 2 - undo the redirection, and point to start url.
webapps::AppId app_at_start_url =
web_contents_manager().CreateBasicInstallPageState(
kInstallUrl1, kManifestUrl2, kStartUrl1);
{
ExternalInstallOptions options = template_options;
options.install_url = kInstallUrl1;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
provider().externally_managed_app_manager().InstallNow(
std::move(options), install_future.GetCallback());
ASSERT_TRUE(install_future.Wait());
EXPECT_THAT(
install_future.Get(),
Eq(std::make_tuple(kInstallUrl1,
ExternallyManagedAppManager::InstallResult(
webapps::InstallResultCode::kSuccessNewInstall,
app_at_start_url))));
}
// This is the current behavior, but it could change if we decide that
// 'placeholder' is a per-app state instead of a per-app-and-source state.
auto app_ids = provider().registrar_unsafe().GetAppIds();
EXPECT_THAT(app_ids,
UnorderedElementsAre(app_at_install_url, app_at_start_url));
}
} // namespace
} // namespace web_app