| // 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 "components/safe_browsing/triggers/ad_sampler_trigger.h" |
| |
| #include "base/metrics/field_trial_params.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/safe_browsing/features.h" |
| #include "components/safe_browsing/triggers/trigger_manager.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "testing/gmock/include/gmock/gmock-generated-function-mockers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using content::NavigationSimulator; |
| using content::RenderFrameHost; |
| using content::RenderFrameHostTester; |
| |
| using testing::_; |
| using testing::Return; |
| |
| namespace safe_browsing { |
| |
| namespace { |
| const char kAdUrl[] = "https://tpc.googlesyndication.com/safeframe/1"; |
| const char kNonAdUrl[] = "https://foo.com/"; |
| const char kAdName[] = "google_ads_iframe_1"; |
| const char kNonAdName[] = "foo"; |
| } // namespace |
| |
| class MockTriggerManager : public TriggerManager { |
| public: |
| MockTriggerManager() : TriggerManager(nullptr) {} |
| |
| MOCK_METHOD6(StartCollectingThreatDetails, |
| bool(TriggerType trigger_type, |
| content::WebContents* web_contents, |
| const security_interstitials::UnsafeResource& resource, |
| net::URLRequestContextGetter* request_context_getter, |
| history::HistoryService* history_service, |
| const SBErrorOptions& error_display_options)); |
| |
| MOCK_METHOD6(FinishCollectingThreatDetails, |
| bool(TriggerType trigger_type, |
| content::WebContents* web_contents, |
| const base::TimeDelta& delay, |
| bool did_proceed, |
| int num_visits, |
| const SBErrorOptions& error_display_options)); |
| }; |
| |
| class AdSamplerTriggerTest : public content::RenderViewHostTestHarness { |
| public: |
| AdSamplerTriggerTest() : task_runner_(new base::TestSimpleTaskRunner) {} |
| ~AdSamplerTriggerTest() override {} |
| |
| void SetUp() override { |
| content::RenderViewHostTestHarness::SetUp(); |
| |
| // Replace the task runner for the UI thread (since tests run on UI, and |
| // so does the trigger) with a testing task runner that specific tests |
| // can synchronize on. |
| base::MessageLoop::current()->SetTaskRunner(task_runner_); |
| |
| // Enable any prefs required for the trigger to run. |
| safe_browsing::RegisterProfilePrefs(prefs_.registry()); |
| prefs_.SetBoolean(prefs::kSafeBrowsingExtendedReportingOptInAllowed, true); |
| prefs_.SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled, true); |
| prefs_.SetBoolean(prefs::kSafeBrowsingScoutGroupSelected, true); |
| } |
| |
| void CreateTriggerWithFrequency(const size_t denominator) { |
| safe_browsing::AdSamplerTrigger::CreateForWebContents( |
| web_contents(), &trigger_manager_, &prefs_, nullptr, nullptr); |
| safe_browsing::AdSamplerTrigger::FromWebContents(web_contents()) |
| ->sampler_frequency_denominator_ = denominator; |
| // Set delay timers artificially to keep tests fast. |
| safe_browsing::AdSamplerTrigger::FromWebContents(web_contents()) |
| ->start_report_delay_ms_ = 0; |
| safe_browsing::AdSamplerTrigger::FromWebContents(web_contents()) |
| ->finish_report_delay_ms_ = 0; |
| } |
| |
| // Returns the final RenderFrameHost after navigation commits. |
| RenderFrameHost* NavigateFrame(const std::string& url, |
| RenderFrameHost* frame) { |
| GURL gurl(url); |
| auto navigation_simulator = |
| NavigationSimulator::CreateRendererInitiated(gurl, frame); |
| navigation_simulator->Commit(); |
| RenderFrameHost* final_frame_host = |
| navigation_simulator->GetFinalRenderFrameHost(); |
| // Call the trigger's FinishLoad event handler directly since it doesn't |
| // happen as part of the navigation. |
| safe_browsing::AdSamplerTrigger::FromWebContents(web_contents()) |
| ->DidFinishLoad(final_frame_host, gurl); |
| return final_frame_host; |
| } |
| |
| // Returns the final RenderFrameHost after navigation commits. |
| RenderFrameHost* NavigateMainFrame(const std::string& url) { |
| return NavigateFrame(url, web_contents()->GetMainFrame()); |
| } |
| |
| // Returns the final RenderFrameHost after navigation commits. |
| RenderFrameHost* CreateAndNavigateSubFrame(const std::string& url, |
| const std::string& frame_name, |
| RenderFrameHost* parent) { |
| RenderFrameHost* subframe = |
| RenderFrameHostTester::For(parent)->AppendChild(frame_name); |
| return NavigateFrame(url, subframe); |
| } |
| |
| void WaitForTaskRunnerIdle() { |
| task_runner_->RunUntilIdle(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| MockTriggerManager* get_trigger_manager() { return &trigger_manager_; } |
| base::HistogramTester* get_histograms() { return &histograms_; } |
| |
| private: |
| TestingPrefServiceSimple prefs_; |
| MockTriggerManager trigger_manager_; |
| base::HistogramTester histograms_; |
| scoped_refptr<base::TestSimpleTaskRunner> task_runner_; |
| }; |
| |
| TEST_F(AdSamplerTriggerTest, TriggerDisabledBySamplingFrequency) { |
| // Make sure the trigger doesn't fire when the sampling frequency is set to |
| // zero, which disables the trigger. |
| CreateTriggerWithFrequency(kSamplerFrequencyDisabled); |
| EXPECT_CALL(*get_trigger_manager(), |
| StartCollectingThreatDetails(_, _, _, _, _, _)) |
| .Times(0); |
| EXPECT_CALL(*get_trigger_manager(), |
| FinishCollectingThreatDetails(_, _, _, _, _, _)) |
| .Times(0); |
| |
| // This page contains two ads - one identifiable by its URL, the other by the |
| // name of the frame. |
| RenderFrameHost* main_frame = NavigateMainFrame(kNonAdUrl); |
| CreateAndNavigateSubFrame(kAdUrl, kNonAdName, main_frame); |
| CreateAndNavigateSubFrame(kNonAdUrl, kAdName, main_frame); |
| |
| // Three navigations (main frame, two subframes). One frame with no ads, and |
| // two skipped ad samples. |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| TRIGGER_CHECK, 3); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_NO_AD, 1); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_AD_SKIPPED_FOR_FREQUENCY, 2); |
| } |
| |
| TEST_F(AdSamplerTriggerTest, PageWithNoAds) { |
| // Make sure the trigger doesn't fire when there are no ads on the page. |
| CreateTriggerWithFrequency(/*denominator=*/1); |
| |
| EXPECT_CALL(*get_trigger_manager(), |
| StartCollectingThreatDetails(_, _, _, _, _, _)) |
| .Times(0); |
| EXPECT_CALL(*get_trigger_manager(), |
| FinishCollectingThreatDetails(_, _, _, _, _, _)) |
| .Times(0); |
| |
| RenderFrameHost* main_frame = NavigateMainFrame(kNonAdUrl); |
| CreateAndNavigateSubFrame(kNonAdUrl, kNonAdName, main_frame); |
| CreateAndNavigateSubFrame(kNonAdUrl, kNonAdName, main_frame); |
| |
| // Three navigations (main frame, two subframes), each with no ad. |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| TRIGGER_CHECK, 3); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_NO_AD, 3); |
| } |
| |
| TEST_F(AdSamplerTriggerTest, PageWithMultipleAds) { |
| // Make sure the trigger fires when there are ads on the page. We expect |
| // one call for each ad detected. |
| CreateTriggerWithFrequency(/*denominator=*/1); |
| EXPECT_CALL(*get_trigger_manager(), |
| StartCollectingThreatDetails(TriggerType::AD_SAMPLE, |
| web_contents(), _, _, _, _)) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| EXPECT_CALL(*get_trigger_manager(), |
| FinishCollectingThreatDetails(TriggerType::AD_SAMPLE, |
| web_contents(), _, _, _, _)) |
| .Times(2); |
| |
| // This page contains two ads - one identifiable by its URL, the other by the |
| // name of the frame. |
| RenderFrameHost* main_frame = NavigateMainFrame(kNonAdUrl); |
| CreateAndNavigateSubFrame(kAdUrl, kNonAdName, main_frame); |
| CreateAndNavigateSubFrame(kNonAdUrl, kAdName, main_frame); |
| |
| // Wait for any posted tasks to finish. |
| WaitForTaskRunnerIdle(); |
| |
| // Three navigations (main frame, two subframes). Main frame with no ads, and |
| // two sampled ads |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| TRIGGER_CHECK, 3); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_NO_AD, 1); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| AD_SAMPLED, 2); |
| } |
| |
| TEST_F(AdSamplerTriggerTest, ReportRejectedByTriggerManager) { |
| // If the trigger manager rejects the report, we don't try to finish/send the |
| // report. |
| CreateTriggerWithFrequency(/*denominator=*/1); |
| EXPECT_CALL(*get_trigger_manager(), |
| StartCollectingThreatDetails(TriggerType::AD_SAMPLE, |
| web_contents(), _, _, _, _)) |
| .Times(1) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(*get_trigger_manager(), |
| FinishCollectingThreatDetails(TriggerType::AD_SAMPLE, |
| web_contents(), _, _, _, _)) |
| .Times(0); |
| |
| // One ad on the page, identified by its URL. |
| RenderFrameHost* main_frame = NavigateMainFrame(kNonAdUrl); |
| CreateAndNavigateSubFrame(kAdUrl, kNonAdName, main_frame); |
| CreateAndNavigateSubFrame(kNonAdUrl, kNonAdName, main_frame); |
| |
| // Wait for any posted tasks to finish. |
| WaitForTaskRunnerIdle(); |
| |
| // Three navigations (main frame, two subframes). Two frames with no ads, and |
| // one ad rejected by trigger manager. |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| TRIGGER_CHECK, 3); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_NO_AD, 2); |
| get_histograms()->ExpectBucketCount(kAdSamplerTriggerActionMetricName, |
| NO_SAMPLE_COULD_NOT_START_REPORT, 1); |
| } |
| |
| TEST(AdSamplerTriggerTestFinch, FrequencyDenominatorFeature) { |
| // Make sure that setting the frequency denominator via Finch params works as |
| // expected. |
| const size_t kDenominatorInt = 12345; |
| base::FieldTrialList field_trial_list(nullptr); |
| |
| base::FieldTrial* trial = base::FieldTrialList::CreateFieldTrial( |
| safe_browsing::kAdSamplerTriggerFeature.name, "Group"); |
| std::map<std::string, std::string> feature_params; |
| feature_params[std::string( |
| safe_browsing::kAdSamplerFrequencyDenominatorParam)] = |
| base::IntToString(kDenominatorInt); |
| base::AssociateFieldTrialParams(safe_browsing::kAdSamplerTriggerFeature.name, |
| "Group", feature_params); |
| std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); |
| feature_list->InitializeFromCommandLine( |
| safe_browsing::kAdSamplerTriggerFeature.name, std::string()); |
| feature_list->AssociateReportingFieldTrial( |
| safe_browsing::kAdSamplerTriggerFeature.name, |
| base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial); |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeatureList(std::move(feature_list)); |
| |
| AdSamplerTrigger trigger(nullptr, nullptr, nullptr, nullptr, nullptr); |
| EXPECT_EQ(kDenominatorInt, trigger.sampler_frequency_denominator_); |
| } |
| } // namespace safe_browsing |