blob: df9c4ef84544d4969bac79fa85453a3ac1b72f03 [file] [log] [blame]
// 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 "chrome/browser/ui/blocked_content/popup_blocker_tab_helper.h"
#include <iterator>
#include <string>
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/subresource_filter/chrome_subresource_filter_client.h"
#include "chrome/browser/ui/blocked_content/blocked_window_params.h"
#include "chrome/browser/ui/blocked_content/list_item_position.h"
#include "chrome/browser/ui/blocked_content/popup_tracker.h"
#include "chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/common/chrome_render_frame.mojom.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/render_messages.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.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/WebKit/common/associated_interfaces/associated_interface_provider.h"
#include "url/gurl.h"
#if defined(OS_ANDROID)
#include "chrome/browser/ui/android/tab_model/tab_model_list.h"
#endif
const size_t kMaximumNumberOfPopups = 25;
DEFINE_WEB_CONTENTS_USER_DATA_KEY(PopupBlockerTabHelper);
struct PopupBlockerTabHelper::BlockedRequest {
BlockedRequest(const NavigateParams& params,
const blink::mojom::WindowFeatures& window_features)
: params(params), window_features(window_features) {}
NavigateParams params;
blink::mojom::WindowFeatures window_features;
};
PopupBlockerTabHelper::PopupBlockerTabHelper(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
safe_browsing_triggered_popup_blocker_(
SafeBrowsingTriggeredPopupBlocker::MaybeCreate(web_contents)) {}
PopupBlockerTabHelper::~PopupBlockerTabHelper() {
}
void PopupBlockerTabHelper::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void PopupBlockerTabHelper::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// static
bool PopupBlockerTabHelper::ConsiderForPopupBlocking(
WindowOpenDisposition disposition) {
return disposition == WindowOpenDisposition::NEW_POPUP ||
disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB ||
disposition == WindowOpenDisposition::NEW_WINDOW;
}
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();
PopupNotificationVisibilityChanged(false);
}
}
void PopupBlockerTabHelper::PopupNotificationVisibilityChanged(
bool visible) {
if (!web_contents()->IsBeingDestroyed()) {
TabSpecificContentSettings::FromWebContents(web_contents())->
SetPopupsBlocked(visible);
}
}
// static
bool PopupBlockerTabHelper::MaybeBlockPopup(
content::WebContents* web_contents,
const base::Optional<GURL>& opener_url,
const NavigateParams& params,
const content::OpenURLParams* open_url_params,
const blink::mojom::WindowFeatures& window_features) {
DCHECK(!open_url_params ||
open_url_params->user_gesture == params.user_gesture);
LogAction(Action::kInitiated);
const bool user_gesture = params.user_gesture;
if (!web_contents)
return false;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisablePopupBlocking)) {
return false;
}
auto* popup_blocker = PopupBlockerTabHelper::FromWebContents(web_contents);
if (!popup_blocker)
return false;
// 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.value() : web_contents->GetLastCommittedURL();
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
if (url.is_valid() &&
HostContentSettingsMapFactory::GetForProfile(profile)->GetContentSetting(
url, url, CONTENT_SETTINGS_TYPE_POPUPS, std::string()) ==
CONTENT_SETTING_ALLOW) {
return false;
}
if (user_gesture) {
auto* safe_browsing_blocker =
popup_blocker->safe_browsing_triggered_popup_blocker_.get();
if (!safe_browsing_blocker ||
!safe_browsing_blocker->ShouldApplyStrongPopupBlocker(
open_url_params)) {
return false;
}
}
popup_blocker->AddBlockedPopup(params, window_features);
return true;
}
void PopupBlockerTabHelper::AddBlockedPopup(
const NavigateParams& params,
const blink::mojom::WindowFeatures& window_features) {
LogAction(Action::kBlocked);
if (blocked_popups_.size() >= kMaximumNumberOfPopups)
return;
int id = next_id_;
next_id_++;
blocked_popups_[id] =
base::MakeUnique<BlockedRequest>(params, window_features);
TabSpecificContentSettings::FromWebContents(web_contents())->
OnContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS);
for (auto& observer : observers_)
observer.BlockedPopupAdded(id, params.url);
}
void PopupBlockerTabHelper::ShowBlockedPopup(
int32_t id,
WindowOpenDisposition disposition) {
auto it = blocked_popups_.find(id);
if (it == blocked_popups_.end())
return;
ListItemPosition position = GetListItemPositionFromDistance(
std::distance(blocked_popups_.begin(), it), blocked_popups_.size());
UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.ClickThroughPosition",
position, ListItemPosition::kLast);
BlockedRequest* popup = it->second.get();
// We set user_gesture to true here, so the new popup gets correctly focused.
popup->params.user_gesture = true;
if (disposition != WindowOpenDisposition::CURRENT_TAB)
popup->params.disposition = disposition;
#if defined(OS_ANDROID)
TabModelList::HandlePopupNavigation(&popup->params);
#else
Navigate(&popup->params);
#endif
if (popup->params.target_contents) {
PopupTracker::CreateForWebContents(popup->params.target_contents,
web_contents());
if (popup->params.disposition == WindowOpenDisposition::NEW_POPUP) {
content::RenderFrameHost* host =
popup->params.target_contents->GetMainFrame();
DCHECK(host);
chrome::mojom::ChromeRenderFrameAssociatedPtr client;
host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
client->SetWindowFeatures(popup->window_features.Clone());
}
}
blocked_popups_.erase(id);
if (blocked_popups_.empty())
PopupNotificationVisibilityChanged(false);
LogAction(Action::kClickedThrough);
}
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->params.url;
}
return result;
}
// static
void PopupBlockerTabHelper::LogAction(Action action) {
UMA_HISTOGRAM_ENUMERATION("ContentSettings.Popups.BlockerActions", action,
Action::kLast);
}