#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/test/bind.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.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_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
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/message_center.mojom-test-utils.h"
#include "chromeos/crosapi/mojom/message_center.mojom.h"
#include "chromeos/crosapi/mojom/notification.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
using testing::_;
using testing::AllOf;
using testing::Eq;
using testing::Field;
using testing::Property;
#endif
namespace web_app {
class ExternallyManagedAppManagerBrowserTest : public WebAppBrowserTestBase { … };
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceeds) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceedsWithRedirect) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallSucceedsWithRedirectNoManifest) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithShortcuts) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsSameAppId) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsDifferentAppIdFomStartUrl) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
UpdatePlaceholderSucceedsDifferentAppIdFomManifestId) { … }
#if BUILDFLAG(IS_CHROMEOS)
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");
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());
}
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");
GURL app_url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
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().GetAppDownloadedIconSizesAny(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));
}
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;
}
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PlaceholderInstallSucceedsWithCustomIconAfterRetry) {
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");
GURL app_url(
embedded_test_server()->GetURL("/server-redirect?" + final_url.spec()));
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().GetAppDownloadedIconSizesAny(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
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
ShutdownWithPendingInstallation) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest, ForceReinstall) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
PolicyAppOverridesUserInstalledApp) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
InstallChromeURLFails) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RequireManifestFailsIfNoManifest) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationSucceeds) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationAlternateUrlSucceeds) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationSkipped) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
AlreadyRegistered) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
CannotFetchManifest) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
RegistrationTimeout) { … }
IN_PROC_BROWSER_TEST_F(ExternallyManagedAppManagerBrowserTest,
ReinstallPolicyAppWithLocallyInstalledApp) { … }
class ExternallyManagedAppManagerBrowserTestShortcut
: public ExternallyManagedAppManagerBrowserTest,
public testing::WithParamInterface<bool> { … };
IN_PROC_BROWSER_TEST_P(ExternallyManagedAppManagerBrowserTestShortcut,
InstallAsShortcut) { … }
INSTANTIATE_TEST_SUITE_P(…);
#if BUILDFLAG(IS_CHROMEOS)
class PlaceholderUpdateRelaunchBrowserTest
: public ExternallyManagedAppManagerBrowserTest,
public NotificationDisplayService::Observer {
public:
~PlaceholderUpdateRelaunchBrowserTest() override {
notification_observation_.Reset();
}
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() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
base::test::TestFuture<std::set<std::string>, bool> get_displayed_future;
NotificationDisplayService::GetForProfile(profile())->GetDisplayed(
get_displayed_future.GetCallback());
#else
base::test::TestFuture<const std::vector<std::string>&>
get_displayed_future;
auto& remote = chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::MessageCenter>();
EXPECT_TRUE(remote.get());
remote->GetDisplayedNotifications(get_displayed_future.GetCallback());
#endif
const auto& notification_ids = get_displayed_future.Get<0>();
EXPECT_TRUE(get_displayed_future.Wait());
return notification_ids;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
void ClearAllNotifications() {
base::test::TestFuture<const std::vector<std::string>&>
get_displayed_future;
auto& service = chromeos::LacrosService::Get()
->GetRemote<crosapi::mojom::MessageCenter>();
EXPECT_TRUE(service.get());
for (const std::string& notification_id : GetAllNotifications()) {
service->CloseNotification(notification_id);
}
}
#endif
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};
};
IN_PROC_BROWSER_TEST_F(
PlaceholderUpdateRelaunchBrowserTest,
DISABLED_UpdatePlaceholderRelaunchClosePreventedAppSucceeds) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
ClearAllNotifications();
WaitUntilDisplayNotificationCount(0u);
#endif
notification_observation_.Observe(
NotificationDisplayService::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");
AddForceInstalledApp(install_url.spec(), "placeholder app");
const webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_url);
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);
ASSERT_TRUE(web_app::LaunchWebAppBrowser(profile(), placeholder_app_id,
WindowOpenDisposition::NEW_WINDOW));
WaitForNumberOfAppInstances(placeholder_app_id,
1u);
simulate_redirect_ = false;
provider()->policy_manager().RefreshPolicyInstalledAppsForTesting(
true);
const webapps::AppId final_app_id = GenerateAppId("some_id", install_url);
WaitForNumberOfAppInstances(placeholder_app_id,
0u);
ASSERT_TRUE(base::test::RunUntil(
[&]() -> bool { return !registrar().IsInstalled(placeholder_app_id); }));
WaitForNumberOfAppInstances(final_app_id, 1u);
WaitUntilDisplayNotificationCount(0u);
EXPECT_NE(final_app_id, placeholder_app_id);
EXPECT_TRUE(registrar().IsInstalled(final_app_id));
EXPECT_FALSE(
registrar().IsPlaceholderApp(final_app_id, WebAppManagement::kPolicy));
EXPECT_EQ(0, registrar().CountUserInstalledApps());
EXPECT_EQ(1u, registrar()
.GetExternallyInstalledApps(
ExternalInstallSource::kExternalPolicy)
.size());
}
#endif
}