| // 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 "components/site_engagement/content/site_engagement_helper.h" |
| |
| #include <memory> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/site_engagement/content/engagement_type.h" |
| #include "components/site_engagement/content/site_engagement_metrics.h" |
| #include "components/site_engagement/content/site_engagement_observer.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace site_engagement { |
| |
| class TestOneShotTimer : public base::OneShotTimer { |
| public: |
| TestOneShotTimer() = default; |
| ~TestOneShotTimer() override = default; |
| |
| // base::OneShotTimer: |
| void Start(const base::Location& posted_from, |
| base::TimeDelta delay, |
| base::OnceClosure user_task) override { |
| base::OneShotTimer::Start(posted_from, base::Seconds(0), |
| std::move(user_task)); |
| |
| // Updates |restarted_| if the timer is restarted. |
| if (started_) |
| restarted_ = true; |
| |
| started_ = true; |
| } |
| |
| bool restarted() { return restarted_; } |
| |
| private: |
| bool started_ = false; |
| // Used to know if SiteEngagementService::Helper::DidFinishNavigation() |
| // handles a page in the prerendering. |
| bool restarted_ = false; |
| }; |
| |
| class SiteEngagementHelperBrowserTest : public InProcessBrowserTest { |
| public: |
| SiteEngagementHelperBrowserTest() |
| : prerender_helper_( |
| base::BindRepeating(&SiteEngagementHelperBrowserTest::web_contents, |
| base::Unretained(this))) {} |
| ~SiteEngagementHelperBrowserTest() override = default; |
| |
| // InProcessBrowserTest: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(test_server_handle_ = |
| embedded_test_server()->StartAndReturnHandle()); |
| } |
| |
| // Set a pause timer on the input tracker for test purposes. |
| void SetInputTrackerPauseTimer(SiteEngagementService::Helper* helper) { |
| input_tracker_timer_ = new TestOneShotTimer; |
| helper->input_tracker_.SetPauseTimerForTesting( |
| base::WrapUnique(input_tracker_timer_.get())); |
| } |
| |
| // Set a pause timer on the media tracker for test purposes. |
| void SetMediaTrackerPauseTimer(SiteEngagementService::Helper* helper) { |
| media_tracker_timer_ = new TestOneShotTimer; |
| helper->media_tracker_.SetPauseTimerForTesting( |
| base::WrapUnique(media_tracker_timer_.get())); |
| } |
| |
| bool IsInputTrackerTimerRestarted(SiteEngagementService::Helper* helper) { |
| return input_tracker_timer_->restarted(); |
| } |
| |
| content::test::PrerenderTestHelper* prerender_helper() { |
| return &prerender_helper_; |
| } |
| |
| content::WebContents* web_contents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| base::HistogramTester* histogram_tester() { return &histogram_tester_; } |
| |
| private: |
| content::test::PrerenderTestHelper prerender_helper_; |
| net::test_server::EmbeddedTestServerHandle test_server_handle_; |
| base::HistogramTester histogram_tester_; |
| raw_ptr<TestOneShotTimer, DanglingUntriaged> input_tracker_timer_; |
| raw_ptr<TestOneShotTimer, DanglingUntriaged> media_tracker_timer_; |
| }; |
| |
| // Tests if SiteEngagementHelper checks the primary main frame in the |
| // prerendering. |
| IN_PROC_BROWSER_TEST_F(SiteEngagementHelperBrowserTest, |
| SiteEngagementHelperInPrerendering) { |
| SiteEngagementService::Helper* helper = |
| SiteEngagementService::Helper::FromWebContents(web_contents()); |
| SetInputTrackerPauseTimer(helper); |
| |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| |
| // SiteEngagementMetrics::kEngagementTypeHistogram is 2 with |
| // kFirstDailyEngagement and kNavigation. |
| histogram_tester()->ExpectTotalCount( |
| SiteEngagementMetrics::kEngagementTypeHistogram, 2); |
| // It's the first navigation after overriding the timer. |
| EXPECT_FALSE(IsInputTrackerTimerRestarted(helper)); |
| |
| // Loads a page in the prerender. |
| auto prerender_url = embedded_test_server()->GetURL("/simple.html"); |
| int host_id = prerender_helper()->AddPrerender(prerender_url); |
| content::test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| // SiteEngagementMetrics::kEngagementTypeHistogram is not updated with the |
| // prerendering. |
| histogram_tester()->ExpectTotalCount( |
| SiteEngagementMetrics::kEngagementTypeHistogram, 2); |
| // Should not be restarted since |
| // SiteEngagementService::Helper::DidFinishNavigation() skips the page in the |
| // prerendering. |
| EXPECT_FALSE(IsInputTrackerTimerRestarted(helper)); |
| |
| prerender_helper()->NavigatePrimaryPage(*web_contents(), prerender_url); |
| |
| // Makes sure that the page is activated from the prerendering. |
| EXPECT_TRUE(host_observer.was_activated()); |
| // Should be restarted since the page is activated from the prerendering. |
| EXPECT_TRUE(IsInputTrackerTimerRestarted(helper)); |
| // Renderer initiated activation is not counted as an engagement event. As a |
| // result, SiteEngagementMetrics::kEngagementTypeHistogram maintains a value |
| // of 2 with the prerendering activation. |
| // |
| // TODO(crbug.com/1166085): Add a test for browser-initiated/omnibox |
| // navigations when available. |
| histogram_tester()->ExpectTotalCount( |
| SiteEngagementMetrics::kEngagementTypeHistogram, 2); |
| // SiteEngagementMetrics::kEngagementTypeHistogram should be 1 with |
| // EngagementType::kNavigation. |
| histogram_tester()->ExpectBucketCount( |
| SiteEngagementMetrics::kEngagementTypeHistogram, |
| EngagementType::kNavigation, 1); |
| } |
| |
| class ObserverTester : public SiteEngagementObserver { |
| public: |
| explicit ObserverTester(SiteEngagementService* service) |
| : SiteEngagementObserver(service) {} |
| |
| void OnEngagementEvent(content::WebContents* web_contents, |
| const GURL& url, |
| double score, |
| EngagementType type) override { |
| last_updated_type_ = type; |
| last_updated_url_ = url; |
| if (type == type_waiting_) { |
| if (quit_closure_) |
| std::move(quit_closure_).Run(); |
| } |
| } |
| |
| void WaitForEngagementEvent(EngagementType type) { |
| type_waiting_ = type; |
| base::RunLoop run_loop; |
| quit_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| EngagementType last_updated_type() { return last_updated_type_; } |
| const GURL& last_updated_url() { return last_updated_url_; } |
| |
| private: |
| base::OnceClosure quit_closure_; |
| GURL last_updated_url_; |
| EngagementType last_updated_type_ = EngagementType::kLast; |
| EngagementType type_waiting_ = EngagementType::kLast; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(SiteEngagementHelperBrowserTest, |
| SiteEngagementHelperMediaTrackerInPrerendering) { |
| site_engagement::SiteEngagementService* service = |
| site_engagement::SiteEngagementService::Get(browser()->profile()); |
| ObserverTester tester(service); |
| |
| SiteEngagementService::Helper* helper = |
| SiteEngagementService::Helper::FromWebContents(web_contents()); |
| SetMediaTrackerPauseTimer(helper); |
| |
| GURL url = embedded_test_server()->GetURL("/empty.html"); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| EXPECT_EQ(tester.last_updated_type(), EngagementType::kNavigation); |
| EXPECT_EQ(tester.last_updated_url(), url); |
| |
| // Load a page in the prerender. |
| GURL prerender_url = |
| embedded_test_server()->GetURL("/media/unified_autoplay.html"); |
| int host_id = prerender_helper()->AddPrerender(prerender_url); |
| content::test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| content::RenderFrameHost* prerendered_frame_host = |
| prerender_helper()->GetPrerenderedMainFrameHost(host_id); |
| // Since the prerendered page couldn't have a user gesture, it runs JS with |
| // EXECUTE_SCRIPT_NO_USER_GESTURE. Requesting playing video without a user |
| // gesture results in the promise rejected. |
| EXPECT_EQ(false, content::EvalJs( |
| prerendered_frame_host, "attemptPlay();", |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| |
| EXPECT_EQ(tester.last_updated_type(), EngagementType::kNavigation); |
| EXPECT_EQ(tester.last_updated_url(), url); |
| |
| // Navigate the primary page to the URL. |
| prerender_helper()->NavigatePrimaryPage(prerender_url); |
| // The page should be activated from the prerendering. |
| EXPECT_TRUE(host_observer.was_activated()); |
| |
| EXPECT_TRUE( |
| content::EvalJs(web_contents()->GetPrimaryMainFrame(), "attemptPlay();") |
| .ExtractBool()); |
| |
| tester.WaitForEngagementEvent(EngagementType::kMediaVisible); |
| EXPECT_EQ(tester.last_updated_type(), EngagementType::kMediaVisible); |
| EXPECT_EQ(tester.last_updated_url(), prerender_url); |
| } |
| |
| } // namespace site_engagement |