| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <cstddef> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include "base/check_deref.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/task_traits.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/test/values_test_util.h" |
| #include "base/test/with_feature_override.h" |
| #include "base/types/expected_macros.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.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/ui/webui/web_app_internals/web_app_internals.mojom.h" |
| #include "chrome/browser/ui/webui/web_app_internals/web_app_internals_handler.h" |
| #include "chrome/browser/ui/webui/web_app_internals/web_app_internals_ui.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/commands/isolated_web_app_install_command_helper.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_test_update_server.h" |
| #include "chrome/browser/web_applications/os_integration/os_integration_manager.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_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_install_manager.h" |
| #include "chrome/browser/web_applications/web_app_install_params.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_utils.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/isolated_web_apps/test_support/signing_keys.h" |
| #include "components/webapps/isolated_web_apps/types/update_channel.h" |
| #include "content/public/test/browser_test.h" |
| #include "extensions/browser/extension_dialog_auto_confirm.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::Eq; |
| using ::testing::Field; |
| using ::testing::HasSubstr; |
| using ::testing::Pointee; |
| |
| } // namespace |
| |
| class WebAppInternalsIwaInstallationBrowserTest |
| : public IsolatedWebAppBrowserTestHarness { |
| protected: |
| WebAppInternalsHandler* OpenWebAppInternals() { |
| EXPECT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), GURL("chrome://web-app-internals"))); |
| return static_cast<WebAppInternalsUI*>(browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetWebUI() |
| ->GetController()) |
| ->GetHandlerForTesting(); |
| } |
| |
| webapps::AppId InstallIwa(WebAppInternalsHandler* handler) { |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("1.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| GURL update_manifest_url = iwa_test_update_server_.GetUpdateManifestUrl( |
| test::GetDefaultEd25519WebBundleId()); |
| |
| base::test::TestFuture<::mojom::ParseUpdateManifestFromUrlResultPtr> |
| parse_manifest_future; |
| handler->ParseUpdateManifestFromUrl(update_manifest_url, |
| parse_manifest_future.GetCallback()); |
| |
| auto um_result = parse_manifest_future.Take(); |
| EXPECT_TRUE(um_result->is_update_manifest()); |
| |
| const auto& update_manifest = *um_result->get_update_manifest(); |
| const GURL& web_bundle_url = update_manifest.versions[0]->web_bundle_url; |
| |
| base::test::TestFuture<::mojom::InstallIsolatedWebAppResultPtr> |
| install_future; |
| auto params = ::mojom::InstallFromBundleUrlParams::New(); |
| params->web_bundle_url = web_bundle_url; |
| params->update_info = ::mojom::UpdateInfo::New( |
| update_manifest_url, UpdateChannel::default_channel().ToString(), |
| /*pinned_version=*/std::nullopt, /*allow_downgrades=*/false); |
| handler->InstallIsolatedWebAppFromBundleUrl(std::move(params), |
| install_future.GetCallback()); |
| EXPECT_TRUE(install_future.Take()->is_success()); |
| |
| return IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| test::GetDefaultEd25519WebBundleId()) |
| .app_id(); |
| } |
| |
| IsolatedWebAppTestUpdateServer iwa_test_update_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| InstallIsolatedWebAppFromBundleUrl) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("1.0.0")); |
| EXPECT_EQ(iwa.isolation_data()->update_manifest_url(), |
| iwa_test_update_server_.GetUpdateManifestUrl( |
| test::GetDefaultEd25519WebBundleId())); |
| EXPECT_EQ(iwa.isolation_data()->update_channel(), |
| UpdateChannel::default_channel()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| UpdateManifestInstalledIsolatedWebApp_AlreadyLatest) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), |
| HasSubstr("app is already on the latest version")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| UpdateManifestInstalledIsolatedWebApp_SuccessfulUpdate) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Add a new entry to the manifest and re-run the update check. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), HasSubstr("Update to v2.0.0 successful")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("2.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| SetUpdateChannelForIsolatedWebApp) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| auto beta_channel = *UpdateChannel::Create("beta"); |
| base::test::TestFuture<bool> set_channel_future; |
| handler->SetUpdateChannelForIsolatedWebApp(app_id, beta_channel.ToString(), |
| set_channel_future.GetCallback()); |
| EXPECT_TRUE(set_channel_future.Get()); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->update_channel(), beta_channel); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| WebAppInternalsIwaInstallationBrowserTest, |
| UpdateManifestInstalledIsolatedWebApp_WithUpdateChannel) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| auto beta_channel = *UpdateChannel::Create("beta"); |
| base::test::TestFuture<bool> set_channel_future; |
| handler->SetUpdateChannelForIsolatedWebApp(app_id, beta_channel.ToString(), |
| set_channel_future.GetCallback()); |
| ASSERT_TRUE(set_channel_future.Get()); |
| |
| // Add new entries with v2.1.0 for the `beta` channel and v2.2.0 for the |
| // `default` channel and force an update check. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.1.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair()), |
| /*update_channels=*/{{beta_channel}}); |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.2.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| // The update logic must pick up the v2.1.0 for `beta` instead of a higher |
| // v2.2.0 for `default`. |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), HasSubstr("Update to v2.1.0 successful")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("2.1.0")); |
| EXPECT_EQ(iwa.isolation_data()->update_channel(), beta_channel); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| UpdateManifestInstalledIsolatedWebApp_PinnedVersion) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Add v2.0.0, but pin the app to v1.0.0. Expect no update. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "1.0.0", set_pinned_version_future.GetCallback()); |
| EXPECT_TRUE(set_pinned_version_future.Get()); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT( |
| update_future.Get(), |
| HasSubstr("Update skipped: app is already on the latest version or the " |
| "updates are disabled due to set `pinned_version` field.")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| UpdateManifestInstalledIsolatedWebApp_ToPinnedVersion) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Add v2.0.0 and v2.1.0. Pin the app to v2.0.0. Expect update to v2.0.0. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.1.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "2.0.0", set_pinned_version_future.GetCallback()); |
| EXPECT_TRUE(set_pinned_version_future.Get()); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), HasSubstr("Update to v2.0.0 successful")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("2.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| ResetPinnedVersionForIsolatedWebApp) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Add v2.0.0, pin to v1.0.0, then unpin. App should be updated to v2.0.0. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "1.0.0", set_pinned_version_future.GetCallback()); |
| ASSERT_TRUE(set_pinned_version_future.Get()); |
| |
| handler->ResetPinnedVersionForIsolatedWebApp(app_id); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), HasSubstr("Update to v2.0.0 successful")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("2.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| SetAllowDowngradesForIsolatedWebApp) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Update to v2.0.0 first. |
| iwa_test_update_server_.AddBundle( |
| IsolatedWebAppBuilder(ManifestBuilder().SetVersion("2.0.0")) |
| .BuildBundle(test::GetDefaultEd25519KeyPair())); |
| { |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| ASSERT_THAT(update_future.Get(), HasSubstr("Update to v2.0.0 successful")); |
| } |
| |
| // Pin to v1.0.0, allow downgrades. Expect update to v1.0.0. |
| handler->SetAllowDowngradesForIsolatedWebApp(true, app_id); |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "1.0.0", set_pinned_version_future.GetCallback()); |
| EXPECT_TRUE(set_pinned_version_future.Get()); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), HasSubstr("Update to v1.0.0 successful")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("1.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| SetPinnedVersionForIsolatedWebApp_VersionNotFound) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Pin the app to an non-existent but valid version. |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "1.1.1", set_pinned_version_future.GetCallback()); |
| EXPECT_TRUE(set_pinned_version_future.Get()); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT( |
| update_future.Get(), |
| HasSubstr( |
| "Update failed: Error::kPinnedVersionNotFoundInUpdateManifest")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("1.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| SetPinnedVersionForIsolatedWebApp_InvalidVersion) { |
| auto* handler = OpenWebAppInternals(); |
| webapps::AppId app_id = InstallIwa(handler); |
| |
| // Fails to pin the app to invalid version. |
| base::test::TestFuture<bool> set_pinned_version_future; |
| handler->SetPinnedVersionForIsolatedWebApp( |
| app_id, "invalid_version", set_pinned_version_future.GetCallback()); |
| EXPECT_FALSE(set_pinned_version_future.Get()); |
| |
| base::test::TestFuture<std::string> update_future; |
| handler->UpdateManifestInstalledIsolatedWebApp( |
| app_id, update_future.GetCallback<const std::string&>()); |
| EXPECT_THAT(update_future.Get(), |
| HasSubstr("app is already on the latest version")); |
| |
| const WebApp& iwa = CHECK_DEREF(provider().registrar_unsafe().GetAppById( |
| app_id, WebAppFilter::IsIsolatedApp())); |
| EXPECT_EQ(iwa.isolation_data()->version(), *IwaVersion::Create("1.0.0")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| ParseUpdateManifestFromUrlFailsWithIncorrectUrl) { |
| auto* handler = OpenWebAppInternals(); |
| |
| base::test::TestFuture<::mojom::ParseUpdateManifestFromUrlResultPtr> |
| parse_manifest_future; |
| |
| // Select some dummy URL that certainly doesn't host an update manifest. |
| handler->ParseUpdateManifestFromUrl(GURL("https://example.com"), |
| parse_manifest_future.GetCallback()); |
| ASSERT_TRUE(parse_manifest_future.Take()->is_error()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| WebAppInternalsIwaInstallationBrowserTest, |
| InstallIsolatedWebAppFromBundleUrlFailsWithIncorrectUrl) { |
| auto* handler = OpenWebAppInternals(); |
| |
| base::test::TestFuture<::mojom::InstallIsolatedWebAppResultPtr> |
| install_future; |
| auto params = ::mojom::InstallFromBundleUrlParams::New(); |
| |
| // Select some dummy URL that certainly doesn't host a web bundle. |
| params->web_bundle_url = GURL("https://example.com"); |
| handler->InstallIsolatedWebAppFromBundleUrl(std::move(params), |
| install_future.GetCallback()); |
| ASSERT_TRUE(install_future.Take()->is_error()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| DeleteIsolatedWebApp) { |
| auto bundle = IsolatedWebAppBuilder(ManifestBuilder()).BuildBundle(); |
| ASSERT_OK_AND_ASSIGN(auto url_info, bundle->Install(profile())); |
| webapps::AppId app_id = url_info.app_id(); |
| |
| EXPECT_NE(provider().registrar_unsafe().GetAppById(app_id), nullptr); |
| |
| auto* handler = OpenWebAppInternals(); |
| |
| // Auto-accept the uninstall dialog. |
| extensions::ScopedTestDialogAutoConfirm auto_accept( |
| extensions::ScopedTestDialogAutoConfirm::ACCEPT); |
| |
| // Call the delete method on the handler. |
| base::test::TestFuture<bool> delete_future; |
| handler->DeleteIsolatedWebApp(app_id, delete_future.GetCallback()); |
| |
| // Verify the deletion was successful and the app is no longer registered. |
| EXPECT_TRUE(delete_future.Get()); |
| |
| EXPECT_EQ(provider().registrar_unsafe().GetAppById(app_id), nullptr); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebAppInternalsIwaInstallationBrowserTest, |
| DeleteIsolatedWebAppCancelled) { |
| auto bundle = IsolatedWebAppBuilder(ManifestBuilder()).BuildBundle(); |
| ASSERT_OK_AND_ASSIGN(auto url_info, bundle->Install(profile())); |
| webapps::AppId app_id = url_info.app_id(); |
| |
| EXPECT_NE(provider().registrar_unsafe().GetAppById(app_id), nullptr); |
| |
| auto* handler = OpenWebAppInternals(); |
| |
| // Auto-cancel the uninstall dialog. |
| extensions::ScopedTestDialogAutoConfirm auto_cancel( |
| extensions::ScopedTestDialogAutoConfirm::CANCEL); |
| |
| // Call the delete method on the handler. |
| base::test::TestFuture<bool> delete_future; |
| handler->DeleteIsolatedWebApp(app_id, delete_future.GetCallback()); |
| |
| // Verify the deletion was not successful because it was cancelled. |
| EXPECT_FALSE(delete_future.Get()); |
| |
| // Verify the app is still registered. |
| EXPECT_NE(provider().registrar_unsafe().GetAppById(app_id), nullptr); |
| } |
| |
| } // namespace web_app |