blob: ca89999a82721a6cc12773ba49b2813ed230913b [file] [log] [blame]
// Copyright 2019 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 <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_test_utils.h"
#include "chrome/browser/reputation/reputation_service.h"
#include "chrome/browser/reputation/reputation_web_contents_observer.h"
#include "chrome/browser/reputation/safety_tip_ui.h"
#include "chrome/browser/reputation/safety_tip_ui_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/location_bar/location_icon_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
#include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
#include "chrome/browser/ui/views/page_info/page_info_view_factory.h"
#include "chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/lookalikes/core/features.h"
#include "components/lookalikes/core/lookalike_url_util.h"
#include "components/page_info/core/features.h"
#include "components/reputation/core/safety_tip_test_utils.h"
#include "components/reputation/core/safety_tips.pb.h"
#include "components/reputation/core/safety_tips_config.h"
#include "components/safe_browsing/core/browser/db/v4_protocol_manager_util.h"
#include "components/security_interstitials/core/common_string_util.h"
#include "components/security_state/core/features.h"
#include "components/security_state/core/security_state.h"
#include "components/site_engagement/content/site_engagement_score.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/strings/grit/components_chromium_strings.h"
#include "components/strings/grit/components_strings.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/referrer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/common/features.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/range/range.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace {
enum class UIStatus {
kDisabled,
kEnabledWithDefaultFeatures,
kEnabledWithSuspiciousSites,
kEnabledWithAllFeatures,
};
// An engagement score above MEDIUM.
const int kHighEngagement = 20;
// An engagement score below MEDIUM.
const int kLowEngagement = 1;
class ClickEvent : public ui::Event {
public:
ClickEvent() : ui::Event(ui::ET_UNKNOWN, base::TimeTicks(), 0) {}
};
// A single test case for UKM collection on triggered heuristics.
// |navigated_url| is the URL that will be navigated to, and |expected_results|
// contains the heuristics that are expected to trigger and be recorded via UKM
// data.
struct HeuristicsTestCase {
GURL navigated_url;
TriggeredHeuristics expected_results;
};
// Simulates a link click navigation. We don't use
// ui_test_utils::NavigateToURL(const GURL&) because it simulates the user
// typing the URL, causing the site to have a site engagement score of at
// least LOW.
//
// This function waits for the reputation check to complete.
void NavigateToURL(Browser* browser,
const GURL& url,
WindowOpenDisposition disposition) {
// If we plan to use an existing tab, ensure that it's latch is reset.
if (disposition == WindowOpenDisposition::CURRENT_TAB) {
content::WebContents* contents =
browser->tab_strip_model()->GetActiveWebContents();
// Null web contents happen when you first create an incognito browser,
// since it doesn't create the tab until first navigation.
if (contents) {
ReputationWebContentsObserver* rep_observer =
ReputationWebContentsObserver::FromWebContents(contents);
rep_observer->reset_reputation_check_pending_for_testing();
}
}
// Otherwise*, we're creating a new tab and we don't need to do anything.
// (* Unless we're using SWITCH_TO_TAB. Then the above code will fetch the
// wrong tab, so we forbid it since we don't presently need it.)
CHECK_NE(disposition, WindowOpenDisposition::SWITCH_TO_TAB);
// Now actually navigate.
NavigateParams params(browser, url, ui::PAGE_TRANSITION_LINK);
params.initiator_origin = url::Origin::Create(GURL("about:blank"));
params.disposition = disposition;
params.is_renderer_initiated = true;
Navigate(&params);
// (Note that we don't need to wait for the load to finish, since we're
// waiting for a reputation check, which will happen even later.)
// If there's still a reputation check pending, wait for it to complete.
ReputationWebContentsObserver* rep_observer =
ReputationWebContentsObserver::FromWebContents(
params.navigated_or_inserted_contents);
if (rep_observer->reputation_check_pending_for_testing()) {
base::RunLoop loop;
rep_observer->RegisterReputationCheckCallbackForTesting(loop.QuitClosure());
loop.Run();
}
}
// Stall execution by at least |delay|. Used for ensuring durations measured for
// metrics are approximately correctly. DO NOT use for synchronization.
void DelayAtLeast(const base::TimeDelta delay) {
// PostDelayedTask guarantees a delay of at least the amount provided, so it's
// sufficient to just wait for the run loop to make its way through the queue.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), delay);
run_loop.Run();
}
void PerformMouseClickOnView(views::View* view) {
ui::AXActionData data;
data.action = ax::mojom::Action::kDoDefault;
view->HandleAccessibleAction(data);
}
bool IsUIShowing() {
return PageInfoBubbleViewBase::BUBBLE_SAFETY_TIP ==
PageInfoBubbleViewBase::GetShownBubbleType();
}
void CloseWarningIgnore(views::Widget::ClosedReason reason) {
if (!PageInfoBubbleViewBase::GetPageInfoBubbleForTesting()) {
return;
}
auto* widget =
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting()->GetWidget();
views::test::WidgetDestroyedWaiter waiter(widget);
widget->CloseWithReason(reason);
waiter.Wait();
}
// Sets the absolute Site Engagement |score| for the testing origin.
void SetEngagementScore(Browser* browser, const GURL& url, double score) {
site_engagement::SiteEngagementService::Get(browser->profile())
->ResetBaseScoreForURL(url, score);
}
// Clicks the location icon to open the page info bubble.
void OpenPageInfoBubble(Browser* browser) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
LocationIconView* location_icon_view =
browser_view->toolbar()->location_bar()->location_icon_view();
ASSERT_TRUE(location_icon_view);
ClickEvent event;
location_icon_view->ShowBubble(event);
views::BubbleDialogDelegateView* page_info =
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting();
EXPECT_NE(nullptr, page_info);
page_info->set_close_on_deactivate(false);
}
// Go to |url| in such a way as to trigger a bad reputation safety tip, by
// adding the given URL to the bad reputation blocklist. This is
// just for convenience, since how we trigger warnings will change. Even if the
// warning is triggered, it may not be shown if the URL is opened in the
// background.
//
// This function blocks the entire host + path, ignoring query parameters.
void TriggerWarningFromBlocklist(Browser* browser,
const GURL& url,
WindowOpenDisposition disposition) {
std::string host;
std::string path;
std::string query;
safe_browsing::V4ProtocolManagerUtil::CanonicalizeUrl(url, &host, &path,
&query);
// For simplicity, ignore query
reputation::SetSafetyTipBadRepPatterns({host + path});
SetEngagementScore(browser, url, kLowEngagement);
NavigateToURL(browser, url, disposition);
}
// Switches the tab at |tab_index| to the foreground, and waits for the
// OnVisibilityChanged reputation check to complete.
void SwitchToTabAndWait(const Browser* browser, int tab_index) {
base::RunLoop loop;
auto* tab_strip = browser->tab_strip_model();
auto* bg_tab = tab_strip->GetWebContentsAt(tab_index);
EXPECT_NE(browser->tab_strip_model()->active_index(), tab_index);
ReputationWebContentsObserver* rep_observer =
ReputationWebContentsObserver::FromWebContents(bg_tab);
rep_observer->RegisterReputationCheckCallbackForTesting(loop.QuitClosure());
tab_strip->ActivateTabAt(tab_index);
if (rep_observer->reputation_check_pending_for_testing()) {
loop.Run();
}
}
} // namespace
class SafetyTipPageInfoBubbleViewBrowserTest
: public InProcessBrowserTest,
public testing::WithParamInterface<UIStatus> {
protected:
UIStatus ui_status() const { return GetParam(); }
virtual bool digital_asset_links_enabled() const { return false; }
bool IsSuspiciousSiteWarningEnabled() const {
return ui_status() == UIStatus::kEnabledWithDefaultFeatures ||
ui_status() == UIStatus::kEnabledWithSuspiciousSites ||
ui_status() == UIStatus::kEnabledWithAllFeatures;
}
bool AreLookalikeWarningsEnabled() const {
// By default, lookalike detection is enabled unless explicitly turned off.
return ui_status() == UIStatus::kEnabledWithDefaultFeatures ||
ui_status() == UIStatus::kEnabledWithAllFeatures;
}
void SetUp() override {
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
enabled_features;
std::vector<base::Feature> disabled_features;
switch (ui_status()) {
case UIStatus::kDisabled:
disabled_features.push_back(security_state::features::kSafetyTipUI);
disabled_features.push_back(
lookalikes::features::kDetectTargetEmbeddingLookalikes);
break;
case UIStatus::kEnabledWithDefaultFeatures:
enabled_features.emplace_back(security_state::features::kSafetyTipUI,
base::FieldTrialParams());
break;
case UIStatus::kEnabledWithSuspiciousSites:
enabled_features.emplace_back(
security_state::features::kSafetyTipUI,
base::FieldTrialParams({{"suspicioussites", "true"},
{"topsites", "false"},
{"editdistance", "false"},
{"editdistance_siteengagement", "false"}}));
break;
case UIStatus::kEnabledWithAllFeatures:
enabled_features.emplace_back(
security_state::features::kSafetyTipUI,
base::FieldTrialParams({{"suspicioussites", "true"},
{"topsites", "true"},
{"editdistance", "true"},
{"editdistance_siteengagement", "true"}}));
enabled_features.emplace_back(
lookalikes::features::kDetectTargetEmbeddingLookalikes,
base::FieldTrialParams());
break;
}
if (digital_asset_links_enabled()) {
enabled_features.emplace_back(
lookalikes::features::kLookalikeDigitalAssetLinks,
base::FieldTrialParams());
} else {
disabled_features.push_back(
lookalikes::features::kLookalikeDigitalAssetLinks);
}
feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
reputation::InitializeSafetyTipConfig();
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
GURL GetURL(const char* hostname) const {
return embedded_test_server()->GetURL(hostname, "/title1.html");
}
GURL GetURLWithoutPath(const char* hostname) const {
return GetURL(hostname).GetWithEmptyPath();
}
void ClickLeaveButton() {
// This class is a friend to SafetyTipPageInfoBubbleView.
auto* bubble = static_cast<SafetyTipPageInfoBubbleView*>(
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting());
PerformMouseClickOnView(bubble->GetLeaveButtonForTesting());
}
void ClickLearnMoreLink() {
// This class is a friend to SafetyTipPageInfoBubbleView.
auto* bubble = static_cast<SafetyTipPageInfoBubbleView*>(
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting());
bubble->OpenHelpCenter();
}
void CheckNoButtons() {
auto* bubble = static_cast<SafetyTipPageInfoBubbleView*>(
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting());
EXPECT_FALSE(bubble->info_link_);
EXPECT_FALSE(bubble->leave_button_);
}
void CloseWarningLeaveSite(Browser* browser) {
if (ui_status() == UIStatus::kDisabled) {
return;
}
content::TestNavigationObserver navigation_observer(
browser->tab_strip_model()->GetActiveWebContents(), 1);
ClickLeaveButton();
navigation_observer.Wait();
}
bool IsUIShowingOrDisabled() {
return ui_status() == UIStatus::kDisabled ? true : IsUIShowing();
}
bool IsUIShowingOrSuspiciousSitesDisabled() {
return IsSuspiciousSiteWarningEnabled() ? IsUIShowing() : !IsUIShowing();
}
bool IsUIShowingOrAllFeaturesEnabled() {
return AreLookalikeWarningsEnabled() ? IsUIShowing() : !IsUIShowing();
}
std::u16string GetSafetyTipSummaryText() {
auto* page_info = PageInfoBubbleView::GetPageInfoBubbleForTesting();
auto* summary_label = page_info->GetViewByID(
PageInfoViewFactory::VIEW_ID_PAGE_INFO_SECURITY_SUMMARY_LABEL);
return static_cast<views::StyledLabel*>(summary_label)->GetText();
}
void CheckPageInfoShowsSafetyTipInfo(
Browser* browser,
security_state::SafetyTipStatus expected_safety_tip_status,
const GURL& expected_safe_url) {
if (ui_status() == UIStatus::kDisabled) {
return;
}
OpenPageInfoBubble(browser);
ASSERT_EQ(PageInfoBubbleViewBase::GetShownBubbleType(),
PageInfoBubbleViewBase::BubbleType::BUBBLE_PAGE_INFO);
auto* page_info = PageInfoBubbleViewBase::GetPageInfoBubbleForTesting();
ASSERT_TRUE(page_info);
switch (expected_safety_tip_status) {
case security_state::SafetyTipStatus::kBadReputation:
case security_state::SafetyTipStatus::kBadReputationIgnored:
EXPECT_EQ(GetSafetyTipSummaryText(),
l10n_util::GetStringUTF16(
IDS_PAGE_INFO_SAFETY_TIP_BAD_REPUTATION_TITLE));
break;
case security_state::SafetyTipStatus::kLookalike:
case security_state::SafetyTipStatus::kLookalikeIgnored:
EXPECT_EQ(GetSafetyTipSummaryText(),
l10n_util::GetStringFUTF16(
IDS_PAGE_INFO_SAFETY_TIP_LOOKALIKE_TITLE,
security_interstitials::common_string_util::
GetFormattedHostName(expected_safe_url)));
break;
case security_state::SafetyTipStatus::kDigitalAssetLinkMatch:
case security_state::SafetyTipStatus::kBadKeyword:
case security_state::SafetyTipStatus::kUnknown:
case security_state::SafetyTipStatus::kNone:
NOTREACHED();
break;
}
content::WebContentsAddedObserver new_tab_observer;
static_cast<views::StyledLabel*>(
page_info->GetViewByID(
PageInfoViewFactory::VIEW_ID_PAGE_INFO_SECURITY_DETAILS_LABEL))
->ClickLinkForTesting();
EXPECT_EQ(chrome::kSafetyTipHelpCenterURL,
new_tab_observer.GetWebContents()->GetVisibleURL());
}
void CheckPageInfoDoesNotShowSafetyTipInfo(Browser* browser) {
OpenPageInfoBubble(browser);
auto* page_info = static_cast<PageInfoBubbleViewBase*>(
PageInfoBubbleViewBase::GetPageInfoBubbleForTesting());
ASSERT_TRUE(page_info);
EXPECT_TRUE(
GetSafetyTipSummaryText() ==
l10n_util::GetStringUTF16(IDS_PAGE_INFO_NOT_SECURE_SUMMARY) ||
GetSafetyTipSummaryText() ==
l10n_util::GetStringUTF16(IDS_PAGE_INFO_INTERNAL_PAGE));
if (PageInfoBubbleViewBase::GetShownBubbleType() ==
PageInfoBubbleViewBase::BubbleType::BUBBLE_PAGE_INFO) {
content::WebContentsAddedObserver new_tab_observer;
static_cast<views::StyledLabel*>(
page_info->GetViewByID(
PageInfoViewFactory::VIEW_ID_PAGE_INFO_SECURITY_DETAILS_LABEL))
->ClickLinkForTesting();
EXPECT_EQ(chrome::kPageInfoHelpCenterURL,
new_tab_observer.GetWebContents()->GetVisibleURL());
}
}
// Checks that a certain amount of safety tip heuristics UKM events have been
// recorded.
void CheckRecordedHeuristicsUkmCount(size_t expected_event_count) {
std::vector<const ukm::mojom::UkmEntry*> entries =
test_ukm_recorder_->GetEntriesByName(
ukm::builders::Security_SafetyTip::kEntryName);
ASSERT_EQ(expected_event_count, entries.size());
}
// Checks that the metrics specified in |test_case| are properly recorded,
// at the index in the UKM data specified by |expected_idx|.
void CheckHeuristicsUkmRecord(const HeuristicsTestCase& test_case,
size_t expected_idx) {
std::vector<const ukm::mojom::UkmEntry*> entries =
test_ukm_recorder_->GetEntriesByName(
ukm::builders::Security_SafetyTip::kEntryName);
EXPECT_LT(expected_idx, entries.size());
test_ukm_recorder_->ExpectEntrySourceHasUrl(entries[expected_idx],
test_case.navigated_url);
test_ukm_recorder_->ExpectEntryMetric(
entries[expected_idx], "TriggeredServerSideBlocklist",
test_case.expected_results.blocklist_heuristic_triggered);
test_ukm_recorder_->ExpectEntryMetric(
entries[expected_idx], "TriggeredKeywordsHeuristics",
test_case.expected_results.keywords_heuristic_triggered);
test_ukm_recorder_->ExpectEntryMetric(
entries[expected_idx], "TriggeredLookalikeHeuristics",
test_case.expected_results.lookalike_heuristic_triggered);
}
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SafetyTipPageInfoBubbleViewBrowserTest,
::testing::Values(UIStatus::kDisabled,
// Disabled for flakiness. https://crbug.com/1113105.
// UIStatus::kEnabledWithDefaultFeatures,
UIStatus::kEnabledWithSuspiciousSites));
// Disabled for flakiness. https://crbug.com/1113105.
// UIStatus::kEnabledWithAllFeatures));
// Ensure normal sites with low engagement are not blocked.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnLowEngagement) {
auto kNavigatedUrl = GetURL("site1.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Ensure normal sites with low engagement are not blocked in incognito.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnLowEngagementIncognito) {
Browser* incognito_browser = Browser::Create(Browser::CreateParams(
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
true));
auto kNavigatedUrl = GetURL("site1.com");
SetEngagementScore(incognito_browser, kNavigatedUrl, kLowEngagement);
NavigateToURL(incognito_browser, kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(
CheckPageInfoDoesNotShowSafetyTipInfo(incognito_browser));
}
// Ensure blocked sites with high engagement are not blocked.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnHighEngagement) {
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
SetEngagementScore(browser(), kNavigatedUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Ensure blocked sites with high engagement are not blocked in incognito.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnHighEngagementIncognito) {
Browser* incognito_browser = Browser::Create(Browser::CreateParams(
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
true));
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
SetEngagementScore(incognito_browser, kNavigatedUrl, kHighEngagement);
NavigateToURL(incognito_browser, kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(
CheckPageInfoDoesNotShowSafetyTipInfo(incognito_browser));
}
// Ensure blocked sites get blocked.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest, ShowOnBlock) {
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// Ensure blocked sites that don't load don't get blocked.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest, NoShowOnError) {
auto kNavigatedUrl =
embedded_test_server()->GetURL("site1.com", "/close-socket");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Ensure blocked sites get blocked in incognito.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
ShowOnBlockIncognito) {
Browser* incognito_browser = Browser::Create(Browser::CreateParams(
browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
true));
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
NavigateToURL(incognito_browser, kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
incognito_browser, security_state::SafetyTipStatus::kBadReputation,
GURL()));
}
// Ensure same-document navigations don't close the Safety Tip.
// Regression test for crbug.com/1137661
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
StillShowAfterSameDocNav) {
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
// Generate a Safety Tip.
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
// Now generate a same-document navigation and verify the tip is still there.
NavigateToURL(browser(), GURL(kNavigatedUrl.spec() + "#fragment"),
WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// Ensure explicitly-allowed sites don't get blocked when the site is otherwise
// blocked server-side.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnAllowlist) {
auto kNavigatedUrl = GetURL("site1.com");
// Ensure a Safety Tip is triggered initially...
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
// ...but suppressed by the allowlist.
reputation::SetSafetyTipAllowlistPatterns({"site1.com/"}, {}, {});
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Ensure sites allowed by enterprise policy don't get blocked.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoShowOnEnterpriseAllowlist) {
const std::vector<const char*> kUrls = {"site1.com", "bla.site2.com",
"bla.site3.com"};
reputation::SetSafetyTipBadRepPatterns({"site1.com/", "site2.com/"});
SetEnterpriseAllowlistForTesting(browser()->profile()->GetPrefs(),
{"site1.com", "bla.site2.com", "site3.com"});
for (auto* const url : kUrls) {
NavigateToURL(browser(), GetURL(url), WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
}
// After the user clicks 'leave site', the user should end up on a safe domain.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
LeaveSiteLeavesSite) {
// The suspicious site warning doesn't have call-to-action buttons, so this
// test only applies to lookalike warnings.
if (!AreLookalikeWarningsEnabled()) {
return;
}
// This domain is a lookalike of a top domain not in the top 500.
const GURL kNavigatedUrl = GetURL("googlé.sk");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
ASSERT_TRUE(IsUIShowing());
CloseWarningLeaveSite(browser());
EXPECT_FALSE(IsUIShowing());
EXPECT_NE(kNavigatedUrl, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Test that clicking 'learn more' opens a help center article.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
LearnMoreOpensHelpCenter) {
if (!IsSuspiciousSiteWarningEnabled()) {
return;
}
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
content::WebContentsAddedObserver new_tab_observer;
ClickLearnMoreLink();
EXPECT_NE(kNavigatedUrl,
new_tab_observer.GetWebContents()->GetLastCommittedURL());
}
// Test that the Suspicious Site Safety Tip has no buttons and has the correct
// strings.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
SuspiciousSiteUI) {
if (!IsSuspiciousSiteWarningEnabled()) {
return;
}
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
ASSERT_NO_FATAL_FAILURE(CheckNoButtons());
auto* page_info = PageInfoBubbleView::GetPageInfoBubbleForTesting();
EXPECT_EQ(
page_info->GetWindowTitle(),
l10n_util::GetStringUTF16(IDS_PAGE_INFO_SAFETY_TIP_BAD_REPUTATION_TITLE));
}
// If the user clicks 'leave site', the warning should re-appear when the user
// re-visits the page.
// Flaky on Mac: https://crbug.com/1139955
// Flaky in general, test depends on subtle timing, https://crbug.com/1142769
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
DISABLED_LeaveSiteStillWarnsAfter) {
auto kNavigatedUrl = GetURL("site1.com");
if (!IsSuspiciousSiteWarningEnabled()) {
return;
}
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
CloseWarningLeaveSite(browser());
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
EXPECT_EQ(kNavigatedUrl, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// After the user closes the warning, they should still be on the same domain.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
IgnoreWarningStaysOnPage) {
if (ui_status() == UIStatus::kDisabled) {
return;
}
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
EXPECT_FALSE(IsUIShowing());
EXPECT_EQ(kNavigatedUrl, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// If the user closes the bubble, the warning should not re-appear when the user
// re-visits the page, but will still show up in PageInfo.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
IgnoreWarningStopsWarning) {
if (ui_status() == UIStatus::kDisabled) {
return;
}
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
EXPECT_EQ(kNavigatedUrl, browser()
->tab_strip_model()
->GetActiveWebContents()
->GetLastCommittedURL());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputationIgnored,
GURL()));
}
// If the UI is disabled, the page should be 'auto-ignored'.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
AlwaysIgnoresWhenDisabled) {
if (ui_status() != UIStatus::kDisabled) {
return;
}
auto kNavigatedUrl = GetURL("site1.com");
const char kHistogramName[] = "Security.SafetyTips.SafetyTipShown";
base::HistogramTester histograms;
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
histograms.ExpectBucketCount(
kHistogramName, security_state::SafetyTipStatus::kBadReputation, 1);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
histograms.ExpectBucketCount(
kHistogramName, security_state::SafetyTipStatus::kBadReputationIgnored,
1);
histograms.ExpectTotalCount(kHistogramName, 2);
}
// Non main-frame navigations should be ignored.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
IgnoreIFrameNavigations) {
const GURL kNavigatedUrl =
embedded_test_server()->GetURL("a.com", "/iframe_cross_site.html");
const GURL kFrameUrl =
embedded_test_server()->GetURL("b.com", "/title1.html");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
reputation::SetSafetyTipBadRepPatterns({"a.com/"});
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// Background tabs shouldn't open a bubble initially, but should when they
// become visible.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
BubbleWaitsForVisible) {
auto kFlaggedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kFlaggedUrl,
WindowOpenDisposition::NEW_BACKGROUND_TAB);
EXPECT_FALSE(IsUIShowing());
SwitchToTabAndWait(browser(),
browser()->tab_strip_model()->active_index() + 1);
EXPECT_TRUE(IsUIShowingOrSuspiciousSitesDisabled());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kBadReputation, GURL()));
}
// Background tabs that are errors shouldn't open a tip initially, and shouldn't
// open when they become visible, either. Test for crbug.com/1019228.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoBubbleOnErrorEvenAfterVisible) {
auto kFlaggedUrl =
embedded_test_server()->GetURL("site1.com", "/close-socket");
TriggerWarningFromBlocklist(browser(), kFlaggedUrl,
WindowOpenDisposition::NEW_BACKGROUND_TAB);
EXPECT_FALSE(IsUIShowing());
SwitchToTabAndWait(browser(),
browser()->tab_strip_model()->active_index() + 1);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Tests that Safety Tips do NOT trigger on lookalike domains that trigger an
// interstitial.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
SkipLookalikeInterstitialed) {
const GURL kNavigatedUrl = GetURL("googlé.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
}
// Tests that Safety Tips trigger on lookalike domains that don't qualify for an
// interstitial, but do not impact Page Info.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
TriggersOnLookalike) {
// This domain is a lookalike of a top domain not in the top 500.
const GURL kNavigatedUrl = GetURL("googlé.sk");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrAllFeaturesEnabled());
if (AreLookalikeWarningsEnabled()) {
ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(
browser(), security_state::SafetyTipStatus::kLookalike,
GURL("https://google.sk")));
} else {
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
}
// Tests that Safety Tips don't trigger on lookalike domains that are explicitly
// allowed by the allowlist.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoTriggersOnLookalikeAllowlist) {
// This domain is a lookalike of a top domain not in the top 500.
const GURL kNavigatedUrl = GetURL("googlé.sk");
// Ensure a Safety Tip is triggered initially...
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrAllFeaturesEnabled());
// ...but suppressed by the allowlist.
reputation::SetSafetyTipAllowlistPatterns({"xn--googl-fsa.sk/"}, {}, {});
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Tests that Safety Tips don't trigger on lookalike domains that are explicitly
// allowed by the allowlist.
// Note: UKM is tied to the heuristic triggering, so we record no UKM here since
// the heuristic doesn't trigger. This is different from the other allowlist
// where the heuristic triggers, UKM is still recorded, but no UI is shown.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoTriggersOnEmbeddedAllowlist) {
// This domain is one edit distance from one of a top 500 domain.
const GURL kNavigatedUrl = GetURL("gooogle.com");
reputation::SetSafetyTipAllowlistPatterns({}, {"google\\.com"}, {});
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
CheckRecordedHeuristicsUkmCount(0);
}
// Tests that Safety Tips trigger (or not) on lookalike domains with edit
// distance when enabled, and not otherwise.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
TriggersOnEditDistance) {
// This domain is an edit distance from google.com.
const GURL kNavigatedUrl = GetURL("goooglé.com");
const GURL kTargetUrl = GetURL("google.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_EQ(IsUIShowing(), AreLookalikeWarningsEnabled());
}
// Tests that Safety Tips don't trigger on lookalike domains that are one
// character swap away from an engaged site.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoTriggerOnCharacterSwap_SiteEngagement) {
const GURL kNavigatedUrl = GetURL("character-wsap.com");
const GURL kTargetUrl = GetURL("character-swap.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
}
// Tests that Safety Tips don't trigger on lookalike domains that are one
// character swap away from a top site.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NoTriggerOnCharacterSwap_TopSite) {
const GURL kNavigatedUrl = GetURL("goolge.com");
const GURL kTargetUrl = GetURL("google.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
}
// Tests that Safety Tips trigger on lookalike domains with tail embedding when
// enabled, and not otherwise.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
TriggersOnTailEmbedding) {
// Tail-embedding has the canonical domain at the very end of the lookalike.
const GURL kNavigatedUrl = GetURL("accounts-google.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowingOrDisabled());
}
// Tests that Safety Tips don't trigger on lookalike domains with non-tail
// target embedding.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
DoesntTriggersOnGenericTargetEmbedding) {
const GURL kNavigatedUrl = GetURL("google.com.evil.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
}
// Tests that the SafetyTipShown histogram triggers correctly.
// Flaky on all platforms: https://crbug.com/1139955
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
DISABLED_SafetyTipShownHistogram) {
const char kHistogramName[] = "Security.SafetyTips.SafetyTipShown";
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site1.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
histograms.ExpectBucketCount(kHistogramName,
security_state::SafetyTipStatus::kNone, 1);
auto kBadRepUrl = GetURL("site2.com");
TriggerWarningFromBlocklist(browser(), kBadRepUrl,
WindowOpenDisposition::CURRENT_TAB);
if (IsSuspiciousSiteWarningEnabled()) {
CloseWarningLeaveSite(browser());
}
histograms.ExpectBucketCount(
kHistogramName, security_state::SafetyTipStatus::kBadReputation, 1);
const GURL kLookalikeUrl = GetURL("googlé.sk");
SetEngagementScore(browser(), kLookalikeUrl, kLowEngagement);
NavigateToURL(browser(), kLookalikeUrl, WindowOpenDisposition::CURRENT_TAB);
// Record metrics for lookalike domains unless explicitly disabled.
histograms.ExpectBucketCount(
kHistogramName, security_state::SafetyTipStatus::kLookalike,
ui_status() == UIStatus::kEnabledWithSuspiciousSites ? 0 : 1);
histograms.ExpectTotalCount(kHistogramName, 3);
}
// Tests that the SafetyTipIgnoredPageLoad histogram triggers correctly.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
SafetyTipIgnoredPageLoadHistogram) {
if (ui_status() == UIStatus::kDisabled) {
return;
}
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
histograms.ExpectBucketCount(
"Security.SafetyTips.SafetyTipIgnoredPageLoad",
security_state::SafetyTipStatus::kBadReputationIgnored, 1);
}
// Tests that Safety Tip interactions are recorded in a histogram.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
InteractionsHistogram) {
const std::string kHistogramPrefix = "Security.SafetyTips.Interaction.";
// These histograms are only recorded when the UI feature is enabled, so bail
// out when disabled.
if (ui_status() != UIStatus::kEnabledWithAllFeatures) {
return;
}
{
// This domain is an edit distance of one from the top 500.
const GURL kNavigatedUrl = GetURL("goooglé.com");
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
base::HistogramTester histogram_tester;
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
// The histogram should not be recorded until the user has interacted with
// the safety tip.
histogram_tester.ExpectTotalCount(kHistogramPrefix + "SafetyTip_Lookalike",
0);
CloseWarningLeaveSite(browser());
histogram_tester.ExpectUniqueSample(
kHistogramPrefix + "SafetyTip_Lookalike",
SafetyTipInteraction::kLeaveSite, 1);
}
{
base::HistogramTester histogram_tester;
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
// The histogram should not be recorded until the user has interacted with
// the safety tip.
histogram_tester.ExpectTotalCount(
kHistogramPrefix + "SafetyTip_BadReputation", 0);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kDismiss, 1);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kDismissWithClose, 1);
}
// Test that the specific dismissal type is recorded correctly.
{
base::HistogramTester histogram_tester;
auto kNavigatedUrl = GetURL("site2.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
CloseWarningIgnore(views::Widget::ClosedReason::kEscKeyPressed);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kDismiss, 1);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kDismissWithEsc, 1);
}
// Test that tab close is recorded properly.
{
base::HistogramTester histogram_tester;
auto kNavigatedUrl = GetURL("site2.com");
// Prep the web contents for later observing.
NavigateToURL(browser(), GURL("about:blank"),
WindowOpenDisposition::NEW_FOREGROUND_TAB);
ReputationWebContentsObserver* rep_observer =
ReputationWebContentsObserver::FromWebContents(
browser()->tab_strip_model()->GetActiveWebContents());
// Trigger the warning in the prepped web contents.
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
// Close all tabs and wait for that to happen.
base::RunLoop loop;
rep_observer->RegisterSafetyTipCloseCallbackForTesting(loop.QuitClosure());
browser()->tab_strip_model()->CloseAllTabs();
loop.Run();
// Verify histograms.
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kCloseTab, 1);
}
// Test that tab switch is recorded properly.
{
base::HistogramTester histogram_tester;
auto kNavigatedUrl = GetURL("site2.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
NavigateToURL(browser(), GURL("about:blank"),
WindowOpenDisposition::NEW_FOREGROUND_TAB);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kSwitchTab, 1);
}
// Test that navigating away is recorded properly.
{
base::HistogramTester histogram_tester;
auto kNavigatedUrl = GetURL("site2.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
NavigateToURL(browser(), GURL("about:blank"),
WindowOpenDisposition::CURRENT_TAB);
histogram_tester.ExpectBucketCount(
kHistogramPrefix + "SafetyTip_BadReputation",
SafetyTipInteraction::kChangePrimaryPage, 1);
}
}
// Tests that the histograms recording how long the Safety Tip is open are
// recorded properly.
// Flaky on Mac: https://crbug.com/1139955
// Flaky in general, test depends on subtle timing, https://crbug.com/1142769
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
DISABLED_TimeOpenHistogram) {
if (!IsSuspiciousSiteWarningEnabled()) {
return;
}
const base::TimeDelta kMinWarningTime = base::Milliseconds(10);
// Test the histogram for no user action taken.
{
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
// Ensure that the tab is open for more than 0 ms, even in the face of bots
// with bad clocks.
DelayAtLeast(kMinWarningTime);
NavigateToURL(browser(), GURL("about:blank"),
WindowOpenDisposition::CURRENT_TAB);
auto samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.ChangePrimaryPage.SafetyTip_"
"BadReputation");
ASSERT_EQ(1u, samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), samples.front().min);
}
// Test the histogram for when the user adheres to the warning.
{
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
DelayAtLeast(kMinWarningTime);
CloseWarningLeaveSite(browser());
auto samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.LeaveSite.SafetyTip_BadReputation");
ASSERT_EQ(1u, samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), samples.front().min);
}
// Test the histogram for when the user dismisses the warning.
{
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site1.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
DelayAtLeast(kMinWarningTime);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
auto base_samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.Dismiss.SafetyTip_"
"BadReputation");
ASSERT_EQ(1u, base_samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), base_samples.front().min);
auto samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.DismissWithClose.SafetyTip_"
"BadReputation");
ASSERT_EQ(1u, samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), samples.front().min);
}
{
base::HistogramTester histograms;
auto kNavigatedUrl = GetURL("site2.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
DelayAtLeast(kMinWarningTime);
CloseWarningIgnore(views::Widget::ClosedReason::kEscKeyPressed);
auto base_samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.Dismiss.SafetyTip_"
"BadReputation");
ASSERT_EQ(1u, base_samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), base_samples.front().min);
auto samples = histograms.GetAllSamples(
"Security.SafetyTips.OpenTime.DismissWithEsc.SafetyTip_"
"BadReputation");
ASSERT_EQ(1u, samples.size());
EXPECT_LE(kMinWarningTime.InMilliseconds(), samples.front().min);
}
}
// Tests that Safety Tips aren't triggered on 'unknown' flag types from the
// component updater. This permits us to add new flag types to the component
// without breaking this release.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NotShownOnUnknownFlag) {
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipPatternsWithFlagType(
{"site1.com/"}, reputation::FlaggedPage::UNKNOWN);
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Tests that Safety Tips aren't triggered on domains flagged as 'YOUNG_DOMAIN'
// in the component. This permits us to use this flag in the component without
// breaking this release.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
NotShownOnYoungDomain) {
auto kNavigatedUrl = GetURL("site1.com");
reputation::SetSafetyTipPatternsWithFlagType(
{"site1.com/"}, reputation::FlaggedPage::YOUNG_DOMAIN);
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Ensure that the sensitive-keyword heuristic doesn't show up in PageInfo. Also
// a regression test for crbug/1061244.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
SensitiveKeywordHeuristicDoesntShowInPageInfo) {
const std::vector<const char*> kSensitiveKeywords = {"test"};
auto kNavigatedUrl = GetURL("test-secure.com");
ReputationService* rep_service = ReputationService::Get(browser()->profile());
rep_service->SetSensitiveKeywordsForTesting(kSensitiveKeywords.data(),
kSensitiveKeywords.size());
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
}
// Tests that UKM data gets properly recorded when safety tip heuristics get
// triggered.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
DISABLED_HeuristicsUkmRecorded) {
const std::vector<const char*> kSensitiveKeywords = {"test"};
ReputationService* rep_service = ReputationService::Get(browser()->profile());
rep_service->SetSensitiveKeywordsForTesting(kSensitiveKeywords.data(),
kSensitiveKeywords.size());
// Note that we only want the lookalike heuristic to trigger when our UI
// status is fully enabled (if it's not, our lookalike heuristic shouldn't
// trigger).
const std::vector<HeuristicsTestCase> test_cases = {
/*blocklist*/ /*lookalike*/ /*keywords*/
{GetURL("test.com"), {false, false, false}},
{GetURL("test-secure.com"), {false, false, true}},
{GetURL("test-insecure.com"), {false, false, true}},
{GetURL("test-blocklist.com"), {true, false, true}},
{GetURL("other.com"), {false, false, false}},
{GetURL("other-insecure.com"), {false, false, false}},
{GetURL("other-insecure.com"), {false, false, false}},
{GetURL("noblocklist.com"), {false, false, false}},
{GetURL("blocklist.com"), {true, false, false}},
{GetURL("a-normal-site.com"), {false, false, false}},
{GetURL("googlé.sk"),
{false, ui_status() != UIStatus::kEnabledWithSuspiciousSites, false}},
{GetURL("test-secure.com"),
{true, false,
true}}, // This test case expects multiple heuristics to trigger.
{GetURL("googlé.sk"),
{true, ui_status() != UIStatus::kEnabledWithSuspiciousSites, false}},
};
for (const HeuristicsTestCase& test_case : test_cases) {
// If we want the blocklist heuristic to trigger here, actually make it
// trigger manually.
if (test_case.expected_results.blocklist_heuristic_triggered) {
TriggerWarningFromBlocklist(browser(), test_case.navigated_url,
WindowOpenDisposition::CURRENT_TAB);
} else {
SetEngagementScore(browser(), test_case.navigated_url, kLowEngagement);
NavigateToURL(browser(), test_case.navigated_url,
WindowOpenDisposition::CURRENT_TAB);
}
// If a warning should show, dismiss it to ensure UKM data gets recorded.
if ((test_case.expected_results.lookalike_heuristic_triggered ||
test_case.expected_results.blocklist_heuristic_triggered) &&
IsSuspiciousSiteWarningEnabled()) {
CloseWarningLeaveSite(browser());
}
}
size_t expected_event_count =
std::count_if(test_cases.begin(), test_cases.end(),
[](const HeuristicsTestCase& test_case) {
return test_case.expected_results.triggered_any();
});
CheckRecordedHeuristicsUkmCount(expected_event_count);
size_t expected_event_idx = 0;
for (const HeuristicsTestCase& test_case : test_cases) {
if (!test_case.expected_results.triggered_any()) {
continue;
}
CheckHeuristicsUkmRecord(test_case, expected_event_idx);
expected_event_idx++;
}
}
// Tests that UKM data is only recorded after the safety tip warning is
// dismissed or accepted, for the lookalike heuristic.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
WarningDismissalCausesUkmRecordingForLookalike) {
// Only test when the lookalike UI is actually enabled.
if (!AreLookalikeWarningsEnabled()) {
return;
}
GURL kNavigatedUrl = GetURL("googlé.sk");
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
// Make sure that the UI is now showing, and that no UKM data has been
// recorded yet.
ASSERT_TRUE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(0);
// Once we close the warning, ensure that the UI is no longer showing, and
// that UKM data has now been recorded.
CloseWarningLeaveSite(browser());
ASSERT_FALSE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(1);
CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
// Navigate to the same site again, but close the warning with an ignore
// instead of an accept. This should still record UKM data.
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
ASSERT_TRUE(IsUIShowing());
// Make sure the already collected UKM data still exists.
CheckRecordedHeuristicsUkmCount(1);
CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
ASSERT_FALSE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(2);
CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 0);
CheckHeuristicsUkmRecord({kNavigatedUrl, {false, true, false}}, 1);
}
// Tests that UKM data is only recorded after the safety tip warning is
// dismissed or accepted, for the blocklist heuristic.
// Flaky on all platforms: https://crbug.com/1139955
IN_PROC_BROWSER_TEST_P(
SafetyTipPageInfoBubbleViewBrowserTest,
DISABLED_WarningDismissalCausesUkmRecordingForBlocklist) {
// Only test when any UI is actually enabled.
if (!IsSuspiciousSiteWarningEnabled()) {
return;
}
GURL kNavigatedUrl = GetURL("www.blocklist.com");
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
// Make sure that the UI is now showing, and that no UKM data has been
// recorded yet.
ASSERT_TRUE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(0);
// Once we close the warning, ensure that the UI is no longer showing, and
// that UKM data has now been recorded.
CloseWarningLeaveSite(browser());
ASSERT_FALSE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(1);
CheckHeuristicsUkmRecord({kNavigatedUrl, {true, false, false}}, 0);
// Navigate to the same site again, but close the warning with an ignore
// instead of an accept. This should still record UKM data.
TriggerWarningFromBlocklist(browser(), kNavigatedUrl,
WindowOpenDisposition::CURRENT_TAB);
ASSERT_TRUE(IsUIShowing());
// Make sure the already collected UKM data still exists.
CheckRecordedHeuristicsUkmCount(1);
CheckHeuristicsUkmRecord({kNavigatedUrl, {true, false, false}}, 0);
CloseWarningIgnore(views::Widget::ClosedReason::kCloseButtonClicked);
ASSERT_FALSE(IsUIShowing());
CheckRecordedHeuristicsUkmCount(2);
CheckHeuristicsUkmRecord({kNavigatedUrl, {true, false, false}}, 0);
CheckHeuristicsUkmRecord({kNavigatedUrl, {true, false, false}}, 1);
}
// Test fixture that enables the |kSafetyTipUIForSimplifiedDomainDisplay|
// feature and disables other Safety Tips features.
class SafetyTipSimplifiedDomainPageInfoBubbleViewBrowserTest
: public InProcessBrowserTest {
protected:
void SetUp() override {
feature_list_.InitWithFeatures(
{security_state::features::kSafetyTipUIForSimplifiedDomainDisplay},
{security_state::features::kSafetyTipUI,
security_state::features::kSafetyTipUIOnDelayedWarning});
reputation::InitializeSafetyTipConfig();
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Tests that the Safety Tips for simplified domains feature enables lookalike
// Safety Tips.
IN_PROC_BROWSER_TEST_F(SafetyTipSimplifiedDomainPageInfoBubbleViewBrowserTest,
SafetyTipForSimplifiedDomain) {
// This domain is a lookalike of a top domain not in the top 500.
const GURL url = embedded_test_server()->GetURL("googlé.sk", "/title1.html");
SetEngagementScore(browser(), url, kLowEngagement);
NavigateToURL(browser(), url, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowing());
}
// Tests that the Safety Tips for simplified domains feature does not enable the
// bad reputation Safety Tip.
IN_PROC_BROWSER_TEST_F(SafetyTipSimplifiedDomainPageInfoBubbleViewBrowserTest,
SafetyTipForSimplifiedDomainNoBadRep) {
GURL url = embedded_test_server()->GetURL("site1.com", "/title1.html");
reputation::SetSafetyTipPatternsWithFlagType(
{"site1.com/"}, reputation::FlaggedPage::BAD_REP);
SetEngagementScore(browser(), url, kLowEngagement);
NavigateToURL(browser(), url, WindowOpenDisposition::CURRENT_TAB);
EXPECT_FALSE(IsUIShowing());
}
// Tests for Digital Asset Links for lookalike checks.
// TODO(meacer): Refactor the DAL code in LookalikeNavigationThrottle tests and
// reuse here.
class SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest
: public SafetyTipPageInfoBubbleViewBrowserTest {
protected:
struct TestSite {
std::string hostname;
std::string manifest;
};
bool digital_asset_links_enabled() const override { return true; }
void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
void SetUpManifests(const std::vector<TestSite>& sites) {
url_loader_interceptor_ = std::make_unique<
content::URLLoaderInterceptor>(base::BindRepeating(
&SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest::OnIntercept,
base::Unretained(this), sites));
}
static std::string MakeManifestWithTarget(const char* target_domain,
bool invalid = false) {
const char* const format = R"([{
"relation": ["%s"],
"target": {
"namespace": "web",
"site": "https://%s"
}
}])";
// Go through MakeURL to convert target_domain to punycode.
return base::StringPrintf(format,
(invalid ? "junkvalue" : "lookalikes/allowlist"),
MakeURL(target_domain).host().c_str());
}
private:
bool OnIntercept(const std::vector<TestSite>& sites,
content::URLLoaderInterceptor::RequestParams* params) {
for (const TestSite& site : sites) {
if (params->url_request.url == MakeManifestURL(site.hostname)) {
DCHECK(!site.manifest.empty());
// Serve manifest contents:
std::string headers =
"HTTP/1.1 200 OK\nContent-Type: application/json; "
"charset=utf-8\n";
content::URLLoaderInterceptor::WriteResponse(headers, site.manifest,
params->client.get());
return true;
}
// Serve site's contents:
if (params->url_request.url == MakeURL(site.hostname)) {
content::URLLoaderInterceptor::WriteResponse(
"HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\n",
"<html>Test page</html>", params->client.get());
return true;
}
}
return false;
}
static GURL MakeManifestURL(const std::string& hostname) {
return GURL("https://" + hostname + "/.well-known/assetlinks.json");
}
static GURL MakeURL(const std::string& hostname) {
return GURL("https://" + hostname);
}
std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
};
INSTANTIATE_TEST_SUITE_P(
All,
SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest,
::testing::Values(UIStatus::kEnabledWithAllFeatures));
// Lookalike and target sites' manifests don't match each other. Show the UI.
// TODO(crbug.com/1191216): Check if there is already an existing manifest
// validation happening and ignore new validation requests.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest,
ShowOnDigitalAssetLinkMismatch) {
if (!AreLookalikeWarningsEnabled()) {
return;
}
const GURL kNavigatedUrl("https://gooogle.com");
const GURL kTargetUrl("https://google.com");
const std::vector<TestSite> sites{
{kNavigatedUrl.host().c_str(), MakeManifestWithTarget("invalid.host")},
{kTargetUrl.host().c_str(), MakeManifestWithTarget("invalid.host")},
};
SetUpManifests(sites);
base::HistogramTester histograms;
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
ASSERT_TRUE(IsUIShowing());
histograms.ExpectTotalCount(
DigitalAssetLinkCrossValidator::kEventHistogramName, 2);
histograms.ExpectBucketCount(
DigitalAssetLinkCrossValidator::kEventHistogramName,
DigitalAssetLinkCrossValidator::Event::kStarted, 1);
histograms.ExpectBucketCount(
DigitalAssetLinkCrossValidator::kEventHistogramName,
DigitalAssetLinkCrossValidator::Event::kLookalikeManifestFailed, 1);
}
// Same as ShowOnDigitalAssetLinkMismatch but with valid manifests.
// An edit distance match would normally display a Safety Tip, but the lookalike
// site properly allowlisted the target site so the Safety Tip is suppressed.
// TODO(crbug.com/1191216): Check if there is already an existing manifest
// validation happening and ignore new validation requests.
IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest,
NoShowOnDigitalAssetLinkMatch) {
if (!AreLookalikeWarningsEnabled()) {
return;
}
const GURL kNavigatedUrl("https://gooogle.com");
const GURL kTargetUrl("https://google.com");
const std::vector<TestSite> sites{
{kNavigatedUrl.host().c_str(),
MakeManifestWithTarget(kTargetUrl.host().c_str())},
{kTargetUrl.host().c_str(),
MakeManifestWithTarget(kNavigatedUrl.host().c_str())},
};
SetUpManifests(sites);
base::HistogramTester histograms;
SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
SetEngagementScore(browser(), kTargetUrl, kHighEngagement);
NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
ASSERT_FALSE(IsUIShowing());
histograms.ExpectTotalCount(
DigitalAssetLinkCrossValidator::kEventHistogramName, 2);
histograms.ExpectBucketCount(
DigitalAssetLinkCrossValidator::kEventHistogramName,
DigitalAssetLinkCrossValidator::Event::kStarted, 1);
histograms.ExpectBucketCount(
DigitalAssetLinkCrossValidator::kEventHistogramName,
DigitalAssetLinkCrossValidator::Event::kValidationSucceeded, 1);
}
class SafetyTipPageInfoBubbleViewPrerenderBrowserTest
: public InProcessBrowserTest {
public:
SafetyTipPageInfoBubbleViewPrerenderBrowserTest()
: prerender_helper_(base::BindRepeating(
&SafetyTipPageInfoBubbleViewPrerenderBrowserTest::web_contents,
base::Unretained(this))) {}
~SafetyTipPageInfoBubbleViewPrerenderBrowserTest() override = default;
SafetyTipPageInfoBubbleViewPrerenderBrowserTest(
const SafetyTipPageInfoBubbleViewPrerenderBrowserTest&) = delete;
SafetyTipPageInfoBubbleViewPrerenderBrowserTest& operator=(
const SafetyTipPageInfoBubbleViewPrerenderBrowserTest&) = delete;
void SetUp() override {
feature_list_.InitWithFeatures({security_state::features::kSafetyTipUI},
{});
reputation::InitializeSafetyTipConfig();
prerender_helper_.SetUp(embedded_test_server());
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::test::PrerenderTestHelper* prerender_helper() {
return &prerender_helper_;
}
private:
content::test::PrerenderTestHelper prerender_helper_;
base::test::ScopedFeatureList feature_list_;
};
// Tests that ReputationWebContentsObserver only checks heuristics when the
// primary page navigates. It loads a page in the prerenderer, verifies that
// heuristics were not run, then navigates to the prerendered site, and verifies
// that heuristics are then run.
IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewPrerenderBrowserTest,
SafetyTipOnPrerender) {
// Start test server.
GURL url = embedded_test_server()->GetURL("/empty.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
base::RunLoop run_loop_for_prerenderer;
auto* rep_observer =
ReputationWebContentsObserver::FromWebContents(web_contents());
ASSERT_TRUE(rep_observer);
rep_observer->reset_reputation_check_pending_for_testing();
rep_observer->RegisterReputationCheckCallbackForTesting(
run_loop_for_prerenderer.QuitClosure());
ASSERT_TRUE(rep_observer->reputation_check_pending_for_testing());
auto prerender_url = embedded_test_server()->GetURL("/simple.html");
// Loads |prerender_url| in the prerenderer.
auto prerender_id = prerender_helper()->AddPrerender(prerender_url);
ASSERT_NE(content::RenderFrameHost::kNoFrameTreeNodeId, prerender_id);
content::test::PrerenderHostObserver host_observer(*web_contents(),
prerender_id);
// Waits until ReputationWebContentsObserver calls the callback.
run_loop_for_prerenderer.Run();
// |reputation_check_pending_for_testing_| is not updated since
// ReputationWebContentsObserver ignores the prerenderer.
ASSERT_TRUE(rep_observer->reputation_check_pending_for_testing());
base::RunLoop run_loop_for_primary;
rep_observer->reset_reputation_check_pending_for_testing();
rep_observer->RegisterReputationCheckCallbackForTesting(
run_loop_for_primary.QuitClosure());
ASSERT_TRUE(rep_observer->reputation_check_pending_for_testing());
// Navigates the primary page to the URL.
prerender_helper()->NavigatePrimaryPage(prerender_url);
// Waits until ReputationWebContentsObserver calls the callback.
run_loop_for_primary.Run();
// |reputation_check_pending_for_testing_| is updated to false as
// ReputationWebContentsObserver works with the primary page.
ASSERT_FALSE(rep_observer->reputation_check_pending_for_testing());
// Make sure that the prerender was activated when the main frame was
// navigated to the prerender_url.
ASSERT_TRUE(host_observer.was_activated());
}
// Ensure prerender navigations don't close the Safety Tip.
IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewPrerenderBrowserTest,
StillShowAfterPrerenderNavigation) {
GURL url = embedded_test_server()->GetURL("site1.com", "/title1.html");
reputation::SetSafetyTipBadRepPatterns({"site1.com/"});
base::HistogramTester histograms;
const char kHistogramName[] = "Security.SafetyTips.SafetyTipShown";
// Generate a Safety Tip.
content::TestNavigationObserver navigation_observer(web_contents());
NavigateToURL(browser(), url, WindowOpenDisposition::CURRENT_TAB);
EXPECT_TRUE(IsUIShowing());
histograms.ExpectTotalCount(kHistogramName, 1);
// Wait until the primary page is loaded and start a prerender.
navigation_observer.Wait();
prerender_helper()->AddPrerender(
embedded_test_server()->GetURL("site1.com", "/title2.html"));
// Ensure the tip isn't closed by prerender navigation and isn't from the
// prerendered page.
EXPECT_TRUE(IsUIShowing());
histograms.ExpectTotalCount(kHistogramName, 1);
}
class SafetyTipPageInfoBubbleViewDialogTest : public DialogBrowserTest {
public:
SafetyTipPageInfoBubbleViewDialogTest() = default;
SafetyTipPageInfoBubbleViewDialogTest(
const SafetyTipPageInfoBubbleViewDialogTest&) = delete;
SafetyTipPageInfoBubbleViewDialogTest& operator=(
const SafetyTipPageInfoBubbleViewDialogTest&) = delete;
~SafetyTipPageInfoBubbleViewDialogTest() override = default;
void ShowUi(const std::string& name) override {
auto status = security_state::SafetyTipStatus::kUnknown;
if (name == "BadReputation")
status = security_state::SafetyTipStatus::kBadReputation;
else if (name == "Lookalike")
status = security_state::SafetyTipStatus::kLookalike;
ShowSafetyTipDialog(browser()->tab_strip_model()->GetActiveWebContents(),
status, GURL("https://www.google.tld"),
base::DoNothing());
}
};
IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewDialogTest,
InvokeUi_BadReputation) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewDialogTest,
InvokeUi_Lookalike) {
ShowAndVerifyUi();
}