| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/offline_pages/offline_page_tab_helper.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/offline_pages/prefetch/prefetch_service_factory.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/offline_pages/core/model/offline_page_model_utils.h" |
| #include "components/offline_pages/core/offline_page_item.h" |
| #include "components/offline_pages/core/prefetch/offline_metrics_collector.h" |
| #include "components/offline_pages/core/prefetch/prefetch_service.h" |
| #include "components/offline_pages/core/prefetch/prefetch_service_test_taco.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| const GURL kTestPageUrl("http://mystery.site/foo.html"); |
| const GURL kTestFileUrl("file://foo"); |
| const base::Time kTestMhtmlCreationTime = |
| base::Time::FromJsTime(1522339419011L); |
| const char kLoadResultUmaNameAsync[] = |
| "OfflinePages.MhtmlLoadResult.async_loading"; |
| |
| #if defined(OS_ANDROID) |
| const GURL kTestContentUrl("content://foo"); |
| #endif |
| |
| const char kTestHeader[] = "reason=download"; |
| } // namespace |
| |
| namespace offline_pages { |
| namespace { |
| |
| using blink::mojom::MHTMLLoadResult; |
| |
| class TestMetricsCollector : public OfflineMetricsCollector { |
| public: |
| TestMetricsCollector() = default; |
| ~TestMetricsCollector() override = default; |
| |
| // OfflineMetricsCollector implementation |
| void OnAppStartupOrResume() override { app_startup_count_++; } |
| void OnSuccessfulNavigationOnline() override { |
| successful_online_navigations_count_++; |
| } |
| void OnSuccessfulNavigationOffline() override { |
| successful_offline_navigations_count_++; |
| } |
| void OnPrefetchEnabled() override {} |
| void OnSuccessfulPagePrefetch() override {} |
| void OnPrefetchedPageOpened() override {} |
| void ReportAccumulatedStats() override { report_stats_count_++; } |
| |
| int app_startup_count_ = 0; |
| int successful_offline_navigations_count_ = 0; |
| int successful_online_navigations_count_ = 0; |
| int report_stats_count_ = 0; |
| }; |
| |
| // This is used by KeyedServiceFactory::SetTestingFactoryAndUse. |
| std::unique_ptr<KeyedService> BuildTestPrefetchService( |
| content::BrowserContext*) { |
| auto taco = std::make_unique<PrefetchServiceTestTaco>(); |
| taco->SetOfflineMetricsCollector(std::make_unique<TestMetricsCollector>()); |
| return taco->CreateAndReturnPrefetchService(); |
| } |
| |
| class OfflinePageTabHelperTest : public content::RenderViewHostTestHarness { |
| public: |
| OfflinePageTabHelperTest(); |
| ~OfflinePageTabHelperTest() override {} |
| |
| void SetUp() override; |
| void TearDown() override; |
| content::BrowserContext* CreateBrowserContext() override; |
| |
| void CreateNavigationSimulator(const GURL& url); |
| |
| void SimulateOfflinePageLoad(const GURL& mhtml_url, |
| base::Time mhtml_creation_time, |
| MHTMLLoadResult load_result); |
| |
| OfflinePageTabHelper* tab_helper() const { return tab_helper_; } |
| PrefetchService* prefetch_service() const { return prefetch_service_; } |
| content::NavigationSimulator* navigation_simulator() { |
| return navigation_simulator_.get(); |
| } |
| TestMetricsCollector* metrics() const { |
| return static_cast<TestMetricsCollector*>( |
| prefetch_service_->GetOfflineMetricsCollector()); |
| } |
| |
| private: |
| OfflinePageTabHelper* tab_helper_; // Owned by WebContents. |
| PrefetchService* prefetch_service_; // Keyed Service. |
| std::unique_ptr<content::NavigationSimulator> navigation_simulator_; |
| |
| base::WeakPtrFactory<OfflinePageTabHelperTest> weak_ptr_factory_; |
| DISALLOW_COPY_AND_ASSIGN(OfflinePageTabHelperTest); |
| }; |
| |
| OfflinePageTabHelperTest::OfflinePageTabHelperTest() |
| : tab_helper_(nullptr), weak_ptr_factory_(this) {} |
| |
| void OfflinePageTabHelperTest::SetUp() { |
| content::RenderViewHostTestHarness::SetUp(); |
| |
| PrefetchServiceFactory::GetInstance()->SetTestingFactoryAndUse( |
| browser_context(), base::BindRepeating(&BuildTestPrefetchService)); |
| prefetch_service_ = |
| PrefetchServiceFactory::GetForBrowserContext(browser_context()); |
| |
| OfflinePageTabHelper::CreateForWebContents(web_contents()); |
| tab_helper_ = OfflinePageTabHelper::FromWebContents(web_contents()); |
| } |
| |
| void OfflinePageTabHelperTest::TearDown() { |
| content::RenderViewHostTestHarness::TearDown(); |
| } |
| |
| content::BrowserContext* OfflinePageTabHelperTest::CreateBrowserContext() { |
| TestingProfile::Builder builder; |
| return builder.Build().release(); |
| } |
| |
| void OfflinePageTabHelperTest::CreateNavigationSimulator(const GURL& url) { |
| navigation_simulator_ = |
| content::NavigationSimulator::CreateBrowserInitiated(url, web_contents()); |
| navigation_simulator_->SetTransition(ui::PAGE_TRANSITION_LINK); |
| } |
| |
| void OfflinePageTabHelperTest::SimulateOfflinePageLoad( |
| const GURL& mhtml_url, |
| base::Time mhtml_creation_time, |
| MHTMLLoadResult load_result) { |
| tab_helper()->SetCurrentTargetFrameForTest(web_contents()->GetMainFrame()); |
| |
| // Simulate navigation |
| CreateNavigationSimulator(kTestFileUrl); |
| navigation_simulator()->Start(); |
| |
| OfflinePageItem offlinePage(mhtml_url, 0, ClientId("async_loading", "1234"), |
| base::FilePath(), 0, mhtml_creation_time); |
| OfflinePageHeader offlineHeader(kTestHeader); |
| tab_helper()->SetOfflinePage( |
| offlinePage, offlineHeader, |
| OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, false); |
| |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| |
| tab_helper()->NotifyMhtmlPageLoadAttempted(load_result, mhtml_url, |
| mhtml_creation_time); |
| navigation_simulator()->Commit(); |
| } |
| |
| // Checks the test setup. |
| TEST_F(OfflinePageTabHelperTest, InitialSetup) { |
| CreateNavigationSimulator(kTestPageUrl); |
| EXPECT_NE(nullptr, tab_helper()); |
| EXPECT_NE(nullptr, prefetch_service()); |
| EXPECT_NE(nullptr, prefetch_service()->GetOfflineMetricsCollector()); |
| EXPECT_EQ(metrics(), prefetch_service()->GetOfflineMetricsCollector()); |
| EXPECT_EQ(0, metrics()->app_startup_count_); |
| EXPECT_EQ(0, metrics()->successful_online_navigations_count_); |
| EXPECT_EQ(0, metrics()->successful_offline_navigations_count_); |
| EXPECT_EQ(0, metrics()->report_stats_count_); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, MetricsStartNavigation) { |
| CreateNavigationSimulator(kTestPageUrl); |
| // This causes WCO::DidStartNavigation() |
| navigation_simulator()->Start(); |
| |
| EXPECT_EQ(1, metrics()->app_startup_count_); |
| EXPECT_EQ(0, metrics()->successful_online_navigations_count_); |
| EXPECT_EQ(0, metrics()->successful_offline_navigations_count_); |
| EXPECT_EQ(0, metrics()->report_stats_count_); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, MetricsOnlineNavigation) { |
| CreateNavigationSimulator(kTestPageUrl); |
| navigation_simulator()->Start(); |
| navigation_simulator()->Commit(); |
| |
| EXPECT_EQ(1, metrics()->app_startup_count_); |
| EXPECT_EQ(1, metrics()->successful_online_navigations_count_); |
| EXPECT_EQ(0, metrics()->successful_offline_navigations_count_); |
| // Since this is online navigation, request to send data should be made. |
| EXPECT_EQ(1, metrics()->report_stats_count_); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, MetricsOfflineNavigation) { |
| CreateNavigationSimulator(kTestPageUrl); |
| navigation_simulator()->Start(); |
| |
| // Simulate offline interceptor loading an offline page instead. |
| OfflinePageItem offlinePage(kTestPageUrl, 0, ClientId(), base::FilePath(), 0); |
| OfflinePageHeader offlineHeader; |
| tab_helper()->SetOfflinePage( |
| offlinePage, offlineHeader, |
| OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, false); |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| |
| navigation_simulator()->Commit(); |
| |
| EXPECT_EQ(1, metrics()->app_startup_count_); |
| EXPECT_EQ(0, metrics()->successful_online_navigations_count_); |
| EXPECT_EQ(1, metrics()->successful_offline_navigations_count_); |
| // During offline navigation, request to send data should not be made. |
| EXPECT_EQ(0, metrics()->report_stats_count_); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, TrustedInternalOfflinePage) { |
| CreateNavigationSimulator(kTestPageUrl); |
| navigation_simulator()->Start(); |
| |
| OfflinePageItem offlinePage(kTestPageUrl, 0, ClientId(), base::FilePath(), 0); |
| OfflinePageHeader offlineHeader(kTestHeader); |
| tab_helper()->SetOfflinePage( |
| offlinePage, offlineHeader, |
| OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, false); |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| navigation_simulator()->Commit(); |
| |
| ASSERT_NE(nullptr, tab_helper()->offline_page()); |
| EXPECT_EQ(kTestPageUrl, tab_helper()->offline_page()->url); |
| EXPECT_EQ(OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, |
| tab_helper()->trusted_state()); |
| EXPECT_TRUE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::DOWNLOAD, |
| tab_helper()->offline_header().reason); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, TrustedPublicOfflinePage) { |
| CreateNavigationSimulator(kTestPageUrl); |
| navigation_simulator()->Start(); |
| |
| OfflinePageItem offlinePage(kTestPageUrl, 0, ClientId(), base::FilePath(), 0); |
| OfflinePageHeader offlineHeader(kTestHeader); |
| tab_helper()->SetOfflinePage( |
| offlinePage, offlineHeader, |
| OfflinePageTrustedState::TRUSTED_AS_UNMODIFIED_AND_IN_PUBLIC_DIR, false); |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| navigation_simulator()->Commit(); |
| |
| ASSERT_NE(nullptr, tab_helper()->offline_page()); |
| EXPECT_EQ(kTestPageUrl, tab_helper()->offline_page()->url); |
| EXPECT_EQ(OfflinePageTrustedState::TRUSTED_AS_UNMODIFIED_AND_IN_PUBLIC_DIR, |
| tab_helper()->trusted_state()); |
| EXPECT_TRUE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::DOWNLOAD, |
| tab_helper()->offline_header().reason); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, UntrustedOfflinePageForFileUrl) { |
| CreateNavigationSimulator(kTestFileUrl); |
| navigation_simulator()->Start(); |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| navigation_simulator()->Commit(); |
| |
| ASSERT_NE(nullptr, tab_helper()->offline_page()); |
| EXPECT_EQ(OfflinePageTrustedState::UNTRUSTED, tab_helper()->trusted_state()); |
| EXPECT_FALSE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::NONE, |
| tab_helper()->offline_header().reason); |
| } |
| |
| #if defined(OS_ANDROID) |
| TEST_F(OfflinePageTabHelperTest, |
| UntrustedOfflinePageForContentUrlWithMultipartRelatedType) { |
| CreateNavigationSimulator(kTestContentUrl); |
| navigation_simulator()->Start(); |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| navigation_simulator()->Commit(); |
| |
| ASSERT_NE(nullptr, tab_helper()->offline_page()); |
| EXPECT_EQ(OfflinePageTrustedState::UNTRUSTED, tab_helper()->trusted_state()); |
| EXPECT_FALSE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::NONE, |
| tab_helper()->offline_header().reason); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, |
| UntrustedOfflinePageForContentUrlWithMessageRfc822Type) { |
| CreateNavigationSimulator(kTestContentUrl); |
| navigation_simulator()->Start(); |
| navigation_simulator()->SetContentsMimeType("message/rfc822"); |
| navigation_simulator()->Commit(); |
| |
| ASSERT_NE(nullptr, tab_helper()->offline_page()); |
| EXPECT_EQ(OfflinePageTrustedState::UNTRUSTED, tab_helper()->trusted_state()); |
| EXPECT_FALSE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::NONE, |
| tab_helper()->offline_header().reason); |
| } |
| #endif |
| |
| TEST_F(OfflinePageTabHelperTest, TestNotifyMhtmlPageLoadAttempted_Success) { |
| GURL mhtml_url("https://www.example.com"); |
| |
| // Simulate navigation and check UMA reporting. |
| base::HistogramTester histogram_tester; |
| SimulateOfflinePageLoad(mhtml_url, kTestMhtmlCreationTime, |
| MHTMLLoadResult::kSuccess); |
| histogram_tester.ExpectUniqueSample(kLoadResultUmaNameAsync, |
| MHTMLLoadResult::kSuccess, 1); |
| |
| EXPECT_EQ(OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, |
| tab_helper()->trusted_state()); |
| EXPECT_TRUE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::DOWNLOAD, |
| tab_helper()->offline_header().reason); |
| |
| const OfflinePageItem* offline_page = tab_helper()->offline_page(); |
| ASSERT_NE(nullptr, offline_page); |
| EXPECT_EQ(mhtml_url, offline_page->url); |
| EXPECT_EQ(kTestMhtmlCreationTime, offline_page->creation_time); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, |
| TestNotifyMhtmlPageLoadAttempted_BadUrlScheme) { |
| GURL mhtml_url("sftp://www.example.com"); |
| |
| base::HistogramTester histogram_tester; |
| SimulateOfflinePageLoad(mhtml_url, kTestMhtmlCreationTime, |
| MHTMLLoadResult::kUrlSchemeNotAllowed); |
| histogram_tester.ExpectUniqueSample(kLoadResultUmaNameAsync, |
| MHTMLLoadResult::kUrlSchemeNotAllowed, 1); |
| |
| EXPECT_EQ(OfflinePageTrustedState::TRUSTED_AS_IN_INTERNAL_DIR, |
| tab_helper()->trusted_state()); |
| EXPECT_TRUE(tab_helper()->IsShowingTrustedOfflinePage()); |
| EXPECT_EQ(OfflinePageHeader::Reason::DOWNLOAD, |
| tab_helper()->offline_header().reason); |
| |
| const OfflinePageItem* offline_page = tab_helper()->offline_page(); |
| EXPECT_EQ(mhtml_url, offline_page->url); |
| EXPECT_EQ(kTestMhtmlCreationTime, offline_page->creation_time); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, |
| TestNotifyMhtmlPageLoadAttempted_MhtmlEmptyFile) { |
| // Test empty file. For now, there's no need to actually load an empty file |
| // since we're calling NotifyMhtmlPageLoadAttempted directly with |
| // MHTMLLoadResult::kEmptyFile. |
| GURL mhtml_url("https://www.example.com"); |
| |
| base::HistogramTester histogram_tester; |
| SimulateOfflinePageLoad(mhtml_url, kTestMhtmlCreationTime, |
| MHTMLLoadResult::kEmptyFile); |
| histogram_tester.ExpectUniqueSample(kLoadResultUmaNameAsync, |
| MHTMLLoadResult::kEmptyFile, 1); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, |
| TestNotifyMhtmlPageLoadAttempted_MhtmlInvalidArchive) { |
| GURL mhtml_url("https://www.example.com"); |
| |
| base::HistogramTester histogram_tester; |
| SimulateOfflinePageLoad(mhtml_url, kTestMhtmlCreationTime, |
| MHTMLLoadResult::kInvalidArchive); |
| histogram_tester.ExpectUniqueSample(kLoadResultUmaNameAsync, |
| MHTMLLoadResult::kInvalidArchive, 1); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, |
| TestNotifyMhtmlPageLoadAttempted_MhtmlMissingMainResource) { |
| GURL mhtml_url("https://www.example.com"); |
| |
| base::HistogramTester histogram_tester; |
| SimulateOfflinePageLoad(mhtml_url, kTestMhtmlCreationTime, |
| MHTMLLoadResult::kMissingMainResource); |
| histogram_tester.ExpectUniqueSample(kLoadResultUmaNameAsync, |
| MHTMLLoadResult::kMissingMainResource, 1); |
| } |
| |
| TEST_F(OfflinePageTabHelperTest, TestNotifyMhtmlPageLoadAttempted_Untrusted) { |
| GURL mhtml_url("https://www.example.com"); |
| base::HistogramTester histogram_tester; |
| |
| tab_helper()->SetCurrentTargetFrameForTest(web_contents()->GetMainFrame()); |
| |
| // Simulate navigation |
| CreateNavigationSimulator(kTestFileUrl); |
| navigation_simulator()->Start(); |
| |
| // We force use of the untrusted page histogram by using an empty namespace. |
| OfflinePageItem offlinePage(mhtml_url, 0, ClientId("", "1234"), |
| base::FilePath(), 0, kTestMhtmlCreationTime); |
| OfflinePageHeader offlineHeader(kTestHeader); |
| tab_helper()->SetOfflinePage(offlinePage, offlineHeader, |
| OfflinePageTrustedState::UNTRUSTED, false); |
| |
| navigation_simulator()->SetContentsMimeType("multipart/related"); |
| |
| tab_helper()->NotifyMhtmlPageLoadAttempted(MHTMLLoadResult::kSuccess, |
| mhtml_url, kTestMhtmlCreationTime); |
| navigation_simulator()->Commit(); |
| |
| // Check histogram |
| histogram_tester.ExpectUniqueSample("OfflinePages.MhtmlLoadResultUntrusted", |
| MHTMLLoadResult::kSuccess, 1); |
| } |
| |
| } // namespace |
| } // namespace offline_pages |