| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/installedapp/installed_app_provider_impl.h" |
| |
| #include "base/functional/bind.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "content/browser/installedapp/installed_app_provider_impl.h" |
| #include "content/browser/installedapp/test/installed_app_provider_impl_test_utils.h" |
| #include "content/common/features.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| #include "content/public/common/content_features.h" |
| #include "content/test/test_render_frame_host.h" |
| #include "content/test/test_render_view_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/installedapp/related_application.mojom.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "content/browser/installedapp/native_win_app_fetcher.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| namespace content { |
| |
| namespace { |
| |
| using testing::Contains; |
| using testing::IsEmpty; |
| using testing::Not; |
| |
| const char kInstalledWinAppId[] = "a"; |
| const char kInstalledWebAppId[] = "http://foo.com/"; |
| |
| const std::vector<std::string> kInstalledWinAppIds = { |
| kInstalledWinAppId, "B", "C", "D", "E", "F", "G"}; |
| const std::vector<std::string> kInstalledWebAppIds = { |
| kInstalledWebAppId, "http://foo.com/test", "http://foo2.com", |
| "http://bar.com"}; |
| } // namespace |
| |
| class RelatedAppsTestWebContentsDelegate : public WebContentsDelegate { |
| public: |
| MOCK_METHOD(std::vector<blink::mojom::RelatedApplicationPtr>, |
| GetSavedRelatedApplications, |
| (content::WebContents*), |
| (override)); |
| }; |
| |
| class InstalledAppProviderImplTest : public RenderViewHostImplTestHarness { |
| public: |
| explicit InstalledAppProviderImplTest() |
| : content_browser_client_(kInstalledWebAppIds) { |
| feature_list_.InitWithFeatures( |
| {features::kInstalledAppProvider, |
| #if BUILDFLAG(IS_WIN) |
| features::kFilterInstalledAppsWinMatching, |
| #endif // BUILDFLAG(IS_WIN) |
| features::kFilterInstalledAppsWebAppMatching}, |
| {}); |
| } |
| |
| void SetUp() override { |
| RenderViewHostImplTestHarness::SetUp(); |
| |
| old_content_browser_client_ = |
| SetBrowserClientForTesting(&content_browser_client_); |
| |
| // Owned ptr, we must be careful to destroy this on TearDown(). |
| provider_ = InstalledAppProviderImpl::CreateForTesting( |
| *(contents()->GetPrimaryMainFrame()), |
| remote_.BindNewPipeAndPassReceiver()); |
| #if BUILDFLAG(IS_WIN) |
| provider_->SetNativeWinAppFetcherFactoryForTesting( |
| base::BindRepeating(&CreateFakeNativeWinAppFetcherForTesting, |
| std::move(kInstalledWinAppIds))); |
| #endif // BUILDFLAG(IS_WIN) |
| web_contents()->SetDelegate(&web_contents_delegate_); |
| } |
| |
| void TearDown() override { |
| SetBrowserClientForTesting(old_content_browser_client_); |
| // Owned ptr, we must be careful to destroy this on TearDown(). |
| provider_ = nullptr; |
| RenderViewHostImplTestHarness::TearDown(); |
| } |
| |
| mojo::Remote<blink::mojom::InstalledAppProvider>& remote() { return remote_; } |
| |
| RelatedAppsTestWebContentsDelegate* web_contents_delegate() { |
| return &web_contents_delegate_; |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| |
| mojo::Remote<blink::mojom::InstalledAppProvider> remote_; |
| FakeContentBrowserClientForQueryInstalledWebApps content_browser_client_; |
| raw_ptr<InstalledAppProviderImpl> provider_; |
| raw_ptr<content::ContentBrowserClient> old_content_browser_client_ = nullptr; |
| RelatedAppsTestWebContentsDelegate web_contents_delegate_; |
| }; |
| |
| MATCHER_P(RelatedAppById, app_id, "") { |
| if (app_id != arg->id) { |
| *result_listener << arg->id.value() << " doesn't match expected " << app_id; |
| } |
| return arg->id == app_id; |
| } |
| |
| TEST_F(InstalledAppProviderImplTest, GetRelatedApps) { |
| std::vector<blink::mojom::RelatedApplicationPtr> related_applications; |
| const std::string unknown_web_app_id = "http://unknownid.com"; |
| |
| // Test that related application matching on windows does not rely on |
| // capitalization. |
| related_applications.push_back(CreateRelatedApplicationFromPlatformAndId( |
| "windows", base::ToUpperASCII(kInstalledWinAppId))); |
| related_applications.push_back( |
| CreateRelatedApplicationFromPlatformAndId("webapp", kInstalledWebAppId)); |
| related_applications.push_back( |
| CreateRelatedApplicationFromPlatformAndId("webapp", unknown_web_app_id)); |
| |
| base::test::TestFuture<std::vector<blink::mojom::RelatedApplicationPtr>> |
| future; |
| |
| remote()->FilterInstalledApps( |
| std::move(related_applications), GURL("http://foo.com/manifest.json"), |
| /*add_saved_related_applications=*/false, future.GetCallback()); |
| |
| ASSERT_TRUE(future.Wait()); |
| const std::vector<blink::mojom::RelatedApplicationPtr>& result = future.Get(); |
| |
| std::size_t expected_number_of_matches = 0u; |
| #if BUILDFLAG(IS_WIN) |
| expected_number_of_matches += 1u; |
| #endif // BUILDFLAG(IS_WIN) |
| #if !BUILDFLAG(IS_ANDROID) |
| expected_number_of_matches += 1u; |
| #endif // !BUILDFLAG(IS_ANDROID) |
| EXPECT_EQ(result.size(), expected_number_of_matches); |
| |
| #if BUILDFLAG(IS_WIN) |
| EXPECT_THAT(result, Contains(RelatedAppById(kInstalledWinAppId))); |
| #endif // BUILDFLAG(IS_WIN) |
| #if !BUILDFLAG(IS_ANDROID) |
| EXPECT_THAT(result, Contains(RelatedAppById(kInstalledWebAppId))); |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Should not be in list. |
| EXPECT_THAT(result, Not(Contains(RelatedAppById(unknown_web_app_id)))); |
| } |
| |
| TEST_F(InstalledAppProviderImplTest, |
| ShouldReturnNothingWithEmptyRelatedApplications) { |
| base::test::TestFuture<std::vector<blink::mojom::RelatedApplicationPtr>> |
| future; |
| |
| // Empty related apps list |
| remote()->FilterInstalledApps( |
| std::vector<blink::mojom::RelatedApplicationPtr>(), |
| GURL("http://foo.com/manifest.json"), |
| /*add_saved_related_applications=*/false, future.GetCallback()); |
| |
| ASSERT_TRUE(future.Wait()); |
| EXPECT_THAT(future.Get(), IsEmpty()); |
| } |
| |
| TEST_F(InstalledAppProviderImplTest, |
| ShouldNotReturnWebAppIfManifestIdIsInvalid) { |
| const std::string invalid_web_app_id = "http:invalid-url"; |
| std::vector<blink::mojom::RelatedApplicationPtr> related_applications; |
| related_applications.push_back( |
| CreateRelatedApplicationFromPlatformAndId("webapp", invalid_web_app_id)); |
| |
| base::test::TestFuture<std::vector<blink::mojom::RelatedApplicationPtr>> |
| future; |
| remote()->FilterInstalledApps( |
| std::move(related_applications), GURL("http://foo.com/manifest.json"), |
| /*add_saved_related_applications=*/false, future.GetCallback()); |
| |
| ASSERT_TRUE(future.Wait()); |
| EXPECT_THAT(future.Get(), IsEmpty()); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| TEST_F(InstalledAppProviderImplTest, LimitNumberOfMatchedApps) { |
| std::vector<blink::mojom::RelatedApplicationPtr> related_applications; |
| |
| for (const std::string& id : kInstalledWebAppIds) { |
| related_applications.push_back( |
| CreateRelatedApplicationFromPlatformAndId("webapp", id)); |
| } |
| for (const std::string& id : kInstalledWinAppIds) { |
| related_applications.push_back( |
| CreateRelatedApplicationFromPlatformAndId("windows", id)); |
| } |
| EXPECT_EQ(related_applications.size(), 11u); |
| |
| base::test::TestFuture<std::vector<blink::mojom::RelatedApplicationPtr>> |
| future; |
| remote()->FilterInstalledApps( |
| std::move(related_applications), GURL("http://foo.com/manifest.json"), |
| /*add_saved_related_applications=*/false, future.GetCallback()); |
| |
| ASSERT_TRUE(future.Wait()); |
| const std::vector<blink::mojom::RelatedApplicationPtr>& result = future.Get(); |
| EXPECT_EQ(result.size(), 10u); |
| } |
| |
| TEST_F(InstalledAppProviderImplTest, UseSavedRelatedAppsIfManifestFetchFailed) { |
| std::vector<blink::mojom::RelatedApplicationPtr> related_applications; |
| |
| std::vector<blink::mojom::RelatedApplicationPtr> saved_related_apps; |
| saved_related_apps.push_back( |
| CreateRelatedApplicationFromPlatformAndId("webapp", kInstalledWebAppId)); |
| |
| EXPECT_CALL(*web_contents_delegate(), GetSavedRelatedApplications(testing::_)) |
| .WillOnce( |
| testing::Return(testing::ByMove(std::move(saved_related_apps)))); |
| |
| base::test::TestFuture<std::vector<blink::mojom::RelatedApplicationPtr>> |
| future; |
| |
| remote()->FilterInstalledApps( |
| std::move(related_applications), GURL("http://foo.com/manifest.json"), |
| /*add_saved_related_applications=*/true, future.GetCallback()); |
| |
| ASSERT_TRUE(future.Wait()); |
| const std::vector<blink::mojom::RelatedApplicationPtr>& result = future.Get(); |
| |
| EXPECT_EQ(result.size(), 1u); |
| EXPECT_THAT(result, Contains(RelatedAppById(kInstalledWebAppId))); |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| } // namespace content |