| // 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/isolated_web_apps/isolated_web_app_update_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <vector> |
| |
| #include "base/containers/to_vector.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/json/values_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/test/values_test_util.h" |
| #include "base/time/time.h" |
| #include "base/version.h" |
| #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h" |
| #include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.h" |
| #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_apply_update_command.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_source.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_trust_checker.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_constants.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/test/test_signed_web_bundle_builder.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_database_factory.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_provider.h" |
| #include "chrome/browser/web_applications/test/fake_web_app_ui_manager.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_observers.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_contents/web_contents_manager.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/url_constants.h" |
| #include "components/nacl/common/buildflags.h" |
| #include "components/web_package/signed_web_bundles/ed25519_public_key.h" |
| #include "components/web_package/signed_web_bundles/signed_web_bundle_id.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/common/content_features.h" |
| #include "net/http/http_status_code.h" |
| #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(ENABLE_NACL) |
| #include "chrome/browser/nacl_host/nacl_browser_delegate_impl.h" |
| #include "components/nacl/browser/nacl_browser.h" |
| #endif // BUILDFLAG(ENABLE_NACL) |
| |
| namespace web_app { |
| namespace { |
| |
| using base::ToVector; |
| using base::test::DictionaryHasValue; |
| using base::test::ValueIs; |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::Each; |
| using ::testing::ElementsAre; |
| using ::testing::Eq; |
| using ::testing::ExplainMatchResult; |
| using ::testing::Field; |
| using ::testing::Ge; |
| using ::testing::Invoke; |
| using ::testing::IsEmpty; |
| using ::testing::IsFalse; |
| using ::testing::IsTrue; |
| using ::testing::Le; |
| using ::testing::Ne; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Optional; |
| using ::testing::Property; |
| using ::testing::SizeIs; |
| using ::testing::VariantWith; |
| using ::testing::WithArg; |
| |
| blink::mojom::ManifestPtr CreateDefaultManifest(const GURL& application_url, |
| std::u16string_view short_name, |
| const base::Version& version) { |
| auto manifest = blink::mojom::Manifest::New(); |
| manifest->id = application_url.DeprecatedGetOriginAsURL(); |
| manifest->scope = application_url.Resolve("/"); |
| manifest->start_url = application_url.Resolve("/testing-start-url.html"); |
| manifest->display = DisplayMode::kStandalone; |
| manifest->short_name = short_name; |
| manifest->version = base::UTF8ToUTF16(version.GetString()); |
| |
| return manifest; |
| } |
| |
| MATCHER_P(IsDict, dict_matcher, "") { |
| return ExplainMatchResult( |
| Property("GetDict", &base::Value::GetDict, dict_matcher), arg, |
| result_listener); |
| } |
| |
| class MockCommandScheduler : public WebAppCommandScheduler { |
| public: |
| using WebAppCommandScheduler::WebAppCommandScheduler; |
| |
| MOCK_METHOD( |
| void, |
| ApplyPendingIsolatedWebAppUpdate, |
| (const IsolatedWebAppUrlInfo& url_info, |
| std::unique_ptr<ScopedKeepAlive> optional_keep_alive, |
| std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, |
| base::OnceCallback< |
| void(base::expected<void, IsolatedWebAppApplyUpdateCommandError>)> |
| callback, |
| const base::Location& call_location), |
| (override)); |
| |
| void DelegateToRealImpl() { |
| ON_CALL(*this, ApplyPendingIsolatedWebAppUpdate) |
| .WillByDefault( |
| [this]( |
| const IsolatedWebAppUrlInfo& url_info, |
| std::unique_ptr<ScopedKeepAlive> optional_keep_alive, |
| std::unique_ptr<ScopedProfileKeepAlive> |
| optional_profile_keep_alive, |
| base::OnceCallback<void( |
| base::expected< |
| void, IsolatedWebAppApplyUpdateCommandError>)> callback, |
| const base::Location& call_location) { |
| return this |
| ->WebAppCommandScheduler::ApplyPendingIsolatedWebAppUpdate( |
| url_info, std::move(optional_keep_alive), |
| std::move(optional_profile_keep_alive), |
| std::move(callback), call_location); |
| }); |
| } |
| }; |
| |
| #if BUILDFLAG(ENABLE_NACL) |
| class ScopedNaClBrowserDelegate { |
| public: |
| ~ScopedNaClBrowserDelegate() { nacl::NaClBrowser::ClearAndDeleteDelegate(); } |
| |
| void Init(ProfileManager* profile_manager) { |
| nacl::NaClBrowser::SetDelegate( |
| std::make_unique<NaClBrowserDelegateImpl>(profile_manager)); |
| } |
| }; |
| #endif // BUILDFLAG(ENABLE_NACL) |
| |
| // TODO(b/304691179): Rely less on `RunUntilIdle` and more on concrete events in |
| // all of these tests. |
| class IsolatedWebAppUpdateManagerTest : public WebAppTest { |
| public: |
| explicit IsolatedWebAppUpdateManagerTest( |
| const base::flat_map<base::test::FeatureRef, bool>& feature_states = |
| {{features::kIsolatedWebApps, true}}, |
| base::test::TaskEnvironment::TimeSource time_source = |
| base::test::TaskEnvironment::TimeSource::DEFAULT) |
| : WebAppTest(WebAppTest::WithTestUrlLoaderFactory(), time_source) { |
| scoped_feature_list_.InitWithFeatureStates(feature_states); |
| } |
| |
| void SetUp() override { |
| WebAppTest::SetUp(); |
| #if BUILDFLAG(ENABLE_NACL) |
| // Clearing Cache will clear PNACL cache, which needs this delegate set. |
| nacl_browser_delegate_.Init(profile_manager().profile_manager()); |
| #endif // BUILDFLAG(ENABLE_NACL) |
| } |
| |
| void TearDown() override { |
| if (task_environment()->UsesMockTime()) { |
| // TODO(b/299074540): Without this line, subsequent tests are unable to |
| // use `test::UninstallWebApp`, which will hang forever. This has |
| // something to do with the combination of `MOCK_TIME` and NaCl, because |
| // the code ends up hanging forever in |
| // `PnaclTranslationCache::DoomEntriesBetween`. A simple `FastForwardBy` |
| // here seems to alleviate this issue. |
| task_environment()->FastForwardBy(TestTimeouts::tiny_timeout()); |
| } |
| |
| WebAppTest::TearDown(); |
| } |
| |
| protected: |
| IsolatedWebAppUpdateManager& update_manager() { |
| return fake_provider().iwa_update_manager(); |
| } |
| |
| FakeWebContentsManager& fake_web_contents_manager() { |
| return static_cast<FakeWebContentsManager&>( |
| fake_provider().web_contents_manager()); |
| } |
| |
| FakeWebAppUiManager& fake_ui_manager() { |
| return static_cast<FakeWebAppUiManager&>(fake_provider().ui_manager()); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| data_decoder::test::InProcessDataDecoder data_decoder_; |
| #if BUILDFLAG(ENABLE_NACL) |
| ScopedNaClBrowserDelegate nacl_browser_delegate_; |
| #endif // BUILDFLAG(ENABLE_NACL) |
| }; |
| |
| class IsolatedWebAppUpdateManagerDevModeUpdateTest |
| : public IsolatedWebAppUpdateManagerTest { |
| public: |
| IsolatedWebAppUpdateManagerDevModeUpdateTest() |
| : IsolatedWebAppUpdateManagerTest( |
| {{features::kIsolatedWebApps, true}, |
| {features::kIsolatedWebAppDevMode, true}}) {} |
| |
| void SetUp() override { |
| IsolatedWebAppUpdateManagerTest::SetUp(); |
| |
| fake_provider().SetEnableAutomaticIwaUpdates( |
| FakeWebAppProvider::AutomaticIwaUpdateStrategy::kForceEnabled); |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| |
| void CreateInstallPageState(const IsolatedWebAppUrlInfo& url_info) { |
| GURL install_url = url_info.origin().GetURL().Resolve( |
| "/.well-known/_generated_install_page.html"); |
| auto& page_state = |
| fake_web_contents_manager().GetOrCreatePageState(install_url); |
| page_state.url_load_result = webapps::WebAppUrlLoaderResult::kUrlLoaded; |
| page_state.error_code = webapps::InstallableStatusCode::NO_ERROR_DETECTED; |
| page_state.manifest_url = |
| url_info.origin().GetURL().Resolve("manifest.webmanifest"); |
| page_state.valid_manifest_for_web_app = true; |
| page_state.manifest_before_default_processing = CreateDefaultManifest( |
| url_info.origin().GetURL(), u"updated iwa", base::Version("2.0.0")); |
| } |
| |
| protected: |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| TEST_F(IsolatedWebAppUpdateManagerDevModeUpdateTest, |
| DiscoversLocalDevModeUpdate) { |
| auto key_pair = web_package::WebBundleSigner::KeyPair::CreateRandom(); |
| TestSignedWebBundle update_bundle = TestSignedWebBundleBuilder::BuildDefault( |
| TestSignedWebBundleBuilder::BuildOptions() |
| .SetVersion(base::Version("2.0.0")) |
| .SetAppName("updated iwa") |
| .SetKeyPair(key_pair)); |
| |
| ASSERT_THAT(temp_dir_.CreateUniqueTempDir(), IsTrue()); |
| base::FilePath path = temp_dir_.GetPath().AppendASCII("bundle.swbn"); |
| base::WriteFile(path, update_bundle.data); |
| IwaStorageUnownedBundle location{path}; |
| |
| IsolatedWebAppUrlInfo url_info = |
| IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| web_package::SignedWebBundleId::CreateForEd25519PublicKey( |
| key_pair.public_key)); |
| |
| AddDummyIsolatedAppToRegistry( |
| profile(), url_info.origin().GetURL(), "installed iwa 1 (unowned bundle)", |
| WebApp::IsolationData(IwaStorageUnownedBundle{base::FilePath()}, |
| base::Version("1.0.0"))); |
| |
| CreateInstallPageState(url_info); |
| |
| base::test::TestFuture<base::expected<base::Version, std::string>> future; |
| fake_provider() |
| .iwa_update_manager() |
| .DiscoverApplyAndPrioritizeLocalDevModeUpdate( |
| IwaSourceBundleDevModeWithFileOp(path, kDefaultBundleDevFileOp), |
| url_info, future.GetCallback()); |
| |
| EXPECT_THAT(future.Get(), ValueIs(Eq(base::Version("2.0.0")))); |
| EXPECT_THAT(fake_provider().registrar_unsafe().GetAppById(url_info.app_id()), |
| test::IwaIs(Eq("updated iwa"), |
| test::IsolationDataIs( |
| location, Eq(base::Version("2.0.0")), |
| /*controlled_frame_partitions=*/_, |
| /*pending_update_info=*/Eq(std::nullopt)))); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| class IsolatedWebAppUpdateManagerUpdateTest |
| : public IsolatedWebAppUpdateManagerTest { |
| public: |
| explicit IsolatedWebAppUpdateManagerUpdateTest( |
| base::test::TaskEnvironment::TimeSource time_source = |
| base::test::TaskEnvironment::TimeSource::DEFAULT) |
| : IsolatedWebAppUpdateManagerTest({{features::kIsolatedWebApps, true}}, |
| time_source) {} |
| |
| protected: |
| struct IwaInfo { |
| IwaInfo(web_package::WebBundleSigner::KeyPair key_pair, |
| IsolatedWebAppStorageLocation installed_location, |
| base::Version installed_version, |
| GURL update_manifest_url, |
| GURL update_bundle_url, |
| base::Version update_version, |
| std::string update_app_name) |
| : url_info(IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| web_package::SignedWebBundleId::CreateForEd25519PublicKey( |
| key_pair.public_key))), |
| key_pair(std::move(key_pair)), |
| installed_location(std::move(installed_location)), |
| installed_version(std::move(installed_version)), |
| update_manifest_url(std::move(update_manifest_url)), |
| update_bundle_url(std::move(update_bundle_url)), |
| update_version(std::move(update_version)), |
| update_app_name(std::move(update_app_name)) {} |
| |
| IsolatedWebAppUrlInfo url_info; |
| web_package::WebBundleSigner::KeyPair key_pair; |
| IsolatedWebAppStorageLocation installed_location; |
| base::Version installed_version; |
| |
| GURL update_manifest_url; |
| GURL update_bundle_url; |
| base::Version update_version; |
| std::string update_app_name; |
| }; |
| |
| void SetUp() override { |
| IsolatedWebAppUpdateManagerTest::SetUp(); |
| fake_provider().SetEnableAutomaticIwaUpdates( |
| FakeWebAppProvider::AutomaticIwaUpdateStrategy::kForceEnabled); |
| |
| auto command_scheduler = |
| std::make_unique<NiceMock<MockCommandScheduler>>(*profile()); |
| command_scheduler->DelegateToRealImpl(); |
| fake_provider().SetScheduler(std::move(command_scheduler)); |
| |
| iwa_info1_ = IwaInfo(web_package::WebBundleSigner::KeyPair::CreateRandom(), |
| IwaStorageOwnedBundle{"iwa1", /*dev_mode=*/false}, |
| base::Version("1.0.0"), |
| GURL("https://example.com/update_manifest1.json"), |
| GURL("https://example.com/bundle1.swbn"), |
| base::Version("2.0.0"), "updated app 1"); |
| SetUpIwaInfo(*iwa_info1_); |
| |
| iwa_info2_ = IwaInfo(web_package::WebBundleSigner::KeyPair::CreateRandom(), |
| IwaStorageOwnedBundle{"iwa2", /*dev_mode=*/false}, |
| base::Version("4.0.0"), |
| GURL("https://example.com/update_manifest2.json"), |
| GURL("https://example.com/bundle2.swbn"), |
| base::Version("7.0.0"), "updated app 2"); |
| SetUpIwaInfo(*iwa_info2_); |
| |
| SetTrustedWebBundleIdsForTesting({iwa_info1_->url_info.web_bundle_id(), |
| iwa_info2_->url_info.web_bundle_id()}); |
| |
| SeedWebAppDatabase(); |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| |
| void SetUpIwaInfo(const IwaInfo& iwa_info) { |
| TestSignedWebBundle update_bundle = |
| CreateBundle(iwa_info.update_version, iwa_info.key_pair); |
| |
| profile_url_loader_factory().AddResponse( |
| iwa_info.update_manifest_url.spec(), |
| base::ReplaceStringPlaceholders(R"( |
| { "versions": [ { "src": "$1", "version": "$2" } ] } |
| )", |
| {iwa_info.update_bundle_url.spec(), |
| iwa_info.update_version.GetString()}, |
| /*offsets=*/nullptr)); |
| profile_url_loader_factory().AddResponse( |
| iwa_info.update_bundle_url.spec(), |
| std::string(update_bundle.data.begin(), update_bundle.data.end())); |
| |
| GURL install_url = iwa_info.url_info.origin().GetURL().Resolve( |
| "/.well-known/_generated_install_page.html"); |
| |
| auto& page_state = |
| fake_web_contents_manager().GetOrCreatePageState(install_url); |
| page_state.url_load_result = webapps::WebAppUrlLoaderResult::kUrlLoaded; |
| page_state.error_code = webapps::InstallableStatusCode::NO_ERROR_DETECTED; |
| page_state.manifest_url = |
| iwa_info.url_info.origin().GetURL().Resolve("manifest.webmanifest"); |
| page_state.valid_manifest_for_web_app = true; |
| page_state.manifest_before_default_processing = CreateDefaultManifest( |
| iwa_info.url_info.origin().GetURL(), |
| base::UTF8ToUTF16(iwa_info.update_app_name), iwa_info.update_version); |
| } |
| |
| TestSignedWebBundle CreateBundle( |
| const base::Version& version, |
| const web_package::WebBundleSigner::KeyPair& key_pair) const { |
| return TestSignedWebBundleBuilder::BuildDefault( |
| TestSignedWebBundleBuilder::BuildOptions() |
| .SetVersion(version) |
| .SetKeyPair(key_pair)); |
| } |
| |
| virtual void SeedWebAppDatabase() {} |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| void SetIwaForceInstallPolicy( |
| std::vector<std::pair<IsolatedWebAppUrlInfo, std::string_view>> entries) { |
| base::Value::List list; |
| for (const auto& [url_info, update_manifest_url] : entries) { |
| list.Append(base::Value::Dict() |
| .Set(kPolicyWebBundleIdKey, url_info.web_bundle_id().id()) |
| .Set(kPolicyUpdateManifestUrlKey, update_manifest_url)); |
| } |
| profile()->GetPrefs()->SetList(prefs::kIsolatedWebAppInstallForceList, |
| std::move(list)); |
| } |
| |
| // TODO(crbug.com/298005569): This should eventually go away and instead rely |
| // on the `IsolatedWebAppPolicyManager` to perform the uninstall by setting |
| // `SetIwaForceInstallPolicy` appropriately. |
| [[nodiscard]] webapps::UninstallResultCode UninstallPolicyInstalledIwa( |
| const webapps::AppId& app_id) { |
| base::test::TestFuture<webapps::UninstallResultCode> future; |
| fake_provider().scheduler().RemoveInstallManagementMaybeUninstall( |
| app_id, WebAppManagement::Type::kIwaPolicy, |
| webapps::WebappUninstallSource::kIwaEnterprisePolicy, |
| future.GetCallback()); |
| return future.Take(); |
| } |
| #endif |
| |
| NiceMock<MockCommandScheduler>& mock_command_scheduler() { |
| return static_cast<NiceMock<MockCommandScheduler>&>( |
| fake_provider().scheduler()); |
| } |
| |
| base::Value debug_log() { |
| return fake_provider().iwa_update_manager().AsDebugValue(); |
| } |
| |
| base::Value::List UpdateDiscoveryLog() { |
| return debug_log() |
| .GetDict() |
| .FindDict("task_queue") |
| ->FindList("update_discovery_log") |
| ->Clone(); |
| } |
| |
| base::Value::List UpdateDiscoveryTasks() { |
| return debug_log() |
| .GetDict() |
| .FindDict("task_queue") |
| ->FindList("update_discovery_tasks") |
| ->Clone(); |
| } |
| |
| base::Value::List UpdateApplyLog() { |
| return debug_log() |
| .GetDict() |
| .FindDict("task_queue") |
| ->FindList("update_apply_log") |
| ->Clone(); |
| } |
| |
| base::Value::List UpdateApplyTasks() { |
| return debug_log() |
| .GetDict() |
| .FindDict("task_queue") |
| ->FindList("update_apply_tasks") |
| ->Clone(); |
| } |
| |
| base::Value::List UpdateApplyWaiters() { |
| return debug_log().GetDict().FindList("update_apply_waiters")->Clone(); |
| } |
| |
| auto UpdateLocationMatcher(Profile* profile) { |
| return Property("variant", &IsolatedWebAppStorageLocation::variant, |
| VariantWith<IwaStorageOwnedBundle>( |
| test::OwnedIwaBundleExists(profile->GetPath()))); |
| } |
| |
| std::optional<IwaInfo> iwa_info1_; |
| std::optional<IwaInfo> iwa_info2_; |
| }; |
| |
| class IsolatedWebAppUpdateManagerUpdateMockTimeTest |
| : public IsolatedWebAppUpdateManagerUpdateTest { |
| public: |
| IsolatedWebAppUpdateManagerUpdateMockTimeTest() |
| : IsolatedWebAppUpdateManagerUpdateTest( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| }; |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| TEST_F(IsolatedWebAppUpdateManagerUpdateMockTimeTest, |
| DiscoversAndPreparesUpdateOfPolicyInstalledApps) { |
| IsolatedWebAppUrlInfo non_installed_url_info = |
| IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| *web_package::SignedWebBundleId::Create( |
| "5tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic")); |
| IsolatedWebAppUrlInfo dev_bundle_url_info = |
| IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| *web_package::SignedWebBundleId::Create( |
| "aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic")); |
| IsolatedWebAppUrlInfo dev_proxy_url_info = |
| IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId( |
| web_package::SignedWebBundleId::CreateRandomForDevelopment()); |
| |
| test::InstallDummyWebApp(profile(), "non-iwa", GURL("https://a")); |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed iwa 1", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), dev_proxy_url_info.origin().GetURL(), |
| "installed iwa 2 (dev mode proxy)", |
| WebApp::IsolationData(IwaStorageProxy{dev_proxy_url_info.origin()}, |
| base::Version("1.0.0")), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), dev_bundle_url_info.origin().GetURL(), |
| "installed iwa 3 (unowned bundle)", |
| WebApp::IsolationData(IwaStorageUnownedBundle{base::FilePath()}, |
| base::Version("1.0.0")), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), GURL("isolated-app://b"), "installed iwa 4", |
| WebApp::IsolationData( |
| IwaStorageOwnedBundle{/*dir_name_ascii=*/"", /*dev_mode=*/false}, |
| base::Version("1.0.0")), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 1); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}, |
| {non_installed_url_info, "https://example.com/update_manifest.json"}, |
| {dev_bundle_url_info, "https://example.com/update_manifest.json"}, |
| {dev_proxy_url_info, "https://example.com/update_manifest.json"}}); |
| |
| task_environment()->FastForwardBy( |
| *update_manager().GetNextUpdateDiscoveryTimeForTesting() - |
| base::TimeTicks::Now()); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT( |
| fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(Eq("installed iwa 1"), |
| test::IsolationDataIs(Eq(iwa_info1_->installed_location), |
| Eq(iwa_info1_->installed_version), |
| /*controlled_frame_partitions=*/_, |
| test::PendingUpdateInfoIs( |
| UpdateLocationMatcher(profile()), |
| Eq(base::Version("2.0.0")))))); |
| |
| EXPECT_THAT( |
| UpdateDiscoveryLog(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "result", base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateMockTimeTest, DiscoverUpdatesNow) { |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed iwa 1", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 1); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}}); |
| |
| // After one hour, the update should not yet have run, but still be scheduled |
| // (i.e. containing a value in the `std::optional`). |
| task_environment()->FastForwardBy(base::Hours(1)); |
| auto old_update_discovery_time = |
| update_manager().GetNextUpdateDiscoveryTimeForTesting(); |
| EXPECT_THAT(old_update_discovery_time.has_value(), IsTrue()); |
| |
| // Once we manually trigger update discovery, the update discovery timer |
| // should reset to a different time in the future. |
| EXPECT_THAT(update_manager().DiscoverUpdatesNow(), Eq(1ul)); |
| EXPECT_THAT(update_manager().GetNextUpdateDiscoveryTimeForTesting(), |
| AllOf(Ne(old_update_discovery_time), Ge(base::TimeTicks::Now()))); |
| |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT( |
| fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(Eq("installed iwa 1"), |
| test::IsolationDataIs(Eq(iwa_info1_->installed_location), |
| Eq(iwa_info1_->installed_version), |
| /*controlled_frame_partitions=*/_, |
| test::PendingUpdateInfoIs( |
| UpdateLocationMatcher(profile()), |
| Eq(base::Version("2.0.0")))))); |
| |
| EXPECT_THAT( |
| UpdateDiscoveryLog(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "result", base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateTest, |
| ApplysUpdatesAfterWindowIsClosed) { |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed app", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 1); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}}); |
| update_manager().DiscoverUpdatesNow(); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT( |
| fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(Eq("installed app"), |
| test::IsolationDataIs(Eq(iwa_info1_->installed_location), |
| Eq(iwa_info1_->installed_version), |
| /*controlled_frame_partitions=*/_, |
| test::PendingUpdateInfoIs( |
| UpdateLocationMatcher(profile()), |
| Eq(iwa_info1_->update_version))))); |
| |
| EXPECT_THAT( |
| UpdateDiscoveryLog(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "result", base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 0); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT(UpdateApplyLog(), UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "result", base::Value("Success"))))); |
| |
| EXPECT_THAT(fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(iwa_info1_->update_app_name, |
| test::IsolationDataIs( |
| UpdateLocationMatcher(profile()), |
| Eq(iwa_info1_->update_version), |
| /*controlled_frame_partitions=*/_, |
| /*pending_update_info=*/Eq(std::nullopt)))); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateTest, |
| ApplysUpdatesWithHigherPriorityThanUpdateDiscovery) { |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed app 1", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info2_->url_info.origin().GetURL(), "installed app 2", |
| WebApp::IsolationData(iwa_info2_->installed_location, |
| iwa_info2_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}, |
| {iwa_info2_->url_info, iwa_info2_->update_manifest_url.spec()}}); |
| update_manager().DiscoverUpdatesNow(); |
| task_environment()->RunUntilIdle(); |
| |
| { |
| auto update_discovery_log = UpdateDiscoveryLog(); |
| auto update_apply_log = UpdateApplyLog(); |
| |
| EXPECT_THAT( |
| update_discovery_log, |
| UnorderedElementsAre( |
| IsDict(DictionaryHasValue( |
| "result", |
| base::Value("Success::kUpdateFoundAndDryRunSuccessful"))), |
| IsDict(DictionaryHasValue( |
| "result", |
| base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| |
| EXPECT_THAT( |
| update_apply_log, |
| UnorderedElementsAre( |
| IsDict(DictionaryHasValue("result", base::Value("Success"))), |
| IsDict(DictionaryHasValue("result", base::Value("Success"))))); |
| |
| std::vector<base::Value*> times( |
| {update_discovery_log[0].GetDict().Find("start_time"), |
| update_discovery_log[0].GetDict().Find("end_time"), |
| update_apply_log[0].GetDict().Find("start_time"), |
| update_apply_log[0].GetDict().Find("end_time"), |
| |
| update_discovery_log[1].GetDict().Find("start_time"), |
| update_discovery_log[1].GetDict().Find("end_time"), |
| update_apply_log[1].GetDict().Find("start_time"), |
| update_apply_log[1].GetDict().Find("end_time")}); |
| EXPECT_THAT(base::ranges::is_sorted(times, {}, |
| [](base::Value* value) { |
| return *base::ValueToTime(value); |
| }), |
| IsTrue()) |
| << base::JoinString(ToVector(times, &base::Value::DebugString), ""); |
| } |
| |
| EXPECT_THAT(fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(iwa_info1_->update_app_name, |
| test::IsolationDataIs( |
| UpdateLocationMatcher(profile()), |
| Eq(iwa_info1_->update_version), |
| /*controlled_frame_partitions=*/_, |
| /*pending_update_info=*/Eq(std::nullopt)))); |
| EXPECT_THAT(fake_provider().registrar_unsafe().GetAppById( |
| iwa_info2_->url_info.app_id()), |
| test::IwaIs(iwa_info2_->update_app_name, |
| test::IsolationDataIs( |
| UpdateLocationMatcher(profile()), |
| Eq(iwa_info2_->update_version), |
| /*controlled_frame_partitions=*/_, |
| /*pending_update_info=*/Eq(std::nullopt)))); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateTest, |
| StopsNonStartedUpdateDiscoveryTasksIfIwaIsUninstalled) { |
| profile_url_loader_factory().ClearResponses(); |
| |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed app 1", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info2_->url_info.origin().GetURL(), "installed app 2", |
| WebApp::IsolationData(iwa_info2_->installed_location, |
| iwa_info2_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}, |
| {iwa_info2_->url_info, iwa_info2_->update_manifest_url.spec()}}); |
| update_manager().DiscoverUpdatesNow(); |
| task_environment()->RunUntilIdle(); |
| |
| // Wait for the update discovery task of either app 1 or app 2 to request the |
| // update manifest (which task starts first is undefined). |
| ASSERT_THAT(profile_url_loader_factory().NumPending(), Eq(1)); |
| EXPECT_THAT(UpdateDiscoveryTasks(), SizeIs(2)); // two tasks should be queued |
| EXPECT_THAT(UpdateDiscoveryLog(), IsEmpty()); // no task should have finished |
| |
| // Uninstall the other IWA whose update discovery task has not yet started. |
| GURL pending_url = |
| profile_url_loader_factory().GetPendingRequest(0)->request.url; |
| IwaInfo* iwa_to_keep; |
| IwaInfo* iwa_to_uninstall; |
| if (pending_url == iwa_info1_->update_manifest_url) { |
| iwa_to_keep = &*iwa_info1_; |
| iwa_to_uninstall = &*iwa_info2_; |
| } else if (pending_url == iwa_info2_->update_manifest_url) { |
| iwa_to_keep = &*iwa_info2_; |
| iwa_to_uninstall = &*iwa_info1_; |
| } else { |
| FAIL() << "Unexpected pending request for: " << pending_url; |
| } |
| |
| EXPECT_THAT(UninstallPolicyInstalledIwa(iwa_to_uninstall->url_info.app_id()), |
| Eq(webapps::UninstallResultCode::kSuccess)); |
| |
| EXPECT_THAT(UpdateDiscoveryTasks(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "app_id", base::Value(iwa_to_keep->url_info.app_id()))))); |
| EXPECT_THAT(UpdateDiscoveryLog(), IsEmpty()); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateTest, StopsWaitingIfIwaIsUninstalled) { |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed app", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 1); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}}); |
| update_manager().DiscoverUpdatesNow(); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT( |
| UpdateDiscoveryLog(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "result", base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| EXPECT_THAT(UpdateApplyWaiters(), |
| UnorderedElementsAre(IsDict(DictionaryHasValue( |
| "app_id", base::Value(iwa_info1_->url_info.app_id()))))); |
| |
| EXPECT_THAT(UninstallPolicyInstalledIwa(iwa_info1_->url_info.app_id()), |
| Eq(webapps::UninstallResultCode::kSuccess)); |
| |
| EXPECT_THAT(UpdateApplyWaiters(), IsEmpty()); |
| EXPECT_THAT(UpdateApplyTasks(), IsEmpty()); |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateTest, |
| StopsNonStartedUpdateApplyTasksIfIwaIsUninstalled) { |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info1_->url_info.origin().GetURL(), "installed app 1", |
| WebApp::IsolationData(iwa_info1_->installed_location, |
| iwa_info1_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| AddDummyIsolatedAppToRegistry( |
| profile(), iwa_info2_->url_info.origin().GetURL(), "installed app 2", |
| WebApp::IsolationData(iwa_info2_->installed_location, |
| iwa_info2_->installed_version), |
| webapps::WebappInstallSource::IWA_EXTERNAL_POLICY); |
| |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 1); |
| fake_ui_manager().SetNumWindowsForApp(iwa_info2_->url_info.app_id(), 1); |
| |
| SetIwaForceInstallPolicy( |
| {{iwa_info1_->url_info, iwa_info1_->update_manifest_url.spec()}, |
| {iwa_info2_->url_info, iwa_info2_->update_manifest_url.spec()}}); |
| update_manager().DiscoverUpdatesNow(); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_THAT( |
| UpdateDiscoveryLog(), |
| UnorderedElementsAre( |
| IsDict(DictionaryHasValue( |
| "result", |
| base::Value("Success::kUpdateFoundAndDryRunSuccessful"))), |
| IsDict(DictionaryHasValue( |
| "result", |
| base::Value("Success::kUpdateFoundAndDryRunSuccessful"))))); |
| EXPECT_THAT(UpdateApplyWaiters(), |
| UnorderedElementsAre( |
| IsDict(DictionaryHasValue( |
| "app_id", base::Value(iwa_info1_->url_info.app_id()))), |
| IsDict(DictionaryHasValue( |
| "app_id", base::Value(iwa_info2_->url_info.app_id()))))); |
| |
| // Wait for the update apply task of either app 1 or app 2 to start. |
| base::test::TestFuture<IsolatedWebAppUrlInfo> future; |
| EXPECT_CALL(mock_command_scheduler(), |
| ApplyPendingIsolatedWebAppUpdate(_, _, _, _, _)) |
| .WillOnce(WithArg<0>(Invoke( |
| &future, &base::test::TestFuture<IsolatedWebAppUrlInfo>::SetValue))); |
| fake_ui_manager().SetNumWindowsForApp(iwa_info1_->url_info.app_id(), 0); |
| fake_ui_manager().SetNumWindowsForApp(iwa_info2_->url_info.app_id(), 0); |
| webapps::AppId iwa_to_keep = future.Take().app_id(); |
| |
| EXPECT_THAT(UpdateApplyTasks(), SizeIs(2)); // two tasks should be queued |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); // no task should have finished |
| |
| // Uninstall the other IWA whose update apply task has not yet started. |
| IwaInfo* iwa_to_uninstall; |
| if (iwa_to_keep == iwa_info1_->url_info.app_id()) { |
| iwa_to_uninstall = &*iwa_info2_; |
| } else if (iwa_to_keep == iwa_info2_->url_info.app_id()) { |
| iwa_to_uninstall = &*iwa_info1_; |
| } else { |
| FAIL() << "Unexpected IWA app id: " << iwa_to_keep; |
| } |
| |
| EXPECT_THAT(UninstallPolicyInstalledIwa(iwa_to_uninstall->url_info.app_id()), |
| Eq(webapps::UninstallResultCode::kSuccess)); |
| |
| EXPECT_THAT(UpdateApplyTasks(), |
| UnorderedElementsAre(IsDict( |
| DictionaryHasValue("app_id", base::Value(iwa_to_keep))))); |
| EXPECT_THAT(UpdateApplyLog(), IsEmpty()); |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| class IsolatedWebAppUpdateManagerUpdateApplyOnStartupTest |
| : public IsolatedWebAppUpdateManagerUpdateTest { |
| protected: |
| void SeedWebAppDatabase() override { |
| // Seed the `WebAppProvider` with an IWA before it is started. |
| EXPECT_THAT(fake_provider().is_registry_ready(), IsFalse()); |
| |
| base::FilePath path = update_location_.GetPath(profile()->GetPath()); |
| EXPECT_THAT(base::CreateDirectory(path.DirName()), IsTrue()); |
| |
| auto update_bundle = |
| CreateBundle(iwa_info1_->update_version, iwa_info1_->key_pair); |
| EXPECT_THAT(base::WriteFile(path, update_bundle.data), IsTrue()); |
| |
| std::unique_ptr<WebApp> iwa = CreateIsolatedWebApp( |
| iwa_info1_->url_info.origin().GetURL(), |
| WebApp::IsolationData( |
| iwa_info1_->installed_location, iwa_info1_->installed_version, {}, |
| WebApp::IsolationData::PendingUpdateInfo( |
| update_location_, iwa_info1_->update_version))); |
| CreateStoragePartition(iwa_info1_->url_info); |
| |
| Registry registry; |
| registry.emplace(iwa->app_id(), std::move(iwa)); |
| auto& database_factory = static_cast<FakeWebAppDatabaseFactory&>( |
| fake_provider().database_factory()); |
| database_factory.WriteRegistry(registry); |
| } |
| |
| IwaStorageOwnedBundle update_location_{"update_folder", /*dev_mode=*/false}; |
| |
| private: |
| void CreateStoragePartition(IsolatedWebAppUrlInfo& url_info) { |
| content::StoragePartition* new_storage_partition = |
| profile()->GetStoragePartition( |
| url_info.storage_partition_config(profile()), |
| /*can_create=*/true); |
| EXPECT_THAT(new_storage_partition, NotNull()); |
| } |
| |
| std::unique_ptr<WebApp> CreateIsolatedWebApp( |
| const GURL& start_url, |
| WebApp::IsolationData isolation_data) { |
| webapps::AppId app_id = GenerateAppId(/*manifest_id=*/"", start_url); |
| auto web_app = std::make_unique<WebApp>(app_id); |
| web_app->SetName("iwa name"); |
| web_app->SetStartUrl(start_url); |
| web_app->SetScope(start_url.DeprecatedGetOriginAsURL()); |
| web_app->SetManifestId(start_url.DeprecatedGetOriginAsURL()); |
| web_app->AddSource(WebAppManagement::Type::kIwaUserInstalled); |
| web_app->SetIsLocallyInstalled(true); |
| web_app->SetIsolationData(isolation_data); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| return web_app; |
| } |
| |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| TEST_F(IsolatedWebAppUpdateManagerUpdateApplyOnStartupTest, |
| SchedulesPendingUpdateApplyTasks) { |
| WebAppTestManifestUpdatedObserver manifest_updated_observer( |
| &fake_provider().install_manager()); |
| manifest_updated_observer.BeginListening({iwa_info1_->url_info.app_id()}); |
| manifest_updated_observer.Wait(); |
| |
| EXPECT_THAT(fake_provider().registrar_unsafe().GetAppById( |
| iwa_info1_->url_info.app_id()), |
| test::IwaIs(iwa_info1_->update_app_name, |
| test::IsolationDataIs( |
| update_location_, Eq(iwa_info1_->update_version), |
| /*controlled_frame_partitions=*/_, |
| /*pending_update_info=*/Eq(std::nullopt)))); |
| } |
| |
| class IsolatedWebAppUpdateManagerDiscoveryTimerTest |
| : public IsolatedWebAppUpdateManagerTest { |
| protected: |
| void SetUp() override { |
| IsolatedWebAppUpdateManagerTest::SetUp(); |
| fake_provider().SetEnableAutomaticIwaUpdates( |
| FakeWebAppProvider::AutomaticIwaUpdateStrategy::kForceEnabled); |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| }; |
| |
| TEST_F(IsolatedWebAppUpdateManagerDiscoveryTimerTest, |
| DoesNotStartUpdateDiscoveryIfNoIwaIsInstalled) { |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsFalse()); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerDiscoveryTimerTest, |
| StartsUpdateDiscoveryTimerWithJitter) { |
| std::vector<base::TimeTicks> times; |
| for (int i = 0; i < 10; ++i) { |
| webapps::AppId app_id = AddDummyIsolatedAppToRegistry( |
| profile(), GURL("isolated-app://a"), "iwa"); |
| auto time = update_manager().GetNextUpdateDiscoveryTimeForTesting(); |
| EXPECT_THAT(time, |
| Optional(AllOf(Ge(base::TimeTicks::Now() + base::Hours(4)), |
| Le(base::TimeTicks::Now() + base::Hours(6))))); |
| if (time.has_value()) { |
| // Check that the time is truly random (and thus different) from all |
| // previously generated times. |
| EXPECT_THAT(times, Each(Ne(*time))); |
| times.push_back(*time); |
| } |
| |
| test::UninstallWebApp(profile(), app_id); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsFalse()); |
| } |
| |
| // TODO(crbug.com/1469880): As a temporary fix to avoid race conditions with |
| // `ScopedProfileKeepAlive`s, manually shutdown `KeyedService`s holding them. |
| fake_provider().Shutdown(); |
| ChromeBrowsingDataRemoverDelegateFactory::GetForProfile(profile()) |
| ->Shutdown(); |
| } |
| |
| TEST_F(IsolatedWebAppUpdateManagerDiscoveryTimerTest, |
| RunsUpdateDiscoveryWhileIwaIsInstalled) { |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsFalse()); |
| |
| webapps::AppId non_iwa_id = |
| test::InstallDummyWebApp(profile(), "non-iwa", GURL("https://a")); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsFalse()); |
| |
| webapps::AppId iwa_app_id1 = AddDummyIsolatedAppToRegistry( |
| profile(), GURL("isolated-app://a"), "iwa1"); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsTrue()); |
| |
| webapps::AppId iwa_app_id2 = AddDummyIsolatedAppToRegistry( |
| profile(), GURL("isolated-app://b"), "iwa2"); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsTrue()); |
| |
| test::UninstallWebApp(profile(), iwa_app_id1); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsTrue()); |
| |
| test::UninstallWebApp(profile(), non_iwa_id); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsTrue()); |
| |
| test::UninstallWebApp(profile(), iwa_app_id2); |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| IsFalse()); |
| } |
| |
| struct FeatureFlagParam { |
| base::flat_map<base::test::FeatureRef, bool> feature_states; |
| bool expected_result; |
| }; |
| |
| class IsolatedWebAppUpdateManagerFeatureFlagTest |
| : public IsolatedWebAppUpdateManagerTest, |
| public ::testing::WithParamInterface<FeatureFlagParam> { |
| public: |
| IsolatedWebAppUpdateManagerFeatureFlagTest() |
| : IsolatedWebAppUpdateManagerTest(GetParam().feature_states) {} |
| |
| protected: |
| void SetUp() override { |
| IsolatedWebAppUpdateManagerTest::SetUp(); |
| // Disable manual overwrite of automatic update behavior and thus behave |
| // like it would outside of tests. |
| fake_provider().SetEnableAutomaticIwaUpdates( |
| FakeWebAppProvider::AutomaticIwaUpdateStrategy::kDefault); |
| test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| } |
| }; |
| |
| TEST_P(IsolatedWebAppUpdateManagerFeatureFlagTest, |
| DoesUpdateDiscoveryIfFeatureFlagsAreEnabled) { |
| AddDummyIsolatedAppToRegistry(profile(), GURL("isolated-app://a"), "iwa"); |
| |
| EXPECT_THAT( |
| update_manager().GetNextUpdateDiscoveryTimeForTesting().has_value(), |
| Eq(GetParam().expected_result)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* no prefix */, |
| IsolatedWebAppUpdateManagerFeatureFlagTest, |
| ::testing::Values( |
| // TODO: crbug.com/40274058 - Enable automatic updates on other platforms. |
| #if !BUILDFLAG(IS_CHROMEOS) |
| FeatureFlagParam{ |
| .feature_states = {{features::kIsolatedWebApps, false}}, |
| .expected_result = false}, |
| FeatureFlagParam{.feature_states = {{features::kIsolatedWebApps, true}}, |
| .expected_result = false} |
| #else // BUILDFLAG(IS_CHROMEOS) |
| FeatureFlagParam{ |
| .feature_states = {{features::kIsolatedWebApps, false}, |
| {features::kIsolatedWebAppAutomaticUpdates, |
| true}}, |
| .expected_result = false}, |
| FeatureFlagParam{ |
| .feature_states = {{features::kIsolatedWebApps, true}, |
| {features::kIsolatedWebAppAutomaticUpdates, |
| false}}, |
| .expected_result = false}, |
| FeatureFlagParam{ |
| .feature_states = {{features::kIsolatedWebApps, true}, |
| {features::kIsolatedWebAppAutomaticUpdates, |
| true}}, |
| .expected_result = true} |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| )); |
| |
| } // namespace |
| } // namespace web_app |