blob: 81968a4398b5b0ea0e81b4c9c742991da855c4b4 [file] [log] [blame]
// Copyright 2023 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 <memory>
#include <optional>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/web_applications/web_app_browsertest_base.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/externally_managed_app_registration_task.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/test/external_app_registration_waiter.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app.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_management_type.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/webapps/browser/features.h"
#include "components/webapps/browser/install_result_code.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/origin.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "base/scoped_observation.h"
#include "base/test/run_until.h"
#include "base/test/test_future.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/web_applications/policy/web_app_policy_constants.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/message_center/public/cpp/notification.h"
#endif // BUILDFLAG(IS_CHROMEOS)
#if BUILDFLAG(IS_CHROMEOS)
using testing::_;
using testing::AllOf;
using testing::Eq;
using testing::Field;
using testing::Property;
#endif // BUILDFLAG(IS_CHROMEOS)
namespace web_app {
class ExternallyManagedAppManagerBrowserTest : public WebAppBrowserTestBase {
public:
std::unique_ptr<net::test_server::HttpResponse> SimulateRedirectHandler(
const net::test_server::HttpRequest& request) {
if (!simulate_redirect_) {
// Fall back to default handlers.
return nullptr;
}
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (request.GetURL().spec().find("redirected") != std::string::npos) {
response->set_code(net::HTTP_MOVED_PERMANENTLY);
response->set_content("Redirect successful");
return response;
}
std::string destination = request.GetURL().spec() + "/redirected";
response->set_code(net::HTTP_TEMPORARY_REDIRECT);
response->set_content_type("text/html");
response->AddCustomHeader("Location", destination);
response->AddCustomHeader("Access-Control-Allow-Origin", "*");
response->set_content(base::StringPrintf(
"<!doctype html><p>Redirecting to %s", destination.c_str()));
return response;
}
protected:
void SetUpOnMainThread() override {
WebAppBrowserTestBase::SetUpOnMainThread();
// Allow different origins to be handled by the embedded_test_server.
host_resolver()->AddRule("*", "127.0.0.1");
test::WaitUntilWebAppProviderAndSubsystemsReady(provider());
}
Profile* profile() { return browser()->profile(); }
WebAppRegistrar& registrar() { return provider()->registrar_unsafe(); }
WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }
ExternallyManagedAppManager& externally_managed_app_manager() {
return provider()->externally_managed_app_manager();
}
void InstallApp(ExternalInstallOptions install_options) {
auto result = ExternallyManagedAppManagerInstall(
profile(), std::move(install_options));
result_code_ = result.code;
}
void CheckServiceWorkerStatus(const GURL& url,
content::ServiceWorkerCapability status) {
std::unique_ptr<content::WebContents> web_contents =
content::WebContents::Create(
content::WebContents::CreateParams(profile()));
content::StoragePartition* storage_partition =
web_contents->GetBrowserContext()->GetStoragePartition(
web_contents->GetSiteInstance());
test::CheckServiceWorkerStatus(url, storage_partition, status);
}
std::optional<webapps::InstallResultCode> result_code_;
bool simulate_redirect_ = false;
};
// Basic integration test to make sure the whole flow works. Each step in the
// flow is unit tested separately.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceeds) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
InstallApp(CreateInstallOptions(url));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
EXPECT_TRUE(app_id.has_value());
EXPECT_EQ("Manifest test app", registrar().GetAppShortName(app_id.value()));
}
// If install URL redirects, install should still succeed.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceedsWithRedirect) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL start_url =
embedded_test_server()->GetURL("/banners/manifest_test_page.html");
GURL install_url =
embedded_test_server()->GetURL("/server-redirect?" + start_url.spec());
// TODO(crbug.com/381408483): Review usage of ExternalInstallOptions in tests.
ExternalInstallOptions install_options(
install_url, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
InstallApp(install_options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(install_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_EQ("Manifest test app", registrar().GetAppShortName(app_id.value()));
// Same AppID should be in the registrar using start_url from the manifest.
EXPECT_EQ(proto::INSTALLED_WITH_OS_INTEGRATION,
registrar().GetInstallState(app_id.value()));
std::optional<webapps::AppId> opt_app_id =
registrar().FindBestAppWithUrlInScope(
start_url,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_TRUE(opt_app_id.has_value());
EXPECT_EQ(*opt_app_id, app_id);
}
// If install URL redirects, install should still succeed.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceedsWithRedirectNoManifest) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url =
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html");
GURL install_url =
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec());
InstallApp(CreateInstallOptions(install_url));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(install_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_EQ("Web app banner test page",
registrar().GetAppShortName(app_id.value()));
std::optional<webapps::AppId> opt_app_id =
registrar().FindBestAppWithUrlInScope(
final_url,
web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
ASSERT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
registrar().GetInstallState(*opt_app_id));
ASSERT_TRUE(opt_app_id.has_value());
EXPECT_EQ(*opt_app_id, app_id);
EXPECT_EQ(registrar().GetAppStartUrl(*opt_app_id), final_url);
}
// Installing a placeholder app with shortcuts should succeed.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithShortcuts) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url = embedded_test_server()->GetURL(
"other.origin.com", "/banners/manifest_test_page.html");
// Add a redirect to a different origin, so a placeholder is installed.
GURL url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
ExternalInstallOptions options =
CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsSameAppId) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html");
ExternalInstallOptions options =
CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
simulate_redirect_ = false;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> final_app_id =
registrar().LookupExternalAppId(url);
ASSERT_TRUE(final_app_id.has_value());
EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(),
WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallPlaceholderAppWindowOpen) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html");
ExternalInstallOptions options =
CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
// Open an app window so that the placeholder resolution is delayed.
Browser* app_browser = LaunchWebAppBrowser(app_id.value());
EXPECT_NE(nullptr, app_browser);
options.placeholder_resolution_behavior =
PlaceholderResolutionBehavior::kWaitForAppWindowsClosed;
base::test::TestFuture<const GURL&,
ExternallyManagedAppManager::InstallResult>
install_future;
simulate_redirect_ = false;
provider()->externally_managed_app_manager().Install(
std::move(options), install_future.GetCallback());
// The callback will not be run since there is an existing app window that is
// open.
EXPECT_FALSE(install_future.IsReady());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
// Once the app window is closed, placeholder resolution should happen.
chrome::CloseWindow(app_browser);
EXPECT_TRUE(install_future.Wait());
EXPECT_EQ(
webapps::InstallResultCode::kSuccessNewInstall,
install_future.Get<ExternallyManagedAppManager::InstallResult>().code);
std::optional<webapps::AppId> final_app_id =
registrar().LookupExternalAppId(url);
ASSERT_TRUE(final_app_id.has_value());
EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(),
WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderResolutionNoAppWindow) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL url = embedded_test_server()->GetURL("/banners/manifest_test_page.html");
ExternalInstallOptions options =
CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
// Since no app windows are open, placeholders are updated instantly.
options.placeholder_resolution_behavior =
PlaceholderResolutionBehavior::kWaitForAppWindowsClosed;
simulate_redirect_ = false;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> final_app_id =
registrar().LookupExternalAppId(url);
ASSERT_TRUE(final_app_id.has_value());
EXPECT_FALSE(registrar().IsPlaceholderApp(final_app_id.value(),
WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsDifferentAppIdFomStartUrl) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL install_url = embedded_test_server()->GetURL(
"/banners/manifest_with_start_url_test_page.html");
ExternalInstallOptions options =
CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
const webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_url);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(install_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_EQ(placeholder_app_id, *app_id);
EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy));
simulate_redirect_ = false;
InstallApp(options);
GURL start_url = embedded_test_server()->GetURL(
"/banners/different_manifest_test_page.html");
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
const webapps::AppId new_app_id = GenerateAppId(std::nullopt, start_url);
EXPECT_NE(new_app_id, placeholder_app_id);
EXPECT_FALSE(registrar().IsInRegistrar(placeholder_app_id));
EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
registrar().GetInstallState(new_app_id));
EXPECT_FALSE(
registrar().IsPlaceholderApp(new_app_id, WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsDifferentAppIdFomManifestId) {
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL install_url = embedded_test_server()->GetURL(
"/banners/manifest_with_id_test_page.html");
ExternalInstallOptions options =
CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
InstallApp(options);
const webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_url);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(install_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_EQ(placeholder_app_id, *app_id);
EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy));
simulate_redirect_ = false;
InstallApp(options);
GURL start_url = embedded_test_server()->GetURL("/banners/start");
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
const webapps::AppId new_app_id = GenerateAppId("some_id", start_url);
EXPECT_NE(new_app_id, placeholder_app_id);
EXPECT_FALSE(registrar().IsInRegistrar(placeholder_app_id));
EXPECT_EQ(proto::InstallState::INSTALLED_WITH_OS_INTEGRATION,
registrar().GetInstallState(new_app_id));
EXPECT_FALSE(
registrar().IsPlaceholderApp(new_app_id, WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
#if BUILDFLAG(IS_CHROMEOS)
// Installing a placeholder app with a custom name should succeed.
// This feature is ChromeOS-only.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithCustomName) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url = embedded_test_server()->GetURL(
"other.origin.com", "/banners/manifest_test_page.html");
// Add a redirect to a different origin, so a placeholder is installed.
GURL url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
const std::string CUSTOM_NAME = "CUSTOM_NAME";
ExternalInstallOptions options =
CreateInstallOptions(url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
options.override_name = CUSTOM_NAME;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
EXPECT_EQ(CUSTOM_NAME,
registrar().GetAppById(app_id.value())->untranslated_name());
}
// Installing a placeholder app with a custom icon should succeed.
// This feature is ChromeOS-only.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithCustomIcon) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url = embedded_test_server()->GetURL(
"other.origin.com", "/banners/manifest_test_page.html");
// Add a redirect to a different origin, so a placeholder is installed.
GURL app_url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
// 192 is chosen to not be part of web_app_icon_generator.h:SizesToGenerate().
GURL icon_url = embedded_test_server()->GetURL("/banners/192x192-green.png");
const SquareSizePx kIconSize = 192;
const SkColor kIconColor = SK_ColorGREEN;
const auto kGeneratedSizes = SizesToGenerate();
EXPECT_TRUE(kGeneratedSizes.find(kIconSize) == kGeneratedSizes.end());
ExternalInstallOptions options =
CreateInstallOptions(app_url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
options.override_icon_url = icon_url;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(app_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
SortedSizesPx downloaded_sizes =
registrar().GetAppTrustedIconSizesFallbackToUntrusted(app_id.value());
EXPECT_EQ(1u + kGeneratedSizes.size(), downloaded_sizes.size());
EXPECT_TRUE(downloaded_sizes.find(kIconSize) != downloaded_sizes.end());
EXPECT_EQ(kIconColor,
IconManagerReadAppIconPixel(provider()->icon_manager(),
app_id.value(), kIconSize, 0, 0));
}
// This RequestHandler returns HTTP_NOT_FOUND the first time a URL containing
// |relative_url| is requested, and behaves normally in all other cases.
std::unique_ptr<net::test_server::HttpResponse> FailFirstRequest(
const std::string& relative_url,
const net::test_server::HttpRequest& request) {
static bool first_run = true;
if (first_run &&
request.GetURL().spec().find(relative_url) != std::string::npos) {
first_run = false;
auto not_found_response =
std::make_unique<net::test_server::BasicHttpResponse>();
not_found_response->set_code(net::HTTP_NOT_FOUND);
return std::move(not_found_response);
}
// Return nullptr to use the default handlers.
return nullptr;
}
// Installing a placeholder app with a custom icon should succeed, even we have
// to retry fetching the icon once.
// This feature is ChromeOS-only.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithCustomIconAfterRetry) {
// Fail the first time that this URL is loaded.
std::string kIconRelativeUrl = "/banners/192x192-green.png";
embedded_test_server()->RegisterRequestHandler(
base::BindRepeating(&FailFirstRequest, kIconRelativeUrl));
ASSERT_TRUE(embedded_test_server()->Start());
GURL final_url = embedded_test_server()->GetURL(
"other.origin.com", "/banners/manifest_test_page.html");
// Add a redirect to a different origin, so a placeholder is installed.
GURL app_url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
// 192 is chosen to not be part of web_app_icon_generator.h:SizesToGenerate().
GURL icon_url = embedded_test_server()->GetURL(kIconRelativeUrl);
const SquareSizePx kIconSize = 192;
const SkColor kIconColor = SK_ColorGREEN;
const auto kGeneratedSizes = SizesToGenerate();
EXPECT_TRUE(kGeneratedSizes.find(kIconSize) == kGeneratedSizes.end());
ExternalInstallOptions options =
CreateInstallOptions(app_url, ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.add_to_applications_menu = true;
options.add_to_desktop = true;
options.override_icon_url = icon_url;
InstallApp(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(app_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_TRUE(
registrar().IsPlaceholderApp(app_id.value(), WebAppManagement::kPolicy));
SortedSizesPx downloaded_sizes =
registrar().GetAppTrustedIconSizesFallbackToUntrusted(app_id.value());
EXPECT_EQ(1u + kGeneratedSizes.size(), downloaded_sizes.size());
EXPECT_TRUE(downloaded_sizes.find(kIconSize) != downloaded_sizes.end());
EXPECT_EQ(kIconColor,
IconManagerReadAppIconPixel(provider()->icon_manager(),
app_id.value(), kIconSize, 0, 0));
}
#endif // BUILDFLAG(IS_CHROMEOS)
// Tests that the browser doesn't crash if it gets shutdown with a pending
// installation.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
ShutdownWithPendingInstallation) {
ASSERT_TRUE(embedded_test_server()->Start());
ExternalInstallOptions install_options = CreateInstallOptions(
embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
// Start an installation but don't wait for it to finish.
provider()->externally_managed_app_manager().Install(
std::move(install_options), base::DoNothing());
// The browser should shutdown cleanly even if there is a pending
// installation.
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, ForceReinstall) {
ASSERT_TRUE(embedded_test_server()->Start());
std::optional<webapps::AppId> app_id;
{
GURL url(embedded_test_server()->GetURL(
"/banners/"
"manifest_test_page.html?manifest=manifest_short_name_only.json"));
ExternalInstallOptions install_options = CreateInstallOptions(url);
install_options.force_reinstall = true;
InstallApp(std::move(install_options));
app_id = registrar().FindBestAppWithUrlInScope(
url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_TRUE(app_id.has_value());
EXPECT_EQ("Manifest", registrar().GetAppShortName(app_id.value()));
}
{
GURL url(
embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
ExternalInstallOptions install_options = CreateInstallOptions(url);
install_options.force_reinstall = true;
InstallApp(std::move(install_options));
std::optional<webapps::AppId> new_app_id =
registrar().FindBestAppWithUrlInScope(
url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
EXPECT_TRUE(new_app_id.has_value());
EXPECT_EQ(new_app_id, app_id);
EXPECT_EQ("Manifest test app",
registrar().GetAppShortName(new_app_id.value()));
}
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PolicyAppOverridesUserInstalledApp) {
ASSERT_TRUE(embedded_test_server()->Start());
std::optional<webapps::AppId> app_id;
{
// Install user app
GURL url(
embedded_test_server()->GetURL("/banners/"
"manifest_test_page.html"));
auto install_info = WebAppInstallInfo::CreateWithStartUrlForTesting(url);
install_info->title = u"Test user app";
app_id = test::InstallWebApp(profile(), std::move(install_info));
ASSERT_TRUE(app_id.has_value());
ASSERT_TRUE(registrar().WasInstalledByUser(app_id.value()));
ASSERT_FALSE(registrar().HasExternalApp(app_id.value()));
ASSERT_EQ("Test user app", registrar().GetAppShortName(app_id.value()));
}
{
// Install policy app
GURL url(
embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
std::optional<webapps::AppId> policy_app_id =
ForceInstallWebApp(profile(), url);
ASSERT_EQ(policy_app_id, app_id);
ASSERT_EQ("Manifest test app",
registrar().GetAppShortName(policy_app_id.value()));
}
}
// Test that adding a manifest that points to a chrome:// URL does not actually
// install a web app that points to a chrome:// URL.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallChromeURLFails) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest=manifest_chrome_url.json"));
InstallApp(CreateInstallOptions(url));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
std::optional<webapps::AppId> app_id = registrar().LookupExternalAppId(url);
ASSERT_TRUE(app_id.has_value());
// The installer falls back to installing a web app of the original URL.
EXPECT_EQ(url, registrar().GetAppStartUrl(app_id.value()));
EXPECT_NE(app_id,
registrar().FindBestAppWithUrlInScope(
GURL("chrome://settings"),
web_app::WebAppFilter::InstalledInOperatingSystemForTesting()));
}
// Test that adding a web app without a manifest while using the
// |require_manifest| flag fails.
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RequireManifestFailsIfNoManifest) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(
embedded_test_server()->GetURL("/banners/no_manifest_test_page.html"));
ExternalInstallOptions install_options = CreateInstallOptions(url);
install_options.require_manifest = true;
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kNotValidManifestForWebApp,
result_code_.value());
std::optional<webapps::AppId> id = registrar().LookupExternalAppId(url);
ASSERT_FALSE(id.has_value());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationSucceeds) {
ASSERT_TRUE(embedded_test_server()->Start());
// Delay service worker registration to second load to simulate it not loading
// during the initial install pass.
GURL install_url(embedded_test_server()->GetURL(
"/web_apps/service_worker_on_second_load.html"));
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
ExternalAppRegistrationWaiter(&externally_managed_app_manager())
.AwaitNextRegistration(install_url, RegistrationResultCode::kSuccess);
CheckServiceWorkerStatus(
install_url,
content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationAlternateUrlSucceeds) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url(
embedded_test_server()->GetURL("/web_apps/no_service_worker.html"));
GURL registration_url =
embedded_test_server()->GetURL("/web_apps/basic.html");
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
install_options.service_worker_registration_url = registration_url;
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
ExternalAppRegistrationWaiter(&externally_managed_app_manager())
.AwaitNextRegistration(registration_url,
RegistrationResultCode::kSuccess);
CheckServiceWorkerStatus(
install_url,
content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationSkipped) {
ASSERT_TRUE(embedded_test_server()->Start());
// Delay service worker registration to second load to simulate it not loading
// during the initial install pass.
GURL install_url(embedded_test_server()->GetURL(
"/web_apps/service_worker_on_second_load.html"));
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
install_options.load_and_await_service_worker_registration = false;
ExternalAppRegistrationWaiter waiter(&externally_managed_app_manager());
InstallApp(std::move(install_options));
waiter.AwaitRegistrationsComplete();
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
CheckServiceWorkerStatus(install_url,
content::ServiceWorkerCapability::NO_SERVICE_WORKER);
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
ServiceWorkerRegistrationSkippedForChromeScheme) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url("chrome://web-app-internals/");
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
install_options.load_and_await_service_worker_registration = false;
ExternalAppRegistrationWaiter waiter(&externally_managed_app_manager());
InstallApp(std::move(install_options));
waiter.AwaitRegistrationsComplete();
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
CheckServiceWorkerStatus(install_url,
content::ServiceWorkerCapability::NO_SERVICE_WORKER);
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
AlreadyRegistered) {
ASSERT_TRUE(embedded_test_server()->Start());
// Ensure service worker registered for http://embedded_test_server/web_apps/.
// We don't need to be installing a web app here but it's convenient just to
// await the service worker registration.
{
GURL install_url(embedded_test_server()->GetURL("/web_apps/basic.html"));
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
install_options.force_reinstall = true;
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
ExternalAppRegistrationWaiter(&externally_managed_app_manager())
.AwaitNextNonFailedRegistration(install_url);
CheckServiceWorkerStatus(
embedded_test_server()->GetURL("/web_apps/basic.html"),
content::ServiceWorkerCapability::SERVICE_WORKER_WITH_FETCH_HANDLER);
}
// With the service worker registered we install a page that doesn't register
// a service worker to check that the existing service worker is seen by our
// service worker registration step.
{
GURL install_url(
embedded_test_server()->GetURL("/web_apps/no_service_worker.html"));
ExternalInstallOptions install_options = CreateInstallOptions(install_url);
install_options.force_reinstall = true;
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
ExternalAppRegistrationWaiter(&externally_managed_app_manager())
.AwaitNextRegistration(install_url,
RegistrationResultCode::kAlreadyRegistered);
}
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
CannotFetchManifest) {
// With a flaky network connection, clients may request an app whose manifest
// cannot currently be retrieved. The app display mode is then assumed to be
// 'browser'.
ASSERT_TRUE(embedded_test_server()->Start());
const GURL app_url(embedded_test_server()->GetURL(
"/banners/manifest_test_page.html?manifest=does_not_exist.json"));
std::vector<ExternalInstallOptions> desired_apps_install_options;
{
ExternalInstallOptions install_options(
app_url, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
install_options.add_to_applications_menu = false;
install_options.add_to_desktop = false;
install_options.add_to_quick_launch_bar = false;
install_options.require_manifest = false;
desired_apps_install_options.push_back(std::move(install_options));
}
base::RunLoop run_loop;
externally_managed_app_manager().SynchronizeInstalledApps(
std::move(desired_apps_install_options),
ExternalInstallSource::kExternalPolicy,
base::BindLambdaForTesting(
[&run_loop, &app_url](
std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode> uninstall_results) {
EXPECT_TRUE(uninstall_results.empty());
EXPECT_EQ(install_results.size(), 1U);
EXPECT_EQ(install_results[app_url].code,
webapps::InstallResultCode::kSuccessNewInstall);
run_loop.Quit();
}));
run_loop.Run();
std::optional<webapps::AppId> app_id = registrar().FindBestAppWithUrlInScope(
app_url, web_app::WebAppFilter::InstalledInOperatingSystemForTesting());
DCHECK(app_id.has_value());
EXPECT_EQ(registrar().GetAppDisplayMode(*app_id), DisplayMode::kBrowser);
EXPECT_EQ(registrar().GetAppUserDisplayMode(*app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetAppEffectiveDisplayMode(*app_id),
DisplayMode::kMinimalUi);
EXPECT_FALSE(registrar().GetAppThemeColor(*app_id).has_value());
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationTimeout) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL(
"/banners/manifest_no_service_worker.html"));
CheckServiceWorkerStatus(url,
content::ServiceWorkerCapability::NO_SERVICE_WORKER);
ExternalInstallOptions install_options = CreateInstallOptions(url);
install_options.service_worker_registration_timeout = base::Seconds(0);
InstallApp(std::move(install_options));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
ExternalAppRegistrationWaiter(&externally_managed_app_manager())
.AwaitNextRegistration(url, RegistrationResultCode::kTimeout);
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
ReinstallPolicyAppWithLocallyInstalledApp) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url(embedded_test_server()->GetURL("/banners/manifest_test_page.html"));
// Install user app
auto install_info = WebAppInstallInfo::CreateWithStartUrlForTesting(url);
install_info->title = u"Test user app";
webapps::AppId app_id =
test::InstallWebApp(profile(), std::move(install_info));
ASSERT_TRUE(registrar().WasInstalledByUser(app_id));
ASSERT_FALSE(registrar().HasExternalApp(app_id));
// Install policy app
std::optional<webapps::AppId> policy_app_id =
ForceInstallWebApp(profile(), url);
ASSERT_EQ(policy_app_id, app_id);
// Uninstall policy app
std::vector<ExternalInstallOptions> desired_apps_install_options;
base::RunLoop run_loop;
externally_managed_app_manager().SynchronizeInstalledApps(
std::move(desired_apps_install_options),
ExternalInstallSource::kExternalPolicy,
base::BindLambdaForTesting(
[&run_loop, &url](
std::map<GURL, ExternallyManagedAppManager::InstallResult>
install_results,
std::map<GURL, webapps::UninstallResultCode> uninstall_results) {
EXPECT_TRUE(install_results.empty());
EXPECT_EQ(uninstall_results.size(), 1U);
EXPECT_EQ(uninstall_results[url],
webapps::UninstallResultCode::kInstallSourceRemoved);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_FALSE(registrar().GetAppById(app_id)->IsPolicyInstalledApp());
// Reinstall policy app
ForceInstallWebApp(profile(), url);
ASSERT_TRUE(registrar().GetAppById(app_id)->IsPolicyInstalledApp());
}
class ExternallyManagedAppManagerBrowserTestShortcut
: public ExternallyManagedAppManagerBrowserTest,
public testing::WithParamInterface<bool> {
public:
ExternallyManagedAppManagerBrowserTestShortcut() = default;
};
// Tests behavior when ExternalInstallOptions.install_as_shortcut is enabled
IN_PROC_BROWSER_TEST_P(ExternallyManagedAppManagerBrowserTestShortcut,
InstallAsShortcut) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL install_url(
embedded_test_server()->GetURL("/web_apps/different_start_url.html"));
GURL manifest_start_url(
embedded_test_server()->GetURL("/web_apps/basic.html"));
ExternalInstallOptions options =
CreateInstallOptions(install_url, ExternalInstallSource::kExternalPolicy);
options.install_as_diy = GetParam();
InstallApp(options);
ASSERT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
result_code_.value());
// The main difference between a normal web app installation and a shortcut
// creation is that in the latter the start_url field of the page's manifest
// is ignored. Thus the installation URL is always used even when the
// manifest tells otherwise, as in the test page used here.
const bool startUrlIsInstallUrl =
registrar().GetAppByStartUrl(install_url) != nullptr;
const bool startUrlFromManifest =
registrar().GetAppByStartUrl(manifest_start_url) != nullptr;
EXPECT_NE(startUrlIsInstallUrl, startUrlFromManifest);
EXPECT_EQ(options.install_as_diy, startUrlIsInstallUrl);
}
INSTANTIATE_TEST_SUITE_P(All,
ExternallyManagedAppManagerBrowserTestShortcut,
::testing::Bool());
#if BUILDFLAG(IS_CHROMEOS)
class PlaceholderUpdateRelaunchBrowserTest
: public ExternallyManagedAppManagerBrowserTest,
public NotificationDisplayService::Observer {
public:
~PlaceholderUpdateRelaunchBrowserTest() override {
notification_observation_.Reset();
}
// NotificationDisplayService::Observer:
MOCK_METHOD(void,
OnNotificationDisplayed,
(const message_center::Notification&,
const NotificationCommon::Metadata* const),
(override));
MOCK_METHOD(void,
OnNotificationClosed,
(const std::string& notification_id),
(override));
void OnNotificationDisplayServiceDestroyed(
NotificationDisplayService* service) override {
notification_observation_.Reset();
}
void AddForceInstalledApp(const std::string& manifest_id,
const std::string& app_name) {
base::test::TestFuture<void> app_sync_future;
provider()
->policy_manager()
.SetOnAppsSynchronizedCompletedCallbackForTesting(
app_sync_future.GetCallback());
PrefService* prefs = profile()->GetPrefs();
base::Value::List install_force_list =
prefs->GetList(prefs::kWebAppInstallForceList).Clone();
install_force_list.Append(
base::Value::Dict()
.Set(kUrlKey, manifest_id)
.Set(kDefaultLaunchContainerKey, kDefaultLaunchContainerWindowValue)
.Set(kFallbackAppNameKey, app_name));
profile()->GetPrefs()->SetList(prefs::kWebAppInstallForceList,
std::move(install_force_list));
EXPECT_TRUE(app_sync_future.Wait());
}
void AddPreventCloseToApp(const std::string& manifest_id,
const std::string& run_on_os_login) {
base::test::TestFuture<void> policy_refresh_sync_future;
provider()
->policy_manager()
.SetRefreshPolicySettingsCompletedCallbackForTesting(
policy_refresh_sync_future.GetCallback());
PrefService* prefs = profile()->GetPrefs();
base::Value::List web_app_settings =
prefs->GetList(prefs::kWebAppSettings).Clone();
web_app_settings.Append(base::Value::Dict()
.Set(kManifestId, manifest_id)
.Set(kRunOnOsLogin, run_on_os_login)
.Set(kPreventClose, true));
prefs->SetList(prefs::kWebAppSettings, std::move(web_app_settings));
EXPECT_TRUE(policy_refresh_sync_future.Wait());
}
void WaitForNumberOfAppInstances(const webapps::AppId& app_id,
size_t number_of_app_instances) {
ASSERT_TRUE(base::test::RunUntil([&]() -> bool {
return provider()->ui_manager().GetNumWindowsForApp(app_id) ==
number_of_app_instances;
}));
}
auto GetAllNotifications() {
base::test::TestFuture<std::set<std::string>, bool> get_displayed_future;
NotificationDisplayServiceFactory::GetForProfile(profile())->GetDisplayed(
get_displayed_future.GetCallback());
const auto& notification_ids = get_displayed_future.Get<0>();
EXPECT_TRUE(get_displayed_future.Wait());
return notification_ids;
}
size_t GetDisplayedNotificationsCount() {
return GetAllNotifications().size();
}
void WaitUntilDisplayNotificationCount(size_t display_count) {
ASSERT_TRUE(base::test::RunUntil([&]() -> bool {
return GetDisplayedNotificationsCount() == display_count;
}));
}
protected:
base::ScopedObservation<NotificationDisplayService,
PlaceholderUpdateRelaunchBrowserTest>
notification_observation_{this};
};
// TODO(b:341035409): Flaky.
IN_PROC_BROWSER_TEST_F(
PlaceholderUpdateRelaunchBrowserTest,
DISABLED_UpdatePlaceholderRelaunchClosePreventedAppSucceeds) {
notification_observation_.Observe(
NotificationDisplayServiceFactory::GetForProfile(profile()));
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&ExternallyManagedAppManagerBrowserTest::SimulateRedirectHandler,
base::Unretained(this)));
ASSERT_TRUE(embedded_test_server()->Start());
simulate_redirect_ = true;
GURL install_url = embedded_test_server()->GetURL(
"/banners/manifest_with_id_test_page.html");
// Force install the placeholder.
AddForceInstalledApp(install_url.spec(), /*app_name=*/"placeholder app");
const webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_url);
// Enable prevent-close close for the placeholder.
AddPreventCloseToApp(install_url.spec(), kRunWindowed);
std::optional<webapps::AppId> app_id =
registrar().LookupExternalAppId(install_url);
ASSERT_TRUE(app_id.has_value());
EXPECT_EQ(placeholder_app_id, *app_id);
EXPECT_TRUE(registrar().IsPlaceholderApp(*app_id, WebAppManagement::kPolicy));
EXPECT_CALL(
*this,
OnNotificationDisplayed(
AllOf(
Property(&message_center::Notification::id,
Eq("web_app_relaunch_notifier:" + placeholder_app_id)),
Property(&message_center::Notification::notifier_id,
Field(&message_center::NotifierId::id,
Eq("web_app_relaunch"))),
Property(&message_center::Notification::title,
Eq(u"Restarting and updating Manifest test app with id "
u"specified")),
Property(
&message_center::Notification::message,
Eq(u"Please wait while this application is being updated"))),
_))
.Times(1);
// Launch the PWA so that the app relaunch is triggered on sync.
ASSERT_TRUE(web_app::LaunchWebAppBrowser(profile(), placeholder_app_id,
WindowOpenDisposition::NEW_WINDOW));
WaitForNumberOfAppInstances(placeholder_app_id,
/*number_of_app_instances=*/1u);
// Resolve the redirect (placeholder can be updated now).
simulate_redirect_ = false;
provider()->policy_manager().RefreshPolicyInstalledAppsForTesting(
/*allow_close_and_relaunch=*/true);
// Wait until the final version of the app is installed.
const webapps::AppId final_app_id = GenerateAppId("some_id", install_url);
// Check that the placeholder app is indeed closed.
WaitForNumberOfAppInstances(placeholder_app_id,
/*number_of_app_instances=*/0u);
// Wait for the placeholder removal task to be done.
ASSERT_FALSE(base::test::RunUntil(
[&]() -> bool { return registrar().IsInRegistrar(placeholder_app_id); }));
// Check that the new app is launched.
WaitForNumberOfAppInstances(final_app_id, /*number_of_app_instances=*/1u);
// Make sure that the notification got cleaned up.
WaitUntilDisplayNotificationCount(/*display_count=*/0u);
EXPECT_NE(final_app_id, placeholder_app_id);
EXPECT_EQ(registrar().GetInstallState(final_app_id),
proto::InstallState::INSTALLED_WITH_OS_INTEGRATION);
EXPECT_FALSE(
registrar().IsPlaceholderApp(final_app_id, WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
#endif // BUILDFLAG(IS_CHROMEOS)
} // namespace web_app