summaryrefslogtreecommitdiff
path: root/chromium/components/blocked_content
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/blocked_content')
-rw-r--r--chromium/components/blocked_content/BUILD.gn95
-rw-r--r--chromium/components/blocked_content/DEPS18
-rw-r--r--chromium/components/blocked_content/OWNERS5
-rw-r--r--chromium/components/blocked_content/android/BUILD.gn16
-rw-r--r--chromium/components/blocked_content/android/DEPS6
-rw-r--r--chromium/components/blocked_content/android/popup_blocked_infobar_delegate.cc141
-rw-r--r--chromium/components/blocked_content/android/popup_blocked_infobar_delegate.h61
-rw-r--r--chromium/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc133
-rw-r--r--chromium/components/blocked_content/android/res/drawable-hdpi/infobar_blocked_popups.pngbin0 -> 285 bytes
-rw-r--r--chromium/components/blocked_content/android/res/drawable-mdpi/infobar_blocked_popups.pngbin0 -> 210 bytes
-rw-r--r--chromium/components/blocked_content/android/res/drawable-xhdpi/infobar_blocked_popups.pngbin0 -> 328 bytes
-rw-r--r--chromium/components/blocked_content/android/res/drawable-xxhdpi/infobar_blocked_popups.pngbin0 -> 472 bytes
-rw-r--r--chromium/components/blocked_content/android/res/drawable-xxxhdpi/infobar_blocked_popups.pngbin0 -> 563 bytes
-rw-r--r--chromium/components/blocked_content/list_item_position.cc27
-rw-r--r--chromium/components/blocked_content/list_item_position.h36
-rw-r--r--chromium/components/blocked_content/popup_blocker.cc137
-rw-r--r--chromium/components/blocked_content/popup_blocker.h58
-rw-r--r--chromium/components/blocked_content/popup_blocker_tab_helper.cc171
-rw-r--r--chromium/components/blocked_content/popup_blocker_tab_helper.h105
-rw-r--r--chromium/components/blocked_content/popup_blocker_tab_helper_unittest.cc189
-rw-r--r--chromium/components/blocked_content/popup_navigation_delegate.h55
-rw-r--r--chromium/components/blocked_content/popup_opener_tab_helper.cc129
-rw-r--r--chromium/components/blocked_content/popup_opener_tab_helper.h105
-rw-r--r--chromium/components/blocked_content/popup_tracker.cc174
-rw-r--r--chromium/components/blocked_content/popup_tracker.h113
-rw-r--r--chromium/components/blocked_content/pref_names.cc16
-rw-r--r--chromium/components/blocked_content/pref_names.h16
-rw-r--r--chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.cc194
-rw-r--r--chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.h148
-rw-r--r--chromium/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc501
-rw-r--r--chromium/components/blocked_content/url_list_manager.cc27
-rw-r--r--chromium/components/blocked_content/url_list_manager.h47
32 files changed, 2723 insertions, 0 deletions
diff --git a/chromium/components/blocked_content/BUILD.gn b/chromium/components/blocked_content/BUILD.gn
new file mode 100644
index 00000000000..4f86ea0ffcf
--- /dev/null
+++ b/chromium/components/blocked_content/BUILD.gn
@@ -0,0 +1,95 @@
+# Copyright 2020 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.
+
+source_set("blocked_content") {
+ sources = [
+ "list_item_position.cc",
+ "list_item_position.h",
+ "popup_blocker.cc",
+ "popup_blocker.h",
+ "popup_blocker_tab_helper.cc",
+ "popup_blocker_tab_helper.h",
+ "popup_navigation_delegate.h",
+ "popup_opener_tab_helper.cc",
+ "popup_opener_tab_helper.h",
+ "popup_tracker.cc",
+ "popup_tracker.h",
+ "pref_names.cc",
+ "pref_names.h",
+ "safe_browsing_triggered_popup_blocker.cc",
+ "safe_browsing_triggered_popup_blocker.h",
+ "url_list_manager.cc",
+ "url_list_manager.h",
+ ]
+ deps = [
+ "//base",
+ "//components/content_settings/browser",
+ "//components/content_settings/core/browser",
+ "//components/embedder_support",
+ "//components/pref_registry",
+ "//components/prefs",
+ "//components/safe_browsing/content/triggers:ad_popup_trigger",
+ "//components/safe_browsing/core/db:util",
+ "//components/subresource_filter/content/browser",
+ "//components/ukm/content",
+ "//components/user_prefs",
+ "//content/public/browser",
+ "//services/metrics/public/cpp:ukm_builders",
+ "//third_party/blink/public/common",
+ ]
+ if (is_android) {
+ sources += [
+ "android/popup_blocked_infobar_delegate.cc",
+ "android/popup_blocked_infobar_delegate.h",
+ ]
+ deps += [
+ "//components/infobars/content",
+ "//components/infobars/core",
+ "//components/resources:android_resources",
+ "//components/strings:components_strings_grit",
+ ]
+ }
+}
+
+source_set("test_support") {
+ testonly = true
+ sources = [
+ "test/test_popup_navigation_delegate.cc",
+ "test/test_popup_navigation_delegate.h",
+ ]
+ deps = [
+ ":blocked_content",
+ "//third_party/blink/public/common",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "popup_blocker_tab_helper_unittest.cc",
+ "safe_browsing_triggered_popup_blocker_unittest.cc",
+ ]
+ deps = [
+ ":blocked_content",
+ ":test_support",
+ "//base",
+ "//base/test:test_support",
+ "//components/content_settings/browser",
+ "//components/content_settings/browser:test_support",
+ "//components/content_settings/core/browser",
+ "//components/subresource_filter/content/browser",
+ "//components/subresource_filter/content/browser:test_support",
+ "//components/subresource_filter/core/browser",
+ "//components/sync_preferences:test_support",
+ "//components/user_prefs",
+ "//content/test:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+ if (is_android) {
+ sources += [ "android/popup_blocked_infobar_delegate_unittest.cc" ]
+ deps += [ "//components/infobars/content" ]
+ }
+}
diff --git a/chromium/components/blocked_content/DEPS b/chromium/components/blocked_content/DEPS
new file mode 100644
index 00000000000..15f6f7aea29
--- /dev/null
+++ b/chromium/components/blocked_content/DEPS
@@ -0,0 +1,18 @@
+include_rules = [
+ "+components/content_settings/browser",
+ "+components/content_settings/core",
+ "+components/embedder_support",
+ "+components/pref_registry",
+ "+components/prefs",
+ "+components/safe_browsing",
+ "+components/subresource_filter/content/browser",
+ "+components/subresource_filter/core/browser",
+ "+components/sync_preferences",
+ "+components/ukm",
+ "+components/user_prefs",
+ "+content/public/browser",
+ "+content/public/test",
+ "+services/metrics/public",
+ "+third_party/blink/public",
+ "+ui/base",
+]
diff --git a/chromium/components/blocked_content/OWNERS b/chromium/components/blocked_content/OWNERS
new file mode 100644
index 00000000000..eff6602f728
--- /dev/null
+++ b/chromium/components/blocked_content/OWNERS
@@ -0,0 +1,5 @@
+avi@chromium.org
+jochen@chromium.org
+csharrison@chromium.org
+
+# COMPONENT: UI>Browser>PopupBlocker
diff --git a/chromium/components/blocked_content/android/BUILD.gn b/chromium/components/blocked_content/android/BUILD.gn
new file mode 100644
index 00000000000..1a42a65a8cf
--- /dev/null
+++ b/chromium/components/blocked_content/android/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2020 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.
+
+import("//build/config/android/rules.gni")
+
+android_resources("java_resources") {
+ sources = [
+ "res/drawable-hdpi/infobar_blocked_popups.png",
+ "res/drawable-mdpi/infobar_blocked_popups.png",
+ "res/drawable-xhdpi/infobar_blocked_popups.png",
+ "res/drawable-xxhdpi/infobar_blocked_popups.png",
+ "res/drawable-xxxhdpi/infobar_blocked_popups.png",
+ ]
+ custom_package = "org.chromium.components.blocked_content"
+}
diff --git a/chromium/components/blocked_content/android/DEPS b/chromium/components/blocked_content/android/DEPS
new file mode 100644
index 00000000000..61e69ecd030
--- /dev/null
+++ b/chromium/components/blocked_content/android/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+ "+components/infobars/content",
+ "+components/infobars/core",
+ "+components/resources/android",
+ "+components/strings",
+]
diff --git a/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.cc b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.cc
new file mode 100644
index 00000000000..0e4d3818aef
--- /dev/null
+++ b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.cc
@@ -0,0 +1,141 @@
+// Copyright 2013 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/blocked_content/android/popup_blocked_infobar_delegate.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "components/blocked_content/popup_blocker_tab_helper.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/content_settings/core/common/content_settings_types.h"
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/infobar.h"
+#include "components/prefs/pref_service.h"
+#include "components/resources/android/theme_resources.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace blocked_content {
+
+// static
+bool PopupBlockedInfoBarDelegate::Create(
+ infobars::ContentInfoBarManager* infobar_manager,
+ int num_popups,
+ HostContentSettingsMap* settings_map,
+ base::OnceClosure on_accept_callback) {
+ const GURL& url = infobar_manager->web_contents()->GetURL();
+ std::unique_ptr<infobars::InfoBar> infobar(
+ infobar_manager->CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate>(
+ new PopupBlockedInfoBarDelegate(num_popups, url, settings_map,
+ std::move(on_accept_callback)))));
+
+ // See if there is an existing popup infobar already.
+ // TODO(dfalcantara) When triggering more than one popup the infobar
+ // will be shown once, then hide then be shown again.
+ // This will be fixed once we have an in place replace infobar mechanism.
+ for (size_t i = 0; i < infobar_manager->infobar_count(); ++i) {
+ infobars::InfoBar* existing_infobar = infobar_manager->infobar_at(i);
+ if (existing_infobar->delegate()->AsPopupBlockedInfoBarDelegate()) {
+ infobar_manager->ReplaceInfoBar(existing_infobar, std::move(infobar));
+ return false;
+ }
+ }
+
+ infobar_manager->AddInfoBar(std::move(infobar));
+
+ return true;
+}
+
+PopupBlockedInfoBarDelegate::~PopupBlockedInfoBarDelegate() = default;
+
+infobars::InfoBarDelegate::InfoBarIdentifier
+PopupBlockedInfoBarDelegate::GetIdentifier() const {
+ return POPUP_BLOCKED_INFOBAR_DELEGATE_MOBILE;
+}
+
+int PopupBlockedInfoBarDelegate::GetIconId() const {
+ return IDR_ANDROID_INFOBAR_BLOCKED_POPUPS;
+}
+
+PopupBlockedInfoBarDelegate*
+PopupBlockedInfoBarDelegate::AsPopupBlockedInfoBarDelegate() {
+ return this;
+}
+
+PopupBlockedInfoBarDelegate::PopupBlockedInfoBarDelegate(
+ int num_popups,
+ const GURL& url,
+ HostContentSettingsMap* map,
+ base::OnceClosure on_accept_callback)
+ : ConfirmInfoBarDelegate(),
+ num_popups_(num_popups),
+ url_(url),
+ map_(map),
+ on_accept_callback_(std::move(on_accept_callback)) {
+ content_settings::SettingInfo setting_info;
+ std::unique_ptr<base::Value> setting = map->GetWebsiteSetting(
+ url, url, ContentSettingsType::POPUPS, std::string(), &setting_info);
+ can_show_popups_ =
+ setting_info.source != content_settings::SETTING_SOURCE_POLICY;
+}
+
+base::string16 PopupBlockedInfoBarDelegate::GetMessageText() const {
+ return l10n_util::GetPluralStringFUTF16(IDS_POPUPS_BLOCKED_INFOBAR_TEXT,
+ num_popups_);
+}
+
+int PopupBlockedInfoBarDelegate::GetButtons() const {
+ if (!can_show_popups_)
+ return 0;
+
+ int buttons = BUTTON_OK;
+
+ return buttons;
+}
+
+base::string16 PopupBlockedInfoBarDelegate::GetButtonLabel(
+ InfoBarButton button) const {
+ switch (button) {
+ case BUTTON_OK:
+ return l10n_util::GetStringUTF16(IDS_POPUPS_BLOCKED_INFOBAR_BUTTON_SHOW);
+ case BUTTON_CANCEL:
+ return l10n_util::GetStringUTF16(IDS_PERMISSION_DENY);
+ default:
+ NOTREACHED();
+ break;
+ }
+ return base::string16();
+}
+
+bool PopupBlockedInfoBarDelegate::Accept() {
+ DCHECK(can_show_popups_);
+
+ // Create exceptions.
+ map_->SetNarrowestContentSetting(url_, url_, ContentSettingsType::POPUPS,
+ CONTENT_SETTING_ALLOW);
+
+ // Launch popups.
+ content::WebContents* web_contents =
+ infobars::ContentInfoBarManager::WebContentsFromInfoBar(infobar());
+ blocked_content::PopupBlockerTabHelper* popup_blocker_helper =
+ blocked_content::PopupBlockerTabHelper::FromWebContents(web_contents);
+ DCHECK(popup_blocker_helper);
+ blocked_content::PopupBlockerTabHelper::PopupIdMap blocked_popups =
+ popup_blocker_helper->GetBlockedPopupRequests();
+ for (blocked_content::PopupBlockerTabHelper::PopupIdMap::iterator it =
+ blocked_popups.begin();
+ it != blocked_popups.end(); ++it) {
+ popup_blocker_helper->ShowBlockedPopup(it->first,
+ WindowOpenDisposition::CURRENT_TAB);
+ }
+
+ if (on_accept_callback_)
+ std::move(on_accept_callback_).Run();
+ return true;
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.h b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.h
new file mode 100644
index 00000000000..43e31266e73
--- /dev/null
+++ b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate.h
@@ -0,0 +1,61 @@
+// Copyright 2013 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_ANDROID_POPUP_BLOCKED_INFOBAR_DELEGATE_H_
+#define COMPONENTS_BLOCKED_CONTENT_ANDROID_POPUP_BLOCKED_INFOBAR_DELEGATE_H_
+
+#include "base/callback.h"
+#include "components/infobars/core/confirm_infobar_delegate.h"
+#include "url/gurl.h"
+
+namespace infobars {
+class ContentInfoBarManager;
+}
+
+class HostContentSettingsMap;
+
+namespace blocked_content {
+
+class PopupBlockedInfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+ // Creates a popup blocked infobar and delegate and adds the infobar to
+ // |infobar_manager|. Returns true if the infobar was created, and false if it
+ // replaced an existing popup infobar. |on_accept_callback| will be run if the
+ // accept button is pressed on the infobar.
+ static bool Create(infobars::ContentInfoBarManager* infobar_manager,
+ int num_popups,
+ HostContentSettingsMap* settings_map,
+ base::OnceClosure on_accept_callback);
+
+ ~PopupBlockedInfoBarDelegate() override;
+
+ PopupBlockedInfoBarDelegate(const PopupBlockedInfoBarDelegate&) = delete;
+ PopupBlockedInfoBarDelegate& operator=(const PopupBlockedInfoBarDelegate&) =
+ delete;
+
+ private:
+ PopupBlockedInfoBarDelegate(int num_popups,
+ const GURL& url,
+ HostContentSettingsMap* map,
+ base::OnceClosure on_accept_callback);
+
+ // ConfirmInfoBarDelegate:
+ infobars::InfoBarDelegate::InfoBarIdentifier GetIdentifier() const override;
+ int GetIconId() const override;
+ PopupBlockedInfoBarDelegate* AsPopupBlockedInfoBarDelegate() override;
+ base::string16 GetMessageText() const override;
+ int GetButtons() const override;
+ base::string16 GetButtonLabel(InfoBarButton button) const override;
+ bool Accept() override;
+
+ const int num_popups_;
+ const GURL url_;
+ HostContentSettingsMap* map_;
+ bool can_show_popups_;
+ base::OnceClosure on_accept_callback_;
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_ANDROID_POPUP_BLOCKED_INFOBAR_DELEGATE_H_
diff --git a/chromium/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc
new file mode 100644
index 00000000000..e9d3bdf24e3
--- /dev/null
+++ b/chromium/components/blocked_content/android/popup_blocked_infobar_delegate_unittest.cc
@@ -0,0 +1,133 @@
+// Copyright 2020 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/blocked_content/android/popup_blocked_infobar_delegate.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/blocked_content/popup_blocker_tab_helper.h"
+#include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
+#include "components/blocked_content/test/test_popup_navigation_delegate.h"
+#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/infobars/content/content_infobar_manager.h"
+#include "components/infobars/core/infobar.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace blocked_content {
+namespace {
+constexpr char kPageUrl[] = "http://example_page.test";
+constexpr char kPopupUrl[] = "http://example_popup.test";
+
+class TestInfoBarManager : public infobars::ContentInfoBarManager {
+ public:
+ explicit TestInfoBarManager(content::WebContents* web_contents)
+ : ContentInfoBarManager(web_contents) {}
+
+ // infobars::InfoBarManager:
+ std::unique_ptr<infobars::InfoBar> CreateConfirmInfoBar(
+ std::unique_ptr<ConfirmInfoBarDelegate> delegate) override {
+ return std::make_unique<infobars::InfoBar>(std::move(delegate));
+ }
+};
+
+} // namespace
+
+class PopupBlockedInfoBarDelegateTest
+ : public content::RenderViewHostTestHarness {
+ public:
+ ~PopupBlockedInfoBarDelegateTest() override {
+ settings_map_->ShutdownOnUIThread();
+ }
+
+ // content::RenderViewHostTestHarness:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+ // Make sure the SafeBrowsingTriggeredPopupBlocker is not created.
+ feature_list_.InitAndDisableFeature(kAbusiveExperienceEnforce);
+
+ HostContentSettingsMap::RegisterProfilePrefs(pref_service_.registry());
+ settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
+ &pref_service_, false, false, false, false);
+ content_settings::TabSpecificContentSettings::CreateForWebContents(
+ web_contents(),
+ std::make_unique<
+ content_settings::TestTabSpecificContentSettingsDelegate>(
+ /*prefs=*/nullptr, settings_map_.get()));
+
+ PopupBlockerTabHelper::CreateForWebContents(web_contents());
+ helper_ = PopupBlockerTabHelper::FromWebContents(web_contents());
+ infobar_manager_ = std::make_unique<TestInfoBarManager>(web_contents());
+
+ NavigateAndCommit(GURL(kPageUrl));
+ }
+
+ PopupBlockerTabHelper* helper() { return helper_; }
+
+ TestInfoBarManager* infobar_manager() { return infobar_manager_.get(); }
+
+ HostContentSettingsMap* settings_map() { return settings_map_.get(); }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+ PopupBlockerTabHelper* helper_ = nullptr;
+ sync_preferences::TestingPrefServiceSyncable pref_service_;
+ scoped_refptr<HostContentSettingsMap> settings_map_;
+ std::unique_ptr<TestInfoBarManager> infobar_manager_;
+};
+
+TEST_F(PopupBlockedInfoBarDelegateTest, ReplacesInfobarOnSecondPopup) {
+ EXPECT_TRUE(PopupBlockedInfoBarDelegate::Create(
+ infobar_manager(), 1, settings_map(), base::NullCallback()));
+ EXPECT_EQ(infobar_manager()->infobar_count(), 1u);
+ // First message should not contain "2";
+ EXPECT_FALSE(base::Contains(infobar_manager()
+ ->infobar_at(0)
+ ->delegate()
+ ->AsConfirmInfoBarDelegate()
+ ->GetMessageText(),
+ base::ASCIIToUTF16("2")));
+
+ EXPECT_FALSE(PopupBlockedInfoBarDelegate::Create(
+ infobar_manager(), 2, settings_map(), base::NullCallback()));
+ EXPECT_EQ(infobar_manager()->infobar_count(), 1u);
+ // Second message blocks 2 popups, so should contain "2";
+ EXPECT_TRUE(base::Contains(infobar_manager()
+ ->infobar_at(0)
+ ->delegate()
+ ->AsConfirmInfoBarDelegate()
+ ->GetMessageText(),
+ base::ASCIIToUTF16("2")));
+}
+
+TEST_F(PopupBlockedInfoBarDelegateTest, ShowsBlockedPopups) {
+ TestPopupNavigationDelegate::ResultHolder result;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kPopupUrl), &result),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ bool on_accept_called = false;
+ EXPECT_TRUE(PopupBlockedInfoBarDelegate::Create(
+ infobar_manager(), 1, settings_map(),
+ base::BindLambdaForTesting(
+ [&on_accept_called] { on_accept_called = true; })));
+ EXPECT_FALSE(on_accept_called);
+
+ EXPECT_TRUE(infobar_manager()
+ ->infobar_at(0)
+ ->delegate()
+ ->AsConfirmInfoBarDelegate()
+ ->Accept());
+ EXPECT_TRUE(result.did_navigate);
+ EXPECT_TRUE(on_accept_called);
+ EXPECT_EQ(settings_map()->GetContentSetting(GURL(kPageUrl), GURL(kPageUrl),
+ ContentSettingsType::POPUPS,
+ std::string()),
+ CONTENT_SETTING_ALLOW);
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/android/res/drawable-hdpi/infobar_blocked_popups.png b/chromium/components/blocked_content/android/res/drawable-hdpi/infobar_blocked_popups.png
new file mode 100644
index 00000000000..c3ebd4ecb8a
--- /dev/null
+++ b/chromium/components/blocked_content/android/res/drawable-hdpi/infobar_blocked_popups.png
Binary files differ
diff --git a/chromium/components/blocked_content/android/res/drawable-mdpi/infobar_blocked_popups.png b/chromium/components/blocked_content/android/res/drawable-mdpi/infobar_blocked_popups.png
new file mode 100644
index 00000000000..06bcee8d67b
--- /dev/null
+++ b/chromium/components/blocked_content/android/res/drawable-mdpi/infobar_blocked_popups.png
Binary files differ
diff --git a/chromium/components/blocked_content/android/res/drawable-xhdpi/infobar_blocked_popups.png b/chromium/components/blocked_content/android/res/drawable-xhdpi/infobar_blocked_popups.png
new file mode 100644
index 00000000000..48e6a6a6ec2
--- /dev/null
+++ b/chromium/components/blocked_content/android/res/drawable-xhdpi/infobar_blocked_popups.png
Binary files differ
diff --git a/chromium/components/blocked_content/android/res/drawable-xxhdpi/infobar_blocked_popups.png b/chromium/components/blocked_content/android/res/drawable-xxhdpi/infobar_blocked_popups.png
new file mode 100644
index 00000000000..98379ed4c49
--- /dev/null
+++ b/chromium/components/blocked_content/android/res/drawable-xxhdpi/infobar_blocked_popups.png
Binary files differ
diff --git a/chromium/components/blocked_content/android/res/drawable-xxxhdpi/infobar_blocked_popups.png b/chromium/components/blocked_content/android/res/drawable-xxxhdpi/infobar_blocked_popups.png
new file mode 100644
index 00000000000..43e03065686
--- /dev/null
+++ b/chromium/components/blocked_content/android/res/drawable-xxxhdpi/infobar_blocked_popups.png
Binary files differ
diff --git a/chromium/components/blocked_content/list_item_position.cc b/chromium/components/blocked_content/list_item_position.cc
new file mode 100644
index 00000000000..c4715ace14f
--- /dev/null
+++ b/chromium/components/blocked_content/list_item_position.cc
@@ -0,0 +1,27 @@
+// 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/blocked_content/list_item_position.h"
+#include "base/check_op.h"
+
+namespace blocked_content {
+
+ListItemPosition GetListItemPositionFromDistance(size_t distance,
+ size_t total_size) {
+ DCHECK(total_size);
+ if (total_size == 1u) {
+ DCHECK_EQ(0u, distance);
+ return ListItemPosition::kOnlyItem;
+ }
+
+ if (distance == 0)
+ return ListItemPosition::kFirstItem;
+
+ if (distance == total_size - 1)
+ return ListItemPosition::kLastItem;
+
+ return ListItemPosition::kMiddleItem;
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/list_item_position.h b/chromium/components/blocked_content/list_item_position.h
new file mode 100644
index 00000000000..7d63fba1423
--- /dev/null
+++ b/chromium/components/blocked_content/list_item_position.h
@@ -0,0 +1,36 @@
+// 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_LIST_ITEM_POSITION_H_
+#define COMPONENTS_BLOCKED_CONTENT_LIST_ITEM_POSITION_H_
+
+#include <cstddef>
+
+namespace blocked_content {
+
+// This enum backs a histogram. Make sure you update enums.xml if you make
+// any changes.
+//
+// Identifies an element's position in an ordered list. Used by both the
+// framebust and popup UI on desktop platforms to indicate which element was
+// clicked.
+enum class ListItemPosition : int {
+ kOnlyItem = 0,
+ kFirstItem = 1,
+ kMiddleItem = 2,
+ kLastItem = 3,
+
+ // Any new values should go before this one.
+ kMaxValue = kLastItem,
+};
+
+// Gets the list item position from the given distance/index and the total size
+// of the collection. Distance is the measure from the beginning of the
+// collection to the given element.
+ListItemPosition GetListItemPositionFromDistance(size_t distance,
+ size_t total_size);
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_LIST_ITEM_POSITION_H_
diff --git a/chromium/components/blocked_content/popup_blocker.cc b/chromium/components/blocked_content/popup_blocker.cc
new file mode 100644
index 00000000000..6b278ac2e49
--- /dev/null
+++ b/chromium/components/blocked_content/popup_blocker.cc
@@ -0,0 +1,137 @@
+// Copyright 2018 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/blocked_content/popup_blocker.h"
+
+#include <string>
+
+#include "base/check.h"
+#include "base/command_line.h"
+#include "components/blocked_content/popup_blocker_tab_helper.h"
+#include "components/blocked_content/popup_navigation_delegate.h"
+#include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/embedder_support/switches.h"
+#include "components/safe_browsing/content/triggers/ad_popup_trigger.h"
+#include "content/public/browser/page_navigator.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace blocked_content {
+namespace {
+
+// If the popup should be blocked, returns the reason why it was blocked.
+// Otherwise returns kNotBlocked.
+PopupBlockType ShouldBlockPopup(content::WebContents* web_contents,
+ const GURL* opener_url,
+ bool user_gesture,
+ const content::OpenURLParams* open_url_params,
+ HostContentSettingsMap* settings_map) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ embedder_support::kDisablePopupBlocking)) {
+ return PopupBlockType::kNotBlocked;
+ }
+ // If an explicit opener is not given, use the current committed load in this
+ // web contents. This is because A page can't spawn popups (or do anything
+ // else, either) until its load commits, so when we reach here, the popup was
+ // spawned by the NavigationController's last committed entry, not the active
+ // entry. For example, if a page opens a popup in an onunload() handler, then
+ // the active entry is the page to be loaded as we navigate away from the
+ // unloading page.
+ const GURL& url =
+ opener_url ? *opener_url : web_contents->GetLastCommittedURL();
+ if (url.is_valid() &&
+ settings_map->GetContentSetting(url, url, ContentSettingsType::POPUPS,
+ std::string()) == CONTENT_SETTING_ALLOW) {
+ return PopupBlockType::kNotBlocked;
+ }
+
+ if (!user_gesture)
+ return PopupBlockType::kNoGesture;
+
+ // This is trusted user action (e.g. shift-click), so make sure it is not
+ // blocked.
+ if (open_url_params && open_url_params->triggering_event_info !=
+ blink::TriggeringEventInfo::kFromUntrustedEvent) {
+ return PopupBlockType::kNotBlocked;
+ }
+
+ auto* safe_browsing_blocker =
+ SafeBrowsingTriggeredPopupBlocker::FromWebContents(web_contents);
+ if (safe_browsing_blocker &&
+ safe_browsing_blocker->ShouldApplyAbusivePopupBlocker()) {
+ return PopupBlockType::kAbusive;
+ }
+ return PopupBlockType::kNotBlocked;
+}
+
+// Tries to get the opener from either the |params| or |open_url_params|,
+// otherwise uses the focused frame from |web_contents| as a proxy.
+content::RenderFrameHost* GetSourceFrameForPopup(
+ PopupNavigationDelegate* params,
+ const content::OpenURLParams* open_url_params,
+ content::WebContents* web_contents) {
+ if (params->GetOpener())
+ return params->GetOpener();
+ // Make sure the source render frame host is alive before we attempt to
+ // retrieve it from |open_url_params|.
+ if (open_url_params) {
+ content::RenderFrameHost* source = content::RenderFrameHost::FromID(
+ open_url_params->source_render_frame_id,
+ open_url_params->source_render_process_id);
+ if (source)
+ return source;
+ }
+ // The focused frame is not always the frame initiating the popup navigation
+ // and is used as a fallback in case opener information is not available.
+ return web_contents->GetFocusedFrame();
+}
+
+} // namespace
+
+bool ConsiderForPopupBlocking(WindowOpenDisposition disposition) {
+ return disposition == WindowOpenDisposition::NEW_POPUP ||
+ disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
+ disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB ||
+ disposition == WindowOpenDisposition::NEW_WINDOW;
+}
+
+std::unique_ptr<PopupNavigationDelegate> MaybeBlockPopup(
+ content::WebContents* web_contents,
+ const GURL* opener_url,
+ std::unique_ptr<PopupNavigationDelegate> delegate,
+ const content::OpenURLParams* open_url_params,
+ const blink::mojom::WindowFeatures& window_features,
+ HostContentSettingsMap* settings_map) {
+ DCHECK(web_contents);
+ DCHECK(!open_url_params ||
+ open_url_params->user_gesture == delegate->GetOriginalUserGesture());
+ PopupBlockerTabHelper::LogAction(PopupBlockerTabHelper::Action::kInitiated);
+
+ // Check |popup_blocker| first since it is cheaper than ShouldBlockPopup().
+ auto* popup_blocker = PopupBlockerTabHelper::FromWebContents(web_contents);
+ if (!popup_blocker)
+ return delegate;
+
+ PopupBlockType block_type = ShouldBlockPopup(
+ web_contents, opener_url, delegate->GetOriginalUserGesture(),
+ open_url_params, settings_map);
+ if (block_type == PopupBlockType::kNotBlocked)
+ return delegate;
+
+ // AddBlockedPopup() takes ownership of the delegate, so grab the source frame
+ // first.
+ content::RenderFrameHost* source_frame =
+ GetSourceFrameForPopup(delegate.get(), open_url_params, web_contents);
+ popup_blocker->AddBlockedPopup(std::move(delegate), window_features,
+ block_type);
+ auto* trigger = safe_browsing::AdPopupTrigger::FromWebContents(web_contents);
+ if (trigger) {
+ trigger->PopupWasBlocked(source_frame);
+ }
+ return nullptr;
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/popup_blocker.h b/chromium/components/blocked_content/popup_blocker.h
new file mode 100644
index 00000000000..be9cc439ac4
--- /dev/null
+++ b/chromium/components/blocked_content/popup_blocker.h
@@ -0,0 +1,58 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_
+#define COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_
+
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "third_party/blink/public/mojom/window_features/window_features.mojom-forward.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+class HostContentSettingsMap;
+
+namespace content {
+class WebContents;
+struct OpenURLParams;
+} // namespace content
+
+namespace blocked_content {
+class PopupNavigationDelegate;
+
+// Classifies what caused a popup to be blocked.
+enum class PopupBlockType {
+ kNotBlocked,
+
+ // Popup blocked due to no user gesture.
+ kNoGesture,
+ // Popup blocked due to the abusive popup blocker.
+ kAbusive,
+};
+
+// Whether a new window opened with |disposition| would be considered for
+// popup blocking. Note that this includes more dispositions than just
+// NEW_POPUP since the popup blocker targets all new windows and tabs.
+bool ConsiderForPopupBlocking(WindowOpenDisposition disposition);
+
+// Returns null if the popup request defined by |delegate| and the optional
+// |open_url_params| should be blocked. In that case, it is also added to the
+// |blocked_popups_| container.
+//
+// |opener_url| is an optional parameter used to compute how the popup
+// permission will behave. If it is nullptr, the current committed URL will be
+// used instead.
+//
+// If this function returns a non-null unique_ptr, the navigation was not
+// blocked and should be continued.
+std::unique_ptr<PopupNavigationDelegate> MaybeBlockPopup(
+ content::WebContents* web_contents,
+ const GURL* opener_url,
+ std::unique_ptr<PopupNavigationDelegate> delegate,
+ const content::OpenURLParams* open_url_params,
+ const blink::mojom::WindowFeatures& window_features,
+ HostContentSettingsMap* settings_map);
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_H_
diff --git a/chromium/components/blocked_content/popup_blocker_tab_helper.cc b/chromium/components/blocked_content/popup_blocker_tab_helper.cc
new file mode 100644
index 00000000000..c3537d88c0e
--- /dev/null
+++ b/chromium/components/blocked_content/popup_blocker_tab_helper.cc
@@ -0,0 +1,171 @@
+// Copyright 2013 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/blocked_content/popup_blocker_tab_helper.h"
+
+#include <iterator>
+#include <string>
+
+#include "base/metrics/histogram_macros.h"
+#include "build/build_config.h"
+#include "components/blocked_content/list_item_position.h"
+#include "components/blocked_content/popup_navigation_delegate.h"
+#include "components/blocked_content/popup_tracker.h"
+#include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
+#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "content/public/browser/back_forward_cache.h"
+#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/page_navigator.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
+
+namespace blocked_content {
+const size_t kMaximumNumberOfPopups = 25;
+
+struct PopupBlockerTabHelper::BlockedRequest {
+ BlockedRequest(std::unique_ptr<PopupNavigationDelegate> delegate,
+ const blink::mojom::WindowFeatures& window_features,
+ PopupBlockType block_type)
+ : delegate(std::move(delegate)),
+ window_features(window_features),
+ block_type(block_type) {}
+
+ std::unique_ptr<PopupNavigationDelegate> delegate;
+ blink::mojom::WindowFeatures window_features;
+ PopupBlockType block_type;
+};
+
+PopupBlockerTabHelper::PopupBlockerTabHelper(content::WebContents* web_contents)
+ : content::WebContentsObserver(web_contents) {
+ blocked_content::SafeBrowsingTriggeredPopupBlocker::MaybeCreate(web_contents);
+}
+
+PopupBlockerTabHelper::~PopupBlockerTabHelper() = default;
+
+void PopupBlockerTabHelper::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ // Clear all page actions, blocked content notifications and browser actions
+ // for this tab, unless this is an same-document navigation. Also only
+ // consider main frame navigations that successfully committed.
+ if (!navigation_handle->IsInMainFrame() ||
+ !navigation_handle->HasCommitted() ||
+ navigation_handle->IsSameDocument()) {
+ return;
+ }
+
+ // Close blocked popups.
+ if (!blocked_popups_.empty()) {
+ blocked_popups_.clear();
+ HidePopupNotification();
+
+ // With back-forward cache we can restore the page, but |blocked_popups_|
+ // are lost here and can't be restored at the moment.
+ // Disable bfcache here to avoid potential loss of the page state.
+ web_contents()
+ ->GetController()
+ .GetBackForwardCache()
+ .DisableForRenderFrameHost(
+ navigation_handle->GetPreviousRenderFrameHostId(),
+ "PopupBlockerTabHelper");
+ }
+}
+
+void PopupBlockerTabHelper::HidePopupNotification() {
+ auto* tscs = content_settings::TabSpecificContentSettings::FromWebContents(
+ web_contents());
+ if (tscs)
+ tscs->ClearPopupsBlocked();
+}
+
+void PopupBlockerTabHelper::AddBlockedPopup(
+ std::unique_ptr<PopupNavigationDelegate> delegate,
+ const blink::mojom::WindowFeatures& window_features,
+ PopupBlockType block_type) {
+ LogAction(Action::kBlocked);
+ if (blocked_popups_.size() >= kMaximumNumberOfPopups)
+ return;
+
+ int id = next_id_;
+ next_id_++;
+ blocked_popups_[id] = std::make_unique<BlockedRequest>(
+ std::move(delegate), window_features, block_type);
+ content_settings::TabSpecificContentSettings::FromWebContents(web_contents())
+ ->OnContentBlocked(ContentSettingsType::POPUPS);
+ auto* raw_delegate = blocked_popups_[id]->delegate.get();
+ manager_.NotifyObservers(id, raw_delegate->GetURL());
+
+ raw_delegate->OnPopupBlocked(web_contents(), GetBlockedPopupsCount());
+}
+
+void PopupBlockerTabHelper::ShowBlockedPopup(
+ int32_t id,
+ WindowOpenDisposition disposition) {
+ auto it = blocked_popups_.find(id);
+ if (it == blocked_popups_.end())
+ return;
+
+ blocked_content::ListItemPosition position =
+ blocked_content::GetListItemPositionFromDistance(
+ std::distance(blocked_popups_.begin(), it), blocked_popups_.size());
+
+ UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.ClickThroughPosition",
+ position);
+
+ BlockedRequest* popup = it->second.get();
+
+ base::Optional<WindowOpenDisposition> updated_disposition;
+ if (disposition != WindowOpenDisposition::CURRENT_TAB)
+ updated_disposition = disposition;
+
+ PopupNavigationDelegate::NavigateResult result =
+ popup->delegate->NavigateWithGesture(popup->window_features,
+ updated_disposition);
+ if (result.navigated_or_inserted_contents) {
+ auto* tracker = blocked_content::PopupTracker::CreateForWebContents(
+ result.navigated_or_inserted_contents, web_contents(),
+ result.disposition);
+ tracker->set_is_trusted(true);
+ }
+
+ switch (popup->block_type) {
+ case PopupBlockType::kNotBlocked:
+ NOTREACHED();
+ break;
+ case PopupBlockType::kNoGesture:
+ LogAction(Action::kClickedThroughNoGesture);
+ break;
+ case PopupBlockType::kAbusive:
+ LogAction(Action::kClickedThroughAbusive);
+ break;
+ }
+
+ blocked_popups_.erase(id);
+ if (blocked_popups_.empty())
+ HidePopupNotification();
+}
+
+size_t PopupBlockerTabHelper::GetBlockedPopupsCount() const {
+ return blocked_popups_.size();
+}
+
+PopupBlockerTabHelper::PopupIdMap
+PopupBlockerTabHelper::GetBlockedPopupRequests() {
+ PopupIdMap result;
+ for (const auto& it : blocked_popups_) {
+ result[it.first] = it.second->delegate->GetURL();
+ }
+ return result;
+}
+
+// static
+void PopupBlockerTabHelper::LogAction(Action action) {
+ UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.BlockerActions", action);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(PopupBlockerTabHelper)
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/popup_blocker_tab_helper.h b/chromium/components/blocked_content/popup_blocker_tab_helper.h
new file mode 100644
index 00000000000..8d1381f718e
--- /dev/null
+++ b/chromium/components/blocked_content/popup_blocker_tab_helper.h
@@ -0,0 +1,105 @@
+// Copyright 2013 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_TAB_HELPER_H_
+#define COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_TAB_HELPER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+
+#include "base/macros.h"
+#include "components/blocked_content/popup_blocker.h"
+#include "components/blocked_content/url_list_manager.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+namespace blocked_content {
+class PopupNavigationDelegate;
+
+// Per-tab class to manage blocked popups.
+class PopupBlockerTabHelper
+ : public content::WebContentsObserver,
+ public content::WebContentsUserData<PopupBlockerTabHelper> {
+ public:
+ // Mapping from popup IDs to blocked popup requests.
+ typedef std::map<int32_t, GURL> PopupIdMap;
+
+ // This enum is backed by a histogram. Make sure enums.xml is updated if this
+ // is updated.
+ enum class Action : int {
+ // A popup was initiated and was sent to the popup blocker for
+ // consideration.
+ kInitiated = 0,
+
+ // A popup was blocked by the popup blocker.
+ kBlocked = 1,
+
+ // A previously blocked popup was clicked through. For popups blocked
+ // without a user gesture.
+ kClickedThroughNoGesture = 2,
+
+ // A previously blocked popup was clicked through. For popups blocked
+ // due to the abusive popup blocker.
+ kClickedThroughAbusive = 3,
+
+ // Add new elements before this value.
+ kMaxValue = kClickedThroughAbusive
+ };
+
+ ~PopupBlockerTabHelper() override;
+
+ // Returns the number of blocked popups.
+ size_t GetBlockedPopupsCount() const;
+
+ PopupIdMap GetBlockedPopupRequests();
+
+ // Creates the blocked popup with |popup_id| in given |dispostion|.
+ // Note that if |disposition| is WindowOpenDisposition::CURRENT_TAB,
+ // blocked popup will be opened as it was specified by renderer.
+ void ShowBlockedPopup(int32_t popup_id, WindowOpenDisposition disposition);
+
+ // Adds a new blocked popup to the UI.
+ void AddBlockedPopup(std::unique_ptr<PopupNavigationDelegate> delegate,
+ const blink::mojom::WindowFeatures& window_features,
+ PopupBlockType block_type);
+
+ // content::WebContentsObserver overrides:
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+
+ // Logs a histogram measuring popup blocker actions.
+ static void LogAction(Action action);
+
+ blocked_content::UrlListManager* manager() { return &manager_; }
+
+ private:
+ struct BlockedRequest;
+ friend class content::WebContentsUserData<PopupBlockerTabHelper>;
+
+ explicit PopupBlockerTabHelper(content::WebContents* web_contents);
+
+ // Called when the blocked popup notification is hidden.
+ void HidePopupNotification();
+
+ blocked_content::UrlListManager manager_;
+
+ // Note, this container should be sorted based on the position in the popup
+ // list, so it is keyed by an id which is continually increased.
+ std::map<int32_t, std::unique_ptr<BlockedRequest>> blocked_popups_;
+
+ int32_t next_id_ = 0;
+
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ DISALLOW_COPY_AND_ASSIGN(PopupBlockerTabHelper);
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_POPUP_BLOCKER_TAB_HELPER_H_
diff --git a/chromium/components/blocked_content/popup_blocker_tab_helper_unittest.cc b/chromium/components/blocked_content/popup_blocker_tab_helper_unittest.cc
new file mode 100644
index 00000000000..1eedfe0421e
--- /dev/null
+++ b/chromium/components/blocked_content/popup_blocker_tab_helper_unittest.cc
@@ -0,0 +1,189 @@
+// Copyright 2020 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/blocked_content/popup_blocker_tab_helper.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/blocked_content/popup_navigation_delegate.h"
+#include "components/blocked_content/safe_browsing_triggered_popup_blocker.h"
+#include "components/blocked_content/test/test_popup_navigation_delegate.h"
+#include "components/blocked_content/url_list_manager.h"
+#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace blocked_content {
+namespace {
+using testing::Pair;
+using testing::UnorderedElementsAre;
+
+constexpr char kUrl1[] = "http://example1.test";
+constexpr char kUrl2[] = "http://example2.test";
+
+// Observer which allows retrieving a map of all the blocked URLs.
+class BlockedUrlListObserver : public UrlListManager::Observer {
+ public:
+ explicit BlockedUrlListObserver(PopupBlockerTabHelper* helper) {
+ observer_.Add(helper->manager());
+ }
+ // UrlListManager::Observer:
+ void BlockedUrlAdded(int32_t id, const GURL& url) override {
+ blocked_urls_.insert({id, url});
+ }
+
+ const std::map<int32_t, GURL>& blocked_urls() const { return blocked_urls_; }
+
+ private:
+ std::map<int32_t, GURL> blocked_urls_;
+ ScopedObserver<UrlListManager, UrlListManager::Observer> observer_{this};
+};
+} // namespace
+
+class PopupBlockerTabHelperTest : public content::RenderViewHostTestHarness {
+ public:
+ ~PopupBlockerTabHelperTest() override { settings_map_->ShutdownOnUIThread(); }
+
+ // content::RenderViewHostTestHarness:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+ // Make sure the SafeBrowsingTriggeredPopupBlocker is not created.
+ feature_list_.InitAndDisableFeature(kAbusiveExperienceEnforce);
+
+ HostContentSettingsMap::RegisterProfilePrefs(pref_service_.registry());
+ settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
+ &pref_service_, false, false, false, false);
+ content_settings::TabSpecificContentSettings::CreateForWebContents(
+ web_contents(),
+ std::make_unique<
+ content_settings::TestTabSpecificContentSettingsDelegate>(
+ /*prefs=*/nullptr, settings_map_.get()));
+
+ PopupBlockerTabHelper::CreateForWebContents(web_contents());
+ helper_ = PopupBlockerTabHelper::FromWebContents(web_contents());
+ }
+
+ PopupBlockerTabHelper* helper() { return helper_; }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+ PopupBlockerTabHelper* helper_ = nullptr;
+ sync_preferences::TestingPrefServiceSyncable pref_service_;
+ scoped_refptr<HostContentSettingsMap> settings_map_;
+};
+
+TEST_F(PopupBlockerTabHelperTest, BlocksAndShowsPopup) {
+ BlockedUrlListObserver observer(helper());
+ TestPopupNavigationDelegate::ResultHolder result;
+ blink::mojom::WindowFeatures window_features;
+ window_features.has_x = true;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result),
+ window_features, PopupBlockType::kNoGesture);
+ EXPECT_EQ(result.total_popups_blocked_on_page, 1);
+ EXPECT_FALSE(result.did_navigate);
+ EXPECT_THAT(observer.blocked_urls(),
+ UnorderedElementsAre(Pair(0, GURL(kUrl1))));
+
+ helper()->ShowBlockedPopup(0, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_TRUE(result.did_navigate);
+ EXPECT_TRUE(result.navigation_window_features.has_x);
+ EXPECT_EQ(result.navigation_disposition,
+ WindowOpenDisposition::NEW_FOREGROUND_TAB);
+}
+
+TEST_F(PopupBlockerTabHelperTest, MultiplePopups) {
+ BlockedUrlListObserver observer(helper());
+ TestPopupNavigationDelegate::ResultHolder result1;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result1),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_EQ(result1.total_popups_blocked_on_page, 1);
+ EXPECT_THAT(observer.blocked_urls(),
+ UnorderedElementsAre(Pair(0, GURL(kUrl1))));
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 1u);
+
+ TestPopupNavigationDelegate::ResultHolder result2;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl2), &result2),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_EQ(result2.total_popups_blocked_on_page, 2);
+ EXPECT_THAT(observer.blocked_urls(),
+ UnorderedElementsAre(Pair(0, GURL(kUrl1)), Pair(1, GURL(kUrl2))));
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 2u);
+
+ helper()->ShowBlockedPopup(1, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 1u);
+ EXPECT_TRUE(result2.did_navigate);
+ EXPECT_EQ(result2.navigation_disposition,
+ WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_FALSE(result1.did_navigate);
+
+ helper()->ShowBlockedPopup(0, WindowOpenDisposition::CURRENT_TAB);
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 0u);
+ EXPECT_TRUE(result1.did_navigate);
+ EXPECT_FALSE(result1.navigation_disposition.has_value());
+}
+
+TEST_F(PopupBlockerTabHelperTest, DoesNotShowPopupWithInvalidID) {
+ TestPopupNavigationDelegate::ResultHolder result;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 1u);
+
+ // Invalid ID should not do anything.
+ helper()->ShowBlockedPopup(1, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 1u);
+ EXPECT_FALSE(result.did_navigate);
+
+ helper()->ShowBlockedPopup(0, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_EQ(helper()->GetBlockedPopupsCount(), 0u);
+ EXPECT_TRUE(result.did_navigate);
+}
+
+TEST_F(PopupBlockerTabHelperTest, SetsContentSettingsPopupState) {
+ auto* content_settings =
+ content_settings::TabSpecificContentSettings::FromWebContents(
+ web_contents());
+ EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
+
+ TestPopupNavigationDelegate::ResultHolder result;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
+
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl2), &result),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
+
+ helper()->ShowBlockedPopup(0, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_TRUE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
+
+ helper()->ShowBlockedPopup(1, WindowOpenDisposition::NEW_FOREGROUND_TAB);
+ EXPECT_FALSE(content_settings->IsContentBlocked(ContentSettingsType::POPUPS));
+}
+
+TEST_F(PopupBlockerTabHelperTest, ClearsContentSettingsPopupStateOnNavigation) {
+ TestPopupNavigationDelegate::ResultHolder result;
+ helper()->AddBlockedPopup(
+ std::make_unique<TestPopupNavigationDelegate>(GURL(kUrl1), &result),
+ blink::mojom::WindowFeatures(), PopupBlockType::kNoGesture);
+ EXPECT_TRUE(content_settings::TabSpecificContentSettings::FromWebContents(
+ web_contents())
+ ->IsContentBlocked(ContentSettingsType::POPUPS));
+
+ NavigateAndCommit(GURL(kUrl2));
+ EXPECT_FALSE(content_settings::TabSpecificContentSettings::FromWebContents(
+ web_contents())
+ ->IsContentBlocked(ContentSettingsType::POPUPS));
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/popup_navigation_delegate.h b/chromium/components/blocked_content/popup_navigation_delegate.h
new file mode 100644
index 00000000000..8c7cb02e33b
--- /dev/null
+++ b/chromium/components/blocked_content/popup_navigation_delegate.h
@@ -0,0 +1,55 @@
+// Copyright 2020 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_NAVIGATION_DELEGATE_H_
+#define COMPONENTS_BLOCKED_CONTENT_POPUP_NAVIGATION_DELEGATE_H_
+
+#include <memory>
+
+#include "base/optional.h"
+#include "third_party/blink/public/mojom/window_features/window_features.mojom-forward.h"
+#include "ui/base/window_open_disposition.h"
+
+class GURL;
+
+namespace content {
+class RenderFrameHost;
+class WebContents;
+} // namespace content
+
+namespace blocked_content {
+
+// A delegate interface to allow an embedder specific representation of a
+// navigation. This is stored internally in the popup blocker to recover
+// navigations when the user clicks through a previously blocked popup.
+class PopupNavigationDelegate {
+ public:
+ virtual ~PopupNavigationDelegate() = default;
+
+ // Gets the opener used if new WebContents are created for this navigation.
+ virtual content::RenderFrameHost* GetOpener() = 0;
+
+ // Gets whether the blocked navigation was initiated by a user gesture.
+ virtual bool GetOriginalUserGesture() = 0;
+
+ // Gets the URL to be loaded.
+ virtual const GURL& GetURL() = 0;
+
+ // Performs the navigation.
+ struct NavigateResult {
+ content::WebContents* navigated_or_inserted_contents = nullptr;
+ WindowOpenDisposition disposition = WindowOpenDisposition::UNKNOWN;
+ };
+ virtual NavigateResult NavigateWithGesture(
+ const blink::mojom::WindowFeatures& window_features,
+ base::Optional<WindowOpenDisposition> updated_disposition) = 0;
+
+ // Called when the navigation represented by this class was blocked.
+ virtual void OnPopupBlocked(content::WebContents* web_contents,
+ int total_popups_blocked_on_page) = 0;
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_POPUP_NAVIGATION_DELEGATE_H_
diff --git a/chromium/components/blocked_content/popup_opener_tab_helper.cc b/chromium/components/blocked_content/popup_opener_tab_helper.cc
new file mode 100644
index 00000000000..b0707531d5b
--- /dev/null
+++ b/chromium/components/blocked_content/popup_opener_tab_helper.cc
@@ -0,0 +1,129 @@
+// 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/blocked_content/popup_opener_tab_helper.h"
+
+#include <utility>
+
+#include "base/check.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/tick_clock.h"
+#include "components/blocked_content/popup_tracker.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings.h"
+#include "components/ukm/content/source_url_recorder.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+#include "ui/base/scoped_visibility_tracker.h"
+
+namespace blocked_content {
+
+// static
+void PopupOpenerTabHelper::CreateForWebContents(
+ content::WebContents* contents,
+ const base::TickClock* tick_clock,
+ HostContentSettingsMap* settings_map) {
+ DCHECK(contents);
+ if (!FromWebContents(contents)) {
+ contents->SetUserData(UserDataKey(),
+ base::WrapUnique(new PopupOpenerTabHelper(
+ contents, tick_clock, settings_map)));
+ }
+}
+
+PopupOpenerTabHelper::~PopupOpenerTabHelper() {
+ DCHECK(visibility_tracker_);
+ base::TimeDelta total_visible_time =
+ visibility_tracker_->GetForegroundDuration();
+ if (did_tab_under()) {
+ UMA_HISTOGRAM_LONG_TIMES(
+ "Tab.TabUnder.VisibleTime",
+ total_visible_time - visible_time_before_tab_under_.value());
+ UMA_HISTOGRAM_LONG_TIMES("Tab.TabUnder.VisibleTimeBefore",
+ visible_time_before_tab_under_.value());
+ }
+ UMA_HISTOGRAM_LONG_TIMES("Tab.VisibleTime", total_visible_time);
+}
+
+void PopupOpenerTabHelper::OnOpenedPopup(PopupTracker* popup_tracker) {
+ has_opened_popup_since_last_user_gesture_ = true;
+ MaybeLogPagePopupContentSettings();
+
+ last_popup_open_time_ = tick_clock_->NowTicks();
+}
+
+void PopupOpenerTabHelper::OnDidTabUnder() {
+ // The tab already did a tab-under.
+ if (did_tab_under())
+ return;
+
+ // Tab-under requires a popup, so this better not be null.
+ DCHECK(!last_popup_open_time_.is_null());
+ UMA_HISTOGRAM_LONG_TIMES("Tab.TabUnder.PopupToTabUnderTime",
+ tick_clock_->NowTicks() - last_popup_open_time_);
+
+ visible_time_before_tab_under_ = visibility_tracker_->GetForegroundDuration();
+}
+
+PopupOpenerTabHelper::PopupOpenerTabHelper(content::WebContents* web_contents,
+ const base::TickClock* tick_clock,
+ HostContentSettingsMap* settings_map)
+ : content::WebContentsObserver(web_contents),
+ tick_clock_(tick_clock),
+ settings_map_(settings_map) {
+ visibility_tracker_ = std::make_unique<ui::ScopedVisibilityTracker>(
+ tick_clock_,
+ web_contents->GetVisibility() != content::Visibility::HIDDEN);
+}
+
+void PopupOpenerTabHelper::OnVisibilityChanged(content::Visibility visibility) {
+ // TODO(csharrison): Consider handling OCCLUDED tabs the same way as HIDDEN
+ // tabs.
+ if (visibility == content::Visibility::HIDDEN)
+ visibility_tracker_->OnHidden();
+ else
+ visibility_tracker_->OnShown();
+}
+
+void PopupOpenerTabHelper::DidGetUserInteraction(
+ const blink::WebInputEvent::Type type) {
+ has_opened_popup_since_last_user_gesture_ = false;
+}
+
+void PopupOpenerTabHelper::DidStartNavigation(
+ content::NavigationHandle* navigation_handle) {
+ // Treat browser-initiated navigations as user interactions.
+ if (!navigation_handle->IsRendererInitiated())
+ has_opened_popup_since_last_user_gesture_ = false;
+}
+
+void PopupOpenerTabHelper::MaybeLogPagePopupContentSettings() {
+ // If the user has opened a popup, record the page popup settings ukm.
+ const GURL& url = web_contents()->GetLastCommittedURL();
+ if (!url.is_valid())
+ return;
+
+ const ukm::SourceId source_id =
+ ukm::GetSourceIdForWebContentsDocument(web_contents());
+
+ // Do not record duplicate Popup.Page events for popups opened in succession
+ // from the same opener.
+ if (source_id != last_opener_source_id_) {
+ bool user_allows_popups = settings_map_->GetContentSetting(
+ url, url, ContentSettingsType::POPUPS,
+ std::string()) == CONTENT_SETTING_ALLOW;
+ ukm::builders::Popup_Page(source_id)
+ .SetAllowed(user_allows_popups)
+ .Record(ukm::UkmRecorder::Get());
+ last_opener_source_id_ = source_id;
+ }
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(PopupOpenerTabHelper)
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/popup_opener_tab_helper.h b/chromium/components/blocked_content/popup_opener_tab_helper.h
new file mode 100644
index 00000000000..363f0fea1e4
--- /dev/null
+++ b/chromium/components/blocked_content/popup_opener_tab_helper.h
@@ -0,0 +1,105 @@
+// 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_OPENER_TAB_HELPER_H_
+#define COMPONENTS_BLOCKED_CONTENT_POPUP_OPENER_TAB_HELPER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/tick_clock.h"
+#include "base/time/time.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace base {
+class TickClock;
+}
+
+namespace content {
+class WebContents;
+}
+
+namespace ui {
+class ScopedVisibilityTracker;
+}
+
+namespace blocked_content {
+class PopupTracker;
+
+// This class tracks WebContents for the purpose of logging metrics related to
+// popup openers.
+class PopupOpenerTabHelper
+ : public content::WebContentsObserver,
+ public content::WebContentsUserData<PopupOpenerTabHelper> {
+ public:
+ // |tick_clock| overrides the internal time for testing. This doesn't take
+ // ownership of |tick_clock| or |settings_map|, and they both must outlive the
+ // PopupOpenerTabHelper instance.
+ static void CreateForWebContents(content::WebContents* contents,
+ const base::TickClock* tick_clock,
+ HostContentSettingsMap* settings_map);
+ ~PopupOpenerTabHelper() override;
+
+ void OnOpenedPopup(PopupTracker* popup_tracker);
+ void OnDidTabUnder();
+
+ bool has_opened_popup_since_last_user_gesture() const {
+ return has_opened_popup_since_last_user_gesture_;
+ }
+
+ bool did_tab_under() const {
+ return visible_time_before_tab_under_.has_value();
+ }
+
+ private:
+ friend class content::WebContentsUserData<PopupOpenerTabHelper>;
+
+ PopupOpenerTabHelper(content::WebContents* web_contents,
+ const base::TickClock* tick_clock,
+ HostContentSettingsMap* settings_map);
+
+ // content::WebContentsObserver:
+ void OnVisibilityChanged(content::Visibility visibility) override;
+ void DidStartNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void DidGetUserInteraction(const blink::WebInputEvent::Type type) override;
+
+ // Logs user popup content settings if the last committed URL is valid and
+ // we have not recorded the settings for the opener id of the helper's
+ // web contents at the time the function is called.
+ void MaybeLogPagePopupContentSettings();
+
+ // Visible time for this tab until a tab-under is detected. At which point it
+ // gets the visible time from the |visibility_tracker_|. Will be unset until a
+ // tab-under is detected.
+ base::Optional<base::TimeDelta> visible_time_before_tab_under_;
+
+ // The clock which is used by the visibility trackers.
+ const base::TickClock* tick_clock_;
+
+ // Keeps track of the total foreground time for this tab.
+ std::unique_ptr<ui::ScopedVisibilityTracker> visibility_tracker_;
+
+ // Measures the time this WebContents opened a popup.
+ base::TimeTicks last_popup_open_time_;
+
+ bool has_opened_popup_since_last_user_gesture_ = false;
+
+ // The last source id used for logging Popup_Page.
+ ukm::SourceId last_opener_source_id_ = ukm::kInvalidSourceId;
+
+ // The settings map for the web contents this object is associated with.
+ HostContentSettingsMap* settings_map_ = nullptr;
+
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ DISALLOW_COPY_AND_ASSIGN(PopupOpenerTabHelper);
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_POPUP_OPENER_TAB_HELPER_H_
diff --git a/chromium/components/blocked_content/popup_tracker.cc b/chromium/components/blocked_content/popup_tracker.cc
new file mode 100644
index 00000000000..2735f516abe
--- /dev/null
+++ b/chromium/components/blocked_content/popup_tracker.cc
@@ -0,0 +1,174 @@
+// 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/blocked_content/popup_tracker.h"
+
+#include <algorithm>
+
+#include "base/check.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/time/default_tick_clock.h"
+#include "components/blocked_content/popup_opener_tab_helper.h"
+#include "components/ukm/content/source_url_recorder.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+
+namespace blocked_content {
+namespace {
+
+int CappedUserInteractions(int user_interactions, int max_interactions) {
+ return std::min(user_interactions, max_interactions);
+}
+
+} // namespace
+
+PopupTracker* PopupTracker::CreateForWebContents(
+ content::WebContents* contents,
+ content::WebContents* opener,
+ WindowOpenDisposition disposition) {
+ DCHECK(contents);
+ DCHECK(opener);
+ auto* tracker = FromWebContents(contents);
+ if (!tracker) {
+ tracker = new PopupTracker(contents, opener, disposition);
+ contents->SetUserData(UserDataKey(), base::WrapUnique(tracker));
+ }
+ return tracker;
+}
+
+PopupTracker::~PopupTracker() = default;
+
+PopupTracker::PopupTracker(content::WebContents* contents,
+ content::WebContents* opener,
+ WindowOpenDisposition disposition)
+ : content::WebContentsObserver(contents),
+ scoped_observer_(this),
+ visibility_tracker_(
+ base::DefaultTickClock::GetInstance(),
+ contents->GetVisibility() != content::Visibility::HIDDEN),
+ opener_source_id_(ukm::GetSourceIdForWebContentsDocument(opener)),
+ window_open_disposition_(disposition) {
+ if (auto* popup_opener = PopupOpenerTabHelper::FromWebContents(opener))
+ popup_opener->OnOpenedPopup(this);
+
+ auto* observer_manager =
+ subresource_filter::SubresourceFilterObserverManager::FromWebContents(
+ contents);
+ if (observer_manager) {
+ scoped_observer_.Add(observer_manager);
+ }
+}
+
+void PopupTracker::WebContentsDestroyed() {
+ base::TimeDelta total_foreground_duration =
+ visibility_tracker_.GetForegroundDuration();
+ if (first_load_visible_time_start_) {
+ base::TimeDelta first_load_visible_time =
+ first_load_visible_time_
+ ? *first_load_visible_time_
+ : total_foreground_duration - *first_load_visible_time_start_;
+ UMA_HISTOGRAM_LONG_TIMES(
+ "ContentSettings.Popups.FirstDocumentEngagementTime2",
+ first_load_visible_time);
+ }
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "ContentSettings.Popups.EngagementTime", total_foreground_duration,
+ base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromHours(6), 50);
+ if (web_contents()->GetClosedByUserGesture()) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "ContentSettings.Popups.EngagementTime.GestureClose",
+ total_foreground_duration, base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromHours(6), 50);
+ }
+
+ if (opener_source_id_ != ukm::kInvalidSourceId) {
+ const int kMaxInteractions = 100;
+ const int kMaxSubcatagoryInteractions = 50;
+ ukm::builders::Popup_Closed(opener_source_id_)
+ .SetEngagementTime(ukm::GetExponentialBucketMinForUserTiming(
+ total_foreground_duration.InMilliseconds()))
+ .SetUserInitiatedClose(web_contents()->GetClosedByUserGesture())
+ .SetTrusted(is_trusted_)
+ .SetSafeBrowsingStatus(static_cast<int>(safe_browsing_status_))
+ .SetWindowOpenDisposition(static_cast<int>(window_open_disposition_))
+ .SetNumInteractions(
+ CappedUserInteractions(num_interactions_, kMaxInteractions))
+ .SetNumActivationInteractions(CappedUserInteractions(
+ num_activation_events_, kMaxSubcatagoryInteractions))
+ .SetNumGestureScrollBeginInteractions(CappedUserInteractions(
+ num_gesture_scroll_begin_events_, kMaxSubcatagoryInteractions))
+ .Record(ukm::UkmRecorder::Get());
+ }
+}
+
+void PopupTracker::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!navigation_handle->HasCommitted() ||
+ navigation_handle->IsSameDocument()) {
+ return;
+ }
+
+ if (!first_load_visible_time_start_) {
+ first_load_visible_time_start_ =
+ visibility_tracker_.GetForegroundDuration();
+ } else if (!first_load_visible_time_) {
+ first_load_visible_time_ = visibility_tracker_.GetForegroundDuration() -
+ *first_load_visible_time_start_;
+ }
+}
+
+void PopupTracker::OnVisibilityChanged(content::Visibility visibility) {
+ // TODO(csharrison): Consider handling OCCLUDED tabs the same way as HIDDEN
+ // tabs.
+ if (visibility == content::Visibility::HIDDEN)
+ visibility_tracker_.OnHidden();
+ else
+ visibility_tracker_.OnShown();
+}
+
+void PopupTracker::DidGetUserInteraction(
+ const blink::WebInputEvent::Type type) {
+ // TODO(csharrison): It would be nice if ctrl-W could be filtered out here,
+ // but the initial ctrl key press is registered as a kRawKeyDown.
+ num_interactions_++;
+
+ if (type == blink::WebInputEvent::Type::kGestureScrollBegin) {
+ num_gesture_scroll_begin_events_++;
+ } else {
+ num_activation_events_++;
+ }
+}
+
+// This method will always be called before the DidFinishNavigation associated
+// with this handle.
+// The exception is a navigation restoring a page from back-forward cache --
+// in that case don't issue any requests, therefore we don't get any
+// safe browsing callbacks. See the comment above for the mitigation.
+void PopupTracker::OnSafeBrowsingChecksComplete(
+ content::NavigationHandle* navigation_handle,
+ const subresource_filter::SubresourceFilterSafeBrowsingClient::CheckResult&
+ result) {
+ DCHECK(navigation_handle->IsInMainFrame());
+ safe_browsing_status_ = PopupSafeBrowsingStatus::kSafe;
+ if (result.threat_type ==
+ safe_browsing::SBThreatType::SB_THREAT_TYPE_URL_PHISHING ||
+ result.threat_type == safe_browsing::SBThreatType::
+ SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING ||
+ result.threat_type ==
+ safe_browsing::SBThreatType::SB_THREAT_TYPE_SUBRESOURCE_FILTER) {
+ safe_browsing_status_ = PopupSafeBrowsingStatus::kUnsafe;
+ }
+}
+
+void PopupTracker::OnSubresourceFilterGoingAway() {
+ scoped_observer_.RemoveAll();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(PopupTracker)
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/popup_tracker.h b/chromium/components/blocked_content/popup_tracker.h
new file mode 100644
index 00000000000..8366782de61
--- /dev/null
+++ b/chromium/components/blocked_content/popup_tracker.h
@@ -0,0 +1,113 @@
+// 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_POPUP_TRACKER_H_
+#define COMPONENTS_BLOCKED_CONTENT_POPUP_TRACKER_H_
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/scoped_observer.h"
+#include "base/time/time.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+#include "ui/base/scoped_visibility_tracker.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace blocked_content {
+
+// This class tracks new popups, and is used to log metrics on the visibility
+// time of the first document in the popup.
+// TODO(csharrison): Consider adding more metrics like total visibility for the
+// lifetime of the WebContents.
+class PopupTracker : public content::WebContentsObserver,
+ public content::WebContentsUserData<PopupTracker>,
+ public subresource_filter::SubresourceFilterObserver {
+ public:
+ // These values are persisted to logs. Entries should not be renumbered and
+ // numeric values should never be reused.
+ enum class PopupSafeBrowsingStatus {
+ kNoValue = 0,
+ kSafe = 1,
+ kUnsafe = 2,
+ kMaxValue = kUnsafe,
+ };
+
+ static PopupTracker* CreateForWebContents(content::WebContents* contents,
+ content::WebContents* opener,
+ WindowOpenDisposition disposition);
+ ~PopupTracker() override;
+
+ void set_is_trusted(bool is_trusted) { is_trusted_ = is_trusted; }
+
+ private:
+ friend class content::WebContentsUserData<PopupTracker>;
+
+ PopupTracker(content::WebContents* contents,
+ content::WebContents* opener,
+ WindowOpenDisposition disposition);
+
+ // content::WebContentsObserver:
+ void WebContentsDestroyed() override;
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+ void OnVisibilityChanged(content::Visibility visibility) override;
+ void DidGetUserInteraction(const blink::WebInputEvent::Type type) override;
+
+ // subresource_filter::SubresourceFilterObserver:
+ void OnSafeBrowsingChecksComplete(
+ content::NavigationHandle* navigation_handle,
+ const subresource_filter::SubresourceFilterSafeBrowsingClient::
+ CheckResult& result) override;
+ void OnSubresourceFilterGoingAway() override;
+
+ ScopedObserver<subresource_filter::SubresourceFilterObserverManager,
+ subresource_filter::SubresourceFilterObserver>
+ scoped_observer_;
+
+ // Will be unset until the first navigation commits. Will be set to the total
+ // time the contents was visible at commit time.
+ base::Optional<base::TimeDelta> first_load_visible_time_start_;
+ // Will be unset until the second navigation commits. Is the total time the
+ // contents is visible while the first document is loading (after commit).
+ base::Optional<base::TimeDelta> first_load_visible_time_;
+
+ ui::ScopedVisibilityTracker visibility_tracker_;
+
+ // The number of user interactions occurring in this popup tab.
+ int num_interactions_ = 0;
+ // The number of user interacitons in a popup tab broken down into
+ // user activation and gesture scroll begin events.
+ int num_activation_events_ = 0;
+ int num_gesture_scroll_begin_events_ = 0;
+
+ // The id of the web contents that created the popup at the time of creation.
+ // SourceIds are permanent so it's okay to use at any point so long as it's
+ // not invalid.
+ const ukm::SourceId opener_source_id_;
+
+ bool is_trusted_ = false;
+
+ // Whether the pop-up navigated to a site on the safe browsing list. Set when
+ // the safe browsing checks complete.
+ PopupSafeBrowsingStatus safe_browsing_status_ =
+ PopupSafeBrowsingStatus::kNoValue;
+
+ // The window open disposition used when creating the popup.
+ const WindowOpenDisposition window_open_disposition_;
+
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ DISALLOW_COPY_AND_ASSIGN(PopupTracker);
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_POPUP_TRACKER_H_
diff --git a/chromium/components/blocked_content/pref_names.cc b/chromium/components/blocked_content/pref_names.cc
new file mode 100644
index 00000000000..a0d41b5f52e
--- /dev/null
+++ b/chromium/components/blocked_content/pref_names.cc
@@ -0,0 +1,16 @@
+// Copyright 2020 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/blocked_content/pref_names.h"
+
+namespace blocked_content {
+namespace prefs {
+
+// A bool pref that indicates whether interventions for abusive experiences
+// should be enforced.
+const char kAbusiveExperienceInterventionEnforce[] =
+ "abusive_experience_intervention_enforce";
+
+} // namespace prefs
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/pref_names.h b/chromium/components/blocked_content/pref_names.h
new file mode 100644
index 00000000000..9d6050c92f6
--- /dev/null
+++ b/chromium/components/blocked_content/pref_names.h
@@ -0,0 +1,16 @@
+// Copyright 2020 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_PREF_NAMES_H_
+#define COMPONENTS_BLOCKED_CONTENT_PREF_NAMES_H_
+
+namespace blocked_content {
+namespace prefs {
+
+extern const char kAbusiveExperienceInterventionEnforce[];
+
+} // namespace prefs
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_PREF_NAMES_H
diff --git a/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.cc b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.cc
new file mode 100644
index 00000000000..792295121cf
--- /dev/null
+++ b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.cc
@@ -0,0 +1,194 @@
+// 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/blocked_content/safe_browsing_triggered_popup_blocker.h"
+
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "components/blocked_content/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/safe_browsing/core/db/util.h"
+#include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
+#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/back_forward_cache.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/navigation/triggering_event_info.h"
+#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
+
+namespace blocked_content {
+namespace {
+
+void LogAction(SafeBrowsingTriggeredPopupBlocker::Action action) {
+ UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.StrongBlockerActions",
+ action,
+ SafeBrowsingTriggeredPopupBlocker::Action::kCount);
+}
+
+} // namespace
+
+using safe_browsing::SubresourceFilterLevel;
+
+const base::Feature kAbusiveExperienceEnforce{"AbusiveExperienceEnforce",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+SafeBrowsingTriggeredPopupBlocker::PageData::PageData() = default;
+
+SafeBrowsingTriggeredPopupBlocker::PageData::~PageData() {
+ if (is_triggered_) {
+ UMA_HISTOGRAM_COUNTS_100("ContentSettings.Popups.StrongBlocker.NumBlocked",
+ num_popups_blocked_);
+ }
+}
+
+// static
+void SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterBooleanPref(prefs::kAbusiveExperienceInterventionEnforce,
+ true /* default_value */);
+}
+
+// static
+void SafeBrowsingTriggeredPopupBlocker::MaybeCreate(
+ content::WebContents* web_contents) {
+ if (!IsEnabled(web_contents))
+ return;
+
+ auto* observer_manager =
+ subresource_filter::SubresourceFilterObserverManager::FromWebContents(
+ web_contents);
+ if (!observer_manager)
+ return;
+
+ if (FromWebContents(web_contents))
+ return;
+
+ web_contents->SetUserData(
+ UserDataKey(), base::WrapUnique(new SafeBrowsingTriggeredPopupBlocker(
+ web_contents, observer_manager)));
+}
+
+SafeBrowsingTriggeredPopupBlocker::~SafeBrowsingTriggeredPopupBlocker() =
+ default;
+
+bool SafeBrowsingTriggeredPopupBlocker::ShouldApplyAbusivePopupBlocker() {
+ LogAction(Action::kConsidered);
+ if (!current_page_data_->is_triggered())
+ return false;
+
+ if (!IsEnabled(web_contents()))
+ return false;
+
+ LogAction(Action::kBlocked);
+ current_page_data_->inc_num_popups_blocked();
+ web_contents()->GetMainFrame()->AddMessageToConsole(
+ blink::mojom::ConsoleMessageLevel::kError, kAbusiveEnforceMessage);
+ return true;
+}
+
+SafeBrowsingTriggeredPopupBlocker::SafeBrowsingTriggeredPopupBlocker(
+ content::WebContents* web_contents,
+ subresource_filter::SubresourceFilterObserverManager* observer_manager)
+ : content::WebContentsObserver(web_contents),
+ scoped_observer_(this),
+ current_page_data_(std::make_unique<PageData>()) {
+ DCHECK(observer_manager);
+ scoped_observer_.Add(observer_manager);
+}
+
+void SafeBrowsingTriggeredPopupBlocker::DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) {
+ if (!navigation_handle->IsInMainFrame())
+ return;
+
+ base::Optional<SubresourceFilterLevel> level;
+ level_for_next_committed_navigation_.swap(level);
+
+ // Only care about main frame navigations that commit.
+ if (!navigation_handle->HasCommitted() ||
+ navigation_handle->IsSameDocument()) {
+ return;
+ }
+
+ DCHECK(current_page_data_);
+ current_page_data_ = std::make_unique<PageData>();
+ if (navigation_handle->IsErrorPage())
+ return;
+
+ // Log a warning only if we've matched a warn-only safe browsing list.
+ if (level == SubresourceFilterLevel::ENFORCE) {
+ current_page_data_->set_is_triggered(true);
+ LogAction(Action::kEnforcedSite);
+ // When a page is restored from back-forward cache, we don't get
+ // OnSafeBrowsingChecksComplete callback, so |level| will always
+ // be empty.
+ // To work around this, we disable back-forward cache if the original
+ // page load had abusive enforcement - this means that not doing checks on
+ // back-forward navigation is fine as it's guaranteed that
+ // the original page load didn't have enforcement.
+ // Note that it's possible for the safe browsing list to update while
+ // the page is in the cache, the risk of this is mininal due to
+ // having a time limit for how long pages are allowed to be in the
+ // cache.
+ content::BackForwardCache::DisableForRenderFrameHost(
+ navigation_handle->GetRenderFrameHost(),
+ "SafeBrowsingTriggeredPopupBlocker");
+ } else if (level == SubresourceFilterLevel::WARN) {
+ web_contents()->GetMainFrame()->AddMessageToConsole(
+ blink::mojom::ConsoleMessageLevel::kWarning, kAbusiveWarnMessage);
+ LogAction(Action::kWarningSite);
+ }
+ LogAction(Action::kNavigation);
+}
+
+// This method will always be called before the DidFinishNavigation associated
+// with this handle.
+// The exception is a navigation restoring a page from back-forward cache --
+// in that case don't issue any requests, therefore we don't get any
+// safe browsing callbacks. See the comment above for the mitigation.
+void SafeBrowsingTriggeredPopupBlocker::OnSafeBrowsingChecksComplete(
+ content::NavigationHandle* navigation_handle,
+ const subresource_filter::SubresourceFilterSafeBrowsingClient::CheckResult&
+ result) {
+ DCHECK(navigation_handle->IsInMainFrame());
+ base::Optional<safe_browsing::SubresourceFilterLevel> match_level;
+ if (result.threat_type ==
+ safe_browsing::SBThreatType::SB_THREAT_TYPE_SUBRESOURCE_FILTER) {
+ auto abusive = result.threat_metadata.subresource_filter_match.find(
+ safe_browsing::SubresourceFilterType::ABUSIVE);
+ if (abusive != result.threat_metadata.subresource_filter_match.end())
+ match_level = abusive->second;
+ }
+
+ if (match_level.has_value()) {
+ level_for_next_committed_navigation_ = match_level;
+ }
+}
+
+void SafeBrowsingTriggeredPopupBlocker::OnSubresourceFilterGoingAway() {
+ scoped_observer_.RemoveAll();
+}
+
+bool SafeBrowsingTriggeredPopupBlocker::IsEnabled(
+ content::WebContents* web_contents) {
+ // If feature is disabled, return false. This is done so that if the feature
+ // is broken it can be disabled irrespective of the policy.
+ if (!base::FeatureList::IsEnabled(kAbusiveExperienceEnforce))
+ return false;
+
+ // If enterprise policy is not set, this will return true which is the default
+ // preference value.
+ return user_prefs::UserPrefs::Get(web_contents->GetBrowserContext())
+ ->GetBoolean(prefs::kAbusiveExperienceInterventionEnforce);
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(SafeBrowsingTriggeredPopupBlocker)
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.h b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.h
new file mode 100644
index 00000000000..4daa6c70756
--- /dev/null
+++ b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker.h
@@ -0,0 +1,148 @@
+// 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_SAFE_BROWSING_TRIGGERED_POPUP_BLOCKER_H_
+#define COMPONENTS_BLOCKED_CONTENT_SAFE_BROWSING_TRIGGERED_POPUP_BLOCKER_H_
+
+#include <memory>
+
+#include "base/feature_list.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/scoped_observer.h"
+#include "components/safe_browsing/core/db/util.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+class WebContents;
+} // namespace content
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace blocked_content {
+extern const base::Feature kAbusiveExperienceEnforce;
+
+constexpr char kAbusiveEnforceMessage[] =
+ "Chrome prevented this site from opening a new tab or window. Learn more "
+ "at https://www.chromestatus.com/feature/5243055179300864";
+constexpr char kAbusiveWarnMessage[] =
+ "Chrome might start preventing this site from opening new tabs or "
+ "windows in the future. Learn more at "
+ "https://www.chromestatus.com/feature/5243055179300864";
+
+// This class observes main frame navigation checks incoming from safe browsing
+// (currently implemented by the subresource_filter component). For navigations
+// which match the ABUSIVE safe browsing list, this class will help the popup
+// tab helper in applying a stronger policy for blocked popups.
+class SafeBrowsingTriggeredPopupBlocker
+ : public content::WebContentsObserver,
+ public content::WebContentsUserData<SafeBrowsingTriggeredPopupBlocker>,
+ public subresource_filter::SubresourceFilterObserver {
+ public:
+ // This enum backs a histogram. Please append new entries to the end, and
+ // update enums.xml when making changes.
+ enum class Action : int {
+ // User committed a navigation to a non-error page.
+ kNavigation,
+
+ // Safe Browsing considered this page abusive and the page should be warned.
+ // Logged at navigation commit.
+ kWarningSite,
+
+ // Safe Browsing considered this page abusive and the page should be be
+ // blocked against. Logged at navigation commit.
+ kEnforcedSite,
+
+ // The popup blocker called into this object to ask if the strong blocking
+ // should be applied.
+ kConsidered,
+
+ // This object responded to the popup blocker in the affirmative, and the
+ // popup was blocked.
+ kBlocked,
+
+ // Add new entries before this one
+ kCount
+ };
+
+ static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+
+ // Creates a SafeBrowsingTriggeredPopupBlocker and attaches it (via UserData)
+ // to |web_contents|.
+ static void MaybeCreate(content::WebContents* web_contents);
+ ~SafeBrowsingTriggeredPopupBlocker() override;
+
+ bool ShouldApplyAbusivePopupBlocker();
+
+ private:
+ friend class content::WebContentsUserData<SafeBrowsingTriggeredPopupBlocker>;
+ // The |web_contents| and |observer_manager| are expected to be
+ // non-nullptr.
+ SafeBrowsingTriggeredPopupBlocker(
+ content::WebContents* web_contents,
+ subresource_filter::SubresourceFilterObserverManager* observer_manager);
+
+ // content::WebContentsObserver:
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override;
+
+ // subresource_filter::SubresourceFilterObserver:
+ void OnSafeBrowsingChecksComplete(
+ content::NavigationHandle* navigation_handle,
+ const subresource_filter::SubresourceFilterSafeBrowsingClient::
+ CheckResult& result) override;
+ void OnSubresourceFilterGoingAway() override;
+
+ // Enabled state is governed by both a feature flag and a pref (which can be
+ // controlled by enterprise policy).
+ static bool IsEnabled(content::WebContents* web_contents);
+
+ // Data scoped to a single page. Will be reset at navigation commit.
+ class PageData {
+ public:
+ PageData();
+
+ // Logs UMA in the destructor based on the number of popups blocked.
+ ~PageData();
+
+ void inc_num_popups_blocked() { ++num_popups_blocked_; }
+
+ void set_is_triggered(bool is_triggered) { is_triggered_ = is_triggered; }
+ bool is_triggered() const { return is_triggered_; }
+
+ private:
+ // How many popups are blocked in this page.
+ int num_popups_blocked_ = 0;
+
+ // Whether the current committed page load should trigger the stronger popup
+ // blocker.
+ bool is_triggered_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(PageData);
+ };
+
+ ScopedObserver<subresource_filter::SubresourceFilterObserverManager,
+ subresource_filter::SubresourceFilterObserver>
+ scoped_observer_;
+
+ // Whether the next main frame navigation that commits should trigger the
+ // stronger popup blocker in enforce or warn mode.
+ base::Optional<safe_browsing::SubresourceFilterLevel>
+ level_for_next_committed_navigation_;
+
+ // Should never be nullptr.
+ std::unique_ptr<PageData> current_page_data_;
+
+ WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+ DISALLOW_COPY_AND_ASSIGN(SafeBrowsingTriggeredPopupBlocker);
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_SAFE_BROWSING_TRIGGERED_POPUP_BLOCKER_H_
diff --git a/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
new file mode 100644
index 00000000000..827799894c9
--- /dev/null
+++ b/chromium/components/blocked_content/safe_browsing_triggered_popup_blocker_unittest.cc
@@ -0,0 +1,501 @@
+// 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/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 "components/blocked_content/popup_blocker.h"
+#include "components/blocked_content/popup_blocker_tab_helper.h"
+#include "components/blocked_content/popup_navigation_delegate.h"
+#include "components/blocked_content/test/test_popup_navigation_delegate.h"
+#include "components/content_settings/browser/tab_specific_content_settings.h"
+#include "components/content_settings/browser/test_tab_specific_content_settings_delegate.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/subresource_filter/content/browser/fake_safe_browsing_database_manager.h"
+#include "components/subresource_filter/content/browser/subresource_filter_client.h"
+#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
+#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
+#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_client.h"
+#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "components/user_prefs/user_prefs.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/navigation_throttle.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/public/test/test_navigation_throttle_inserter.h"
+#include "content/public/test/test_renderer_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/navigation/triggering_event_info.h"
+#include "third_party/blink/public/mojom/window_features/window_features.mojom.h"
+#include "ui/base/page_transition_types.h"
+#include "ui/base/window_open_disposition.h"
+#include "url/gurl.h"
+
+namespace blocked_content {
+const char kNumBlockedHistogram[] =
+ "ContentSettings.Popups.StrongBlocker.NumBlocked";
+
+class SafeBrowsingTriggeredPopupBlockerTest
+ : public content::RenderViewHostTestHarness,
+ public subresource_filter::SubresourceFilterClient {
+ public:
+ SafeBrowsingTriggeredPopupBlockerTest() = default;
+ ~SafeBrowsingTriggeredPopupBlockerTest() override {
+ settings_map_->ShutdownOnUIThread();
+ }
+
+ // subresource_filter::SubresourceFilterClient:
+ void ShowNotification() override {}
+ subresource_filter::mojom::ActivationLevel OnPageActivationComputed(
+ content::NavigationHandle* navigation_handle,
+ subresource_filter::mojom::ActivationLevel initial_activation_level,
+ subresource_filter::ActivationDecision* decision) override {
+ return initial_activation_level;
+ }
+
+ // content::RenderViewHostTestHarness:
+ void SetUp() override {
+ content::RenderViewHostTestHarness::SetUp();
+
+ fake_safe_browsing_database_ =
+ base::MakeRefCounted<FakeSafeBrowsingDatabaseManager>();
+
+ user_prefs::UserPrefs::Set(browser_context(), &pref_service_);
+ SafeBrowsingTriggeredPopupBlocker::RegisterProfilePrefs(
+ pref_service_.registry());
+ HostContentSettingsMap::RegisterProfilePrefs(pref_service_.registry());
+ settings_map_ = base::MakeRefCounted<HostContentSettingsMap>(
+ &pref_service_, false, false, false, false);
+
+ scoped_feature_list_ = DefaultFeatureList();
+ subresource_filter::SubresourceFilterObserverManager::CreateForWebContents(
+ web_contents());
+ PopupBlockerTabHelper::CreateForWebContents(web_contents());
+ content_settings::TabSpecificContentSettings::CreateForWebContents(
+ web_contents(),
+ std::make_unique<
+ content_settings::TestTabSpecificContentSettingsDelegate>(
+ /*prefs=*/nullptr, settings_map_.get()));
+ popup_blocker_ =
+ SafeBrowsingTriggeredPopupBlocker::FromWebContents(web_contents());
+
+ throttle_inserter_ =
+ std::make_unique<content::TestNavigationThrottleInserter>(
+ web_contents(),
+ base::BindRepeating(
+ &SafeBrowsingTriggeredPopupBlockerTest::CreateThrottle,
+ base::Unretained(this)));
+ }
+
+ 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()->AddBlocklistedUrl(
+ 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();
+ }
+
+ HostContentSettingsMap* settings_map() { return settings_map_.get(); }
+
+ private:
+ std::unique_ptr<content::NavigationThrottle> CreateThrottle(
+ content::NavigationHandle* handle) {
+ return std::make_unique<
+ subresource_filter::SubresourceFilterSafeBrowsingActivationThrottle>(
+ handle, this, content::GetIOThreadTaskRunner({}),
+ fake_safe_browsing_database_);
+ }
+
+ std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
+ scoped_refptr<FakeSafeBrowsingDatabaseManager> fake_safe_browsing_database_;
+ SafeBrowsingTriggeredPopupBlocker* popup_blocker_ = nullptr;
+ std::unique_ptr<content::TestNavigationThrottleInserter> throttle_inserter_;
+ sync_preferences::TestingPrefServiceSyncable pref_service_;
+ scoped_refptr<HostContentSettingsMap> settings_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(SafeBrowsingTriggeredPopupBlockerTest);
+};
+
+struct RedirectSamplesAndResults {
+ GURL initial_url;
+ GURL redirect_url;
+ bool expect_strong_blocker;
+};
+
+// We always make our decision to trigger on the last entry in the chain.
+TEST_F(SafeBrowsingTriggeredPopupBlockerTest,
+ MatchOnSafeBrowsingWithRedirectChain) {
+ 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::TriggeringEventInfo::kFromUntrustedEvent;
+
+ MaybeBlockPopup(web_contents(), nullptr,
+ std::make_unique<TestPopupNavigationDelegate>(
+ popup_url, nullptr /* result_holder */),
+ &params, blink::mojom::WindowFeatures(), settings_map());
+
+ 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::TriggeringEventInfo::kFromTrustedEvent;
+
+ MaybeBlockPopup(web_contents(), nullptr,
+ std::make_unique<TestPopupNavigationDelegate>(
+ popup_url, nullptr /* result_holder */),
+ &params, blink::mojom::WindowFeatures(), settings_map());
+
+ 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, EnforcementRedirectPosition) {
+ // Turn on the feature to perform safebrowsing on redirects.
+ base::test::ScopedFeatureList scoped_feature_list;
+
+ const GURL enforce_url("https://enforce.test/");
+ const GURL warn_url("https://warn.test/");
+ MarkUrlAsAbusiveEnforce(enforce_url);
+ MarkUrlAsAbusiveWarning(warn_url);
+
+ using subresource_filter::RedirectPosition;
+ struct {
+ std::vector<const char*> urls;
+ base::Optional<RedirectPosition> last_enforcement_position;
+ } kTestCases[] = {
+ {{"https://normal.test/"}, base::nullopt},
+ {{"https://enforce.test/"}, RedirectPosition::kOnly},
+ {{"https://warn.test/"}, base::nullopt},
+
+ {{"https://normal.test/", "https://warn.test/"}, base::nullopt},
+ {{"https://normal.test/", "https://normal.test/",
+ "https://enforce.test/"},
+ RedirectPosition::kLast},
+
+ {{"https://enforce.test", "https://normal.test/", "https://warn.test/"},
+ RedirectPosition::kFirst},
+ {{"https://warn.test/", "https://normal.test/"}, base::nullopt},
+
+ {{"https://normal.test/", "https://enforce.test/",
+ "https://normal.test/"},
+ RedirectPosition::kMiddle},
+ };
+
+ 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(
+ "SubresourceFilter.PageLoad.Activation.RedirectPosition2.Enforcement",
+ test_case.last_enforcement_position.has_value() ? 1 : 0);
+ if (test_case.last_enforcement_position.has_value()) {
+ histograms.ExpectUniqueSample(
+ "SubresourceFilter.PageLoad.Activation.RedirectPosition2.Enforcement",
+ static_cast<int>(test_case.last_enforcement_position.value()), 1);
+ }
+ }
+}
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/url_list_manager.cc b/chromium/components/blocked_content/url_list_manager.cc
new file mode 100644
index 00000000000..745946bd402
--- /dev/null
+++ b/chromium/components/blocked_content/url_list_manager.cc
@@ -0,0 +1,27 @@
+// Copyright 2018 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/blocked_content/url_list_manager.h"
+
+namespace blocked_content {
+
+UrlListManager::UrlListManager() = default;
+
+UrlListManager::~UrlListManager() = default;
+
+void UrlListManager::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void UrlListManager::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void UrlListManager::NotifyObservers(int32_t id, const GURL& url) {
+ for (auto& observer : observers_) {
+ observer.BlockedUrlAdded(id, url);
+ }
+}
+
+} // namespace blocked_content
diff --git a/chromium/components/blocked_content/url_list_manager.h b/chromium/components/blocked_content/url_list_manager.h
new file mode 100644
index 00000000000..0f26cd6a963
--- /dev/null
+++ b/chromium/components/blocked_content/url_list_manager.h
@@ -0,0 +1,47 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_BLOCKED_CONTENT_URL_LIST_MANAGER_H_
+#define COMPONENTS_BLOCKED_CONTENT_URL_LIST_MANAGER_H_
+
+#include <stdint.h>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+
+class GURL;
+
+namespace blocked_content {
+
+// This class manages lists of blocked URLs in order to drive UI surfaces.
+// Currently it is used by the redirect / popup blocked UIs.
+//
+// TODO(csharrison): Currently this object is composed within the framebust /
+// popup tab helpers. Eventually those objects could be replaced almost entirely
+// by shared logic here.
+class UrlListManager {
+ public:
+ class Observer {
+ public:
+ virtual ~Observer() {}
+ virtual void BlockedUrlAdded(int32_t id, const GURL& url) = 0;
+ };
+
+ UrlListManager();
+ ~UrlListManager();
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void NotifyObservers(int32_t id, const GURL& url);
+
+ private:
+ base::ObserverList<Observer>::Unchecked observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(UrlListManager);
+};
+
+} // namespace blocked_content
+
+#endif // COMPONENTS_BLOCKED_CONTENT_URL_LIST_MANAGER_H_