blob: 2d40cb4239b644463d966500ee64dfa1f512b127 [file] [log] [blame]
// 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/ui/blocked_content/safe_browsing_triggered_popup_blocker.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h"
#include "chrome/browser/ui/blocked_content/popup_blocker.h"
#include "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/safe_browsing/db/v4_protocol_manager_util.h"
#include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/subresource_filter/core/browser/subresource_filter_features.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_triggering_event_info.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
#include "url/gurl.h"
const char kNumBlockedHistogram[] =
"ContentSettings.Popups.StrongBlocker.NumBlocked";
class SafeBrowsingTriggeredPopupBlockerTest
: public ChromeRenderViewHostTestHarness {
public:
SafeBrowsingTriggeredPopupBlockerTest() {}
~SafeBrowsingTriggeredPopupBlockerTest() override {}
// ChromeRenderViewHostTestHarness:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
system_request_context_getter_ =
base::MakeRefCounted<net::TestURLRequestContextGetter>(
base::CreateSingleThreadTaskRunnerWithTraits(
{content::BrowserThread::IO}));
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(
system_request_context_getter_.get());
// Set up safe browsing service with the fake database manager.
//
// TODO(csharrison): This is a bit ugly. See if the instructions in
// test_safe_browsing_service.h can be adapted to be used in unit tests.
safe_browsing::TestSafeBrowsingServiceFactory sb_service_factory;
fake_safe_browsing_database_ = new FakeSafeBrowsingDatabaseManager();
sb_service_factory.SetTestDatabaseManager(
fake_safe_browsing_database_.get());
safe_browsing::SafeBrowsingService::RegisterFactory(&sb_service_factory);
auto* safe_browsing_service =
sb_service_factory.CreateSafeBrowsingService();
safe_browsing::SafeBrowsingService::RegisterFactory(nullptr);
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(
safe_browsing_service);
g_browser_process->safe_browsing_service()->Initialize();
// Required for the safe browsing notifications on navigation.
ChromeSubresourceFilterClient::CreateForWebContents(web_contents());
scoped_feature_list_ = DefaultFeatureList();
PopupBlockerTabHelper::CreateForWebContents(web_contents());
InfoBarService::CreateForWebContents(web_contents());
TabSpecificContentSettings::CreateForWebContents(web_contents());
popup_blocker_ =
SafeBrowsingTriggeredPopupBlocker::FromWebContents(web_contents());
}
void TearDown() override {
fake_safe_browsing_database_ = nullptr;
TestingBrowserProcess::GetGlobal()->safe_browsing_service()->ShutDown();
// Must explicitly set these to null and pump the run loop to ensure that
// all cleanup related to these classes actually happens.
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(nullptr);
base::RunLoop().RunUntilIdle();
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(nullptr);
system_request_context_getter_ = nullptr;
ChromeRenderViewHostTestHarness::TearDown();
}
virtual std::unique_ptr<base::test::ScopedFeatureList> DefaultFeatureList() {
auto feature_list = std::make_unique<base::test::ScopedFeatureList>();
feature_list->InitAndEnableFeature(kAbusiveExperienceEnforce);
return feature_list;
}
FakeSafeBrowsingDatabaseManager* fake_safe_browsing_database() {
return fake_safe_browsing_database_.get();
}
base::test::ScopedFeatureList* ResetFeatureAndGet() {
scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
return scoped_feature_list_.get();
}
SafeBrowsingTriggeredPopupBlocker* popup_blocker() { return popup_blocker_; }
void SimulateDeleteContents() {
DeleteContents();
popup_blocker_ = nullptr;
}
void MarkUrlAsAbusiveWithLevel(const GURL& url,
safe_browsing::SubresourceFilterLevel level) {
safe_browsing::ThreatMetadata metadata;
metadata.subresource_filter_match
[safe_browsing::SubresourceFilterType::ABUSIVE] = level;
fake_safe_browsing_database()->AddBlacklistedUrl(
url, safe_browsing::SB_THREAT_TYPE_SUBRESOURCE_FILTER, metadata);
}
void MarkUrlAsAbusiveEnforce(const GURL& url) {
MarkUrlAsAbusiveWithLevel(url,
safe_browsing::SubresourceFilterLevel::ENFORCE);
}
void MarkUrlAsAbusiveWarning(const GURL& url) {
MarkUrlAsAbusiveWithLevel(url, safe_browsing::SubresourceFilterLevel::WARN);
}
const std::vector<std::string>& GetMainFrameConsoleMessages() {
content::RenderFrameHostTester* rfh_tester =
content::RenderFrameHostTester::For(main_rfh());
return rfh_tester->GetConsoleMessages();
}
private:
std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
scoped_refptr<FakeSafeBrowsingDatabaseManager> fake_safe_browsing_database_;
SafeBrowsingTriggeredPopupBlocker* popup_blocker_ = nullptr;
scoped_refptr<net::URLRequestContextGetter> system_request_context_getter_;
DISALLOW_COPY_AND_ASSIGN(SafeBrowsingTriggeredPopupBlockerTest);
};
struct RedirectSamplesAndResults {
GURL initial_url;
GURL redirect_url;
bool expect_strong_blocker;
};
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
MatchOnSafeBrowsingWithRedirectDetection) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
subresource_filter::kSafeBrowsingSubresourceFilterConsiderRedirects);
GURL enforce_url("https://example.enforce");
GURL warning_url("https://example.warning");
GURL regular_url("https://example.regular");
MarkUrlAsAbusiveEnforce(enforce_url);
MarkUrlAsAbusiveWarning(warning_url);
const RedirectSamplesAndResults kTestCases[] = {
{enforce_url, regular_url, true}, {regular_url, enforce_url, true},
{warning_url, enforce_url, true}, {enforce_url, warning_url, true},
{regular_url, warning_url, false}, {warning_url, regular_url, false}};
for (const auto& test_case : kTestCases) {
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
test_case.initial_url, web_contents()->GetMainFrame());
simulator->Start();
simulator->Redirect(test_case.redirect_url);
simulator->Commit();
EXPECT_EQ(test_case.expect_strong_blocker,
popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
MatchOnSafeBrowsingWithoutRedirectDetection) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
subresource_filter::kSafeBrowsingSubresourceFilterConsiderRedirects);
GURL enforce_url("https://example.enforce");
GURL warning_url("https://example.warning");
GURL regular_url("https://example.regular");
MarkUrlAsAbusiveEnforce(enforce_url);
MarkUrlAsAbusiveWarning(warning_url);
const RedirectSamplesAndResults kTestCases[] = {
{enforce_url, regular_url, false},
{regular_url, enforce_url, true},
{warning_url, enforce_url, true},
{enforce_url, warning_url, false}};
for (const auto& test_case : kTestCases) {
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
test_case.initial_url, web_contents()->GetMainFrame());
simulator->Start();
simulator->Redirect(test_case.redirect_url);
simulator->Commit();
EXPECT_EQ(test_case.expect_strong_blocker,
popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, MatchingURL_BlocksPopupAndLogs) {
const GURL url("https://example.test/");
MarkUrlAsAbusiveEnforce(url);
NavigateAndCommit(url);
EXPECT_TRUE(GetMainFrameConsoleMessages().empty());
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
EXPECT_EQ(1u, GetMainFrameConsoleMessages().size());
EXPECT_EQ(GetMainFrameConsoleMessages().front(), kAbusiveEnforceMessage);
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
MatchingURL_BlocksPopupFromOpenURL) {
const GURL url("https://example.test/");
MarkUrlAsAbusiveEnforce(url);
NavigateAndCommit(url);
// If the popup is coming from OpenURL params, the strong popup blocker is
// only going to look at the triggering event info. It will only block the
// popup if we know the triggering event is untrusted.
GURL popup_url("https://example.popup/");
content::OpenURLParams params(
popup_url, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, true /* is_renderer_initiated */);
params.user_gesture = true;
params.triggering_event_info =
blink::WebTriggeringEventInfo::kFromUntrustedEvent;
NavigateParams nav_params(profile(), popup_url, ui::PAGE_TRANSITION_LINK);
nav_params.FillNavigateParamsFromOpenURLParams(params);
nav_params.source_contents = web_contents();
nav_params.user_gesture = true;
MaybeBlockPopup(web_contents(), base::nullopt, &nav_params, &params,
blink::mojom::WindowFeatures());
EXPECT_EQ(1u, PopupBlockerTabHelper::FromWebContents(web_contents())
->GetBlockedPopupsCount());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
MatchingURLTrusted_DoesNotBlockPopup) {
const GURL url("https://example.test/");
MarkUrlAsAbusiveEnforce(url);
NavigateAndCommit(url);
// If the popup is coming from OpenURL params, the strong popup blocker is
// only going to look at the triggering event info. It will only block the
// popup if we know the triggering event is untrusted.
GURL popup_url("https://example.popup/");
content::OpenURLParams params(
popup_url, content::Referrer(), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, true /* is_renderer_initiated */);
params.user_gesture = true;
params.triggering_event_info =
blink::WebTriggeringEventInfo::kFromTrustedEvent;
NavigateParams nav_params(profile(), popup_url, ui::PAGE_TRANSITION_LINK);
nav_params.FillNavigateParamsFromOpenURLParams(params);
nav_params.source_contents = web_contents();
nav_params.user_gesture = true;
MaybeBlockPopup(web_contents(), base::nullopt, &nav_params, &params,
blink::mojom::WindowFeatures());
EXPECT_EQ(0u, PopupBlockerTabHelper::FromWebContents(web_contents())
->GetBlockedPopupsCount());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, NoMatch_NoBlocking) {
const GURL url("https://example.test/");
NavigateAndCommit(url);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
EXPECT_TRUE(GetMainFrameConsoleMessages().empty());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, FeatureEnabledByDefault) {
ResetFeatureAndGet();
SafeBrowsingTriggeredPopupBlocker::MaybeCreate(web_contents());
EXPECT_NE(nullptr,
SafeBrowsingTriggeredPopupBlocker::FromWebContents(web_contents()));
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, OnlyBlockOnMatchingUrls) {
const GURL url1("https://example.first/");
const GURL url2("https://example.second/");
const GURL url3("https://example.third/");
// Only mark url2 as abusive.
MarkUrlAsAbusiveEnforce(url2);
NavigateAndCommit(url1);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
NavigateAndCommit(url2);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
NavigateAndCommit(url3);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
NavigateAndCommit(url1);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
SameDocumentNavigation_MaintainsBlocking) {
const GURL url("https://example.first/");
const GURL hash_url("https://example.first/#hash");
MarkUrlAsAbusiveEnforce(url);
NavigateAndCommit(url);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
// This is merely a same document navigation, keep the popup blocker.
NavigateAndCommit(hash_url);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
FailNavigation_MaintainsBlocking) {
const GURL url("https://example.first/");
const GURL fail_url("https://example.fail/");
MarkUrlAsAbusiveEnforce(url);
NavigateAndCommit(url);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
// Abort the navigation before it commits.
content::NavigationSimulator::NavigateAndFailFromDocument(
fail_url, net::ERR_ABORTED, main_rfh());
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
// Committing an error page should probably reset the blocker though, despite
// the fact that it is probably a bug for an error page to spawn popups.
content::NavigationSimulator::NavigateAndFailFromDocument(
fail_url, net::ERR_CONNECTION_RESET, main_rfh());
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, LogActions) {
base::HistogramTester histogram_tester;
const char kActionHistogram[] = "ContentSettings.Popups.StrongBlockerActions";
int total_count = 0;
// Call this when a new histogram entry is logged. Call it multiple times if
// multiple entries are logged.
auto check_histogram = [&](SafeBrowsingTriggeredPopupBlocker::Action action,
int expected_count) {
histogram_tester.ExpectBucketCount(
kActionHistogram, static_cast<int>(action), expected_count);
total_count++;
};
const GURL url_enforce("https://example.enforce/");
const GURL url_warn("https://example.warn/");
const GURL url_nothing("https://example.nothing/");
MarkUrlAsAbusiveEnforce(url_enforce);
MarkUrlAsAbusiveWarning(url_warn);
// Navigate to an enforce site.
NavigateAndCommit(url_enforce);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kNavigation, 1);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kEnforcedSite, 1);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
// Block two popups.
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kConsidered, 1);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kBlocked, 1);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kConsidered, 2);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kBlocked, 2);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
// Only log the num blocked histogram after navigation.
histogram_tester.ExpectTotalCount(kNumBlockedHistogram, 0);
// Navigate to a warn site.
NavigateAndCommit(url_warn);
histogram_tester.ExpectBucketCount(kNumBlockedHistogram, 2, 1);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kNavigation, 2);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kWarningSite, 1);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
// Let one popup through.
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kConsidered, 3);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
// Navigate to a site not matched in Safe Browsing.
NavigateAndCommit(url_nothing);
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kNavigation, 3);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
// Let one popup through.
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
check_histogram(SafeBrowsingTriggeredPopupBlocker::Action::kConsidered, 4);
histogram_tester.ExpectTotalCount(kActionHistogram, total_count);
histogram_tester.ExpectTotalCount(kNumBlockedHistogram, 1);
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, LogBlockMetricsOnClose) {
base::HistogramTester histogram_tester;
const GURL url_enforce("https://example.enforce/");
MarkUrlAsAbusiveEnforce(url_enforce);
NavigateAndCommit(url_enforce);
EXPECT_TRUE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
histogram_tester.ExpectTotalCount(kNumBlockedHistogram, 0);
// Simulate deleting the web contents.
SimulateDeleteContents();
histogram_tester.ExpectUniqueSample(kNumBlockedHistogram, 1, 1);
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
WarningMatchWithoutAdBlockOnAbusiveSites_OnlyLogs) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
subresource_filter::kFilterAdsOnAbusiveSites);
const GURL url("https://example.test/");
MarkUrlAsAbusiveWarning(url);
NavigateAndCommit(url);
// Warning should come at navigation commit time, not at popup time.
EXPECT_EQ(1u, GetMainFrameConsoleMessages().size());
EXPECT_EQ(GetMainFrameConsoleMessages().front(), kAbusiveWarnMessage);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
WarningMatchWithAdBlockOnAbusiveSites_OnlyLogs) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
subresource_filter::kFilterAdsOnAbusiveSites);
const GURL url("https://example.test/");
MarkUrlAsAbusiveWarning(url);
NavigateAndCommit(url);
// Warning should come at navigation commit time, not at popup time.
EXPECT_EQ(2u, GetMainFrameConsoleMessages().size());
EXPECT_EQ(GetMainFrameConsoleMessages().front(), kAbusiveWarnMessage);
EXPECT_EQ(GetMainFrameConsoleMessages().back(),
subresource_filter::kActivationWarningConsoleMessage);
EXPECT_FALSE(popup_blocker()->ShouldApplyAbusivePopupBlocker());
}
TEST_F(SafeBrowsingTriggeredPopupBlockerTest, ActivationPosition) {
// Turn on the feature to perform safebrowsing on redirects.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
subresource_filter::kSafeBrowsingSubresourceFilterConsiderRedirects);
const GURL enforce_url("https://enforce.test/");
const GURL warn_url("https://warn.test/");
MarkUrlAsAbusiveEnforce(enforce_url);
MarkUrlAsAbusiveWarning(warn_url);
using subresource_filter::ActivationPosition;
struct {
std::vector<const char*> urls;
base::Optional<ActivationPosition> expected_position;
} kTestCases[] = {
{{"https://normal.test/"}, base::nullopt},
{{"https://enforce.test/"}, ActivationPosition::kOnly},
{{"https://warn.test/"}, ActivationPosition::kOnly},
{{"https://normal.test/", "https://warn.test/"},
ActivationPosition::kLast},
{{"https://normal.test/", "https://normal.test/",
"https://enforce.test/"},
ActivationPosition::kLast},
{{"https://enforce.test", "https://normal.test/", "https://warn.test/"},
ActivationPosition::kFirst},
{{"https://warn.test/", "https://normal.test/"},
ActivationPosition::kFirst},
{{"https://normal.test/", "https://enforce.test/",
"https://normal.test/"},
base::nullopt},
};
for (const auto& test_case : kTestCases) {
base::HistogramTester histograms;
const GURL& first_url = GURL(test_case.urls.front());
auto navigation_simulator =
content::NavigationSimulator::CreateRendererInitiated(first_url,
main_rfh());
for (size_t i = 1; i < test_case.urls.size(); ++i) {
navigation_simulator->Redirect(GURL(test_case.urls[i]));
}
navigation_simulator->Commit();
histograms.ExpectTotalCount(
"ContentSettings.Popups.StrongBlockerActivationPosition",
test_case.expected_position.has_value() ? 1 : 0);
if (test_case.expected_position.has_value()) {
histograms.ExpectUniqueSample(
"ContentSettings.Popups.StrongBlockerActivationPosition",
static_cast<int>(test_case.expected_position.value()), 1);
}
}
}