blob: e60b0d45a512e60e32e8edb334d638f8abd3023f [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/downloads/downloads_dom_handler.h"
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/supports_user_data.h"
#include "base/task/current_thread.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/browser/download/download_history.h"
#include "chrome/browser/download/download_item_model.h"
#include "chrome/browser/download/download_item_warning_data.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_query.h"
#include "chrome/browser/download/download_ui_safe_browsing_util.h"
#include "chrome/browser/download/download_warning_desktop_hats_utils.h"
#include "chrome/browser/download/drag_download_item.h"
#include "chrome/browser/download/offline_item_utils.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/lifetime/browser_shutdown.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/hats/trust_safety_sentiment_service.h"
#include "chrome/browser/ui/hats/trust_safety_sentiment_service_factory.h"
#include "chrome/browser/ui/webui/downloads/downloads.mojom.h"
#include "chrome/browser/ui/webui/fileicon_source.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "components/download/public/common/download_item.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/history/core/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/common/safebrowsing_referral_methods.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/url_data_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/filename_util.h"
#include "ui/base/l10n/time_format.h"
#include "ui/display/screen.h"
#include "ui/gfx/image/image.h"
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#endif
#if !BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/download/bubble/download_bubble_ui_controller.h"
#endif
using content::BrowserThread;
namespace {
using WarningAction = DownloadItemWarningData::WarningAction;
using WarningSurface = DownloadItemWarningData::WarningSurface;
enum DownloadsDOMEvent {
DOWNLOADS_DOM_EVENT_GET_DOWNLOADS = 0,
DOWNLOADS_DOM_EVENT_OPEN_FILE = 1,
DOWNLOADS_DOM_EVENT_DRAG = 2,
// Obsolete: DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS = 3,
DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS = 4,
DOWNLOADS_DOM_EVENT_SHOW = 5,
DOWNLOADS_DOM_EVENT_PAUSE = 6,
DOWNLOADS_DOM_EVENT_REMOVE = 7,
DOWNLOADS_DOM_EVENT_CANCEL = 8,
DOWNLOADS_DOM_EVENT_CLEAR_ALL = 9,
DOWNLOADS_DOM_EVENT_OPEN_FOLDER = 10,
DOWNLOADS_DOM_EVENT_RESUME = 11,
DOWNLOADS_DOM_EVENT_RETRY_DOWNLOAD = 12,
DOWNLOADS_DOM_EVENT_OPEN_DURING_SCANNING = 13,
DOWNLOADS_DOM_EVENT_REVIEW_DANGEROUS = 14,
DOWNLOADS_DOM_EVENT_DEEP_SCAN = 15,
DOWNLOADS_DOM_EVENT_BYPASS_DEEP_SCAN = 16,
DOWNLOADS_DOM_EVENT_SAVE_SUSPICIOUS = 17,
DOWNLOADS_DOM_EVENT_OPEN_BYPASS_WARNING_PROMPT = 18,
DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS_FROM_PROMPT = 19,
DOWNLOADS_DOM_EVENT_CANCEL_BYPASS_WARNING_PROMPT = 20,
DOWNLOADS_DOM_EVENT_OPEN_SURVEY_ON_DANGEROUS_INTERSTITIAL = 21,
DOWNLOADS_DOM_EVENT_MAX
};
void CountDownloadsDOMEvents(DownloadsDOMEvent event) {
UMA_HISTOGRAM_ENUMERATION("Download.DOMEvent", event,
DOWNLOADS_DOM_EVENT_MAX);
}
bool CanLogWarningMetrics(download::DownloadItem* file) {
return file && file->IsDangerous() && !file->IsDone();
}
void PromptForScanningInBubble(content::WebContents* web_contents,
download::DownloadItem* download) {
// ChromeOS does not have the download bubble and does not support local
// password prompts for deep scans.
#if !BUILDFLAG(IS_CHROMEOS)
Browser* browser = chrome::FindBrowserWithTab(web_contents);
if (!browser) {
return;
}
browser->window()
->GetDownloadBubbleUIController()
->GetDownloadDisplayController()
->OpenSecuritySubpage(
OfflineItemUtils::GetContentIdForDownload(download));
#endif
}
// Records DownloadItemWarningData and maybe sends the Safe Browsing report.
// This should be called when the user takes a bypass action (either proceed or
// cancel).
void MaybeReportBypassAction(download::DownloadItem* file,
WarningSurface surface,
WarningAction action) {
CHECK(file);
CHECK(file->IsDangerous());
CHECK(!file->IsDone());
CHECK(surface == WarningSurface::DOWNLOADS_PAGE ||
surface == WarningSurface::DOWNLOAD_PROMPT);
CHECK(action == WarningAction::PROCEED || action == WarningAction::CANCEL ||
action == WarningAction::DISCARD || action == WarningAction::KEEP);
// If this is called from the DOWNLOADS_PAGE, the action must be proceed,
// discard, or keep. There is no cancellation action on the page, because
// there's no prompt to cancel.
CHECK(surface != WarningSurface::DOWNLOADS_PAGE ||
action != WarningAction::CANCEL);
// The warning action event needs to be added before Safe Browsing report is
// sent, because this event should be included in the report.
DownloadItemWarningData::AddWarningActionEvent(file, surface, action);
// Do not send cancel or keep report since it's not a terminal action.
if (action != WarningAction::PROCEED && action != WarningAction::DISCARD) {
return;
}
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
SendSafeBrowsingDownloadReport(
safe_browsing::ClientSafeBrowsingReportRequest::
DANGEROUS_DOWNLOAD_RECOVERY,
/*did_proceed=*/action == WarningAction::PROCEED, file);
#endif
}
// Triggers a Trust and Safety sentiment survey (if enabled). Should be called
// when the user takes an explicit action to save or discard a
// suspicious/dangerous file. Not called when the prompt is merely shown.
void MaybeTriggerTrustSafetySurvey(download::DownloadItem* file,
WarningSurface surface,
WarningAction action) {
CHECK(file);
CHECK(surface == WarningSurface::DOWNLOADS_PAGE ||
surface == WarningSurface::DOWNLOAD_PROMPT);
CHECK(action == WarningAction::PROCEED || action == WarningAction::DISCARD);
if (Profile* profile = Profile::FromBrowserContext(
content::DownloadItemUtils::GetBrowserContext(file));
profile &&
safe_browsing::IsSafeBrowsingSurveysEnabled(*profile->GetPrefs())) {
TrustSafetySentimentService* trust_safety_sentiment_service =
TrustSafetySentimentServiceFactory::GetForProfile(profile);
if (trust_safety_sentiment_service) {
trust_safety_sentiment_service->InteractedWithDownloadWarningUI(surface,
action);
}
}
}
void RecordDownloadsPageValidatedHistogram(download::DownloadItem* item) {
base::UmaHistogramEnumeration(
"Download.UserValidatedDangerousDownload.DownloadsPage",
item->GetDangerType(), download::DOWNLOAD_DANGER_TYPE_MAX);
}
} // namespace
DownloadsDOMHandler::DownloadsDOMHandler(
mojo::PendingReceiver<downloads::mojom::PageHandler> receiver,
mojo::PendingRemote<downloads::mojom::Page> page,
content::DownloadManager* download_manager,
content::WebUI* web_ui)
: list_tracker_(download_manager, std::move(page)),
web_ui_(web_ui),
receiver_(this, std::move(receiver)) {
// Create our fileicon data source.
content::URLDataSource::Add(
Profile::FromBrowserContext(download_manager->GetBrowserContext()),
std::make_unique<FileIconSource>());
CheckForRemovedFiles();
}
DownloadsDOMHandler::~DownloadsDOMHandler() {
OnDownloadsPageDismissed();
list_tracker_.Stop();
list_tracker_.Reset();
if (!render_process_gone_) {
CheckForRemovedFiles();
}
FinalizeRemovals();
}
void DownloadsDOMHandler::PrimaryMainFrameRenderProcessGone(
base::TerminationStatus status) {
// TODO(dbeam): WebUI + WebUIMessageHandler should do this automatically.
// http://crbug.com/610450
render_process_gone_ = true;
}
void DownloadsDOMHandler::GetDownloads(
const std::vector<std::string>& search_terms) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_GET_DOWNLOADS);
bool terms_changed = list_tracker_.SetSearchTerms(search_terms);
if (terms_changed) {
list_tracker_.Reset();
}
list_tracker_.StartAndSendChunk();
}
void DownloadsDOMHandler::OpenFileRequiringGesture(const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "OpenFileRequiringGesture received without recent "
"user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FILE);
download::DownloadItem* file = GetDownloadByStringId(id);
if (file) {
file->OpenDownload();
}
}
void DownloadsDOMHandler::Drag(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DRAG);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!file) {
return;
}
content::WebContents* web_contents = GetWebUIWebContents();
// |web_contents| is only NULL in the test.
if (!web_contents) {
return;
}
if (file->GetState() != download::DownloadItem::COMPLETE) {
return;
}
const display::Screen* const screen = display::Screen::Get();
gfx::NativeView view = web_contents->GetNativeView();
gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
file->GetTargetFilePath(), IconLoader::NORMAL,
screen->GetPreferredScaleFactorForView(view).value_or(1.0f));
{
// Enable nested tasks during DnD, while |DragDownload()| blocks.
base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
DragDownloadItem(file, icon, view);
}
}
// "Suspicious" in this context applies to insecure as well as dangerous
// downloads of certain danger types.
void DownloadsDOMHandler::SaveSuspiciousRequiringGesture(
const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "SaveSuspiciousRequiringGesture received without recent "
"user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_SUSPICIOUS);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!file || file->IsDone()) {
return;
}
// If a download is insecure, validate that first. Is most cases, insecure
// download warnings will occur first, but in the worst case scenario, we show
// a dangerous warning twice. That's better than showing an insecure download
// warning, then dismissing the dangerous download warning. Since insecure
// downloads triggering the UI are temporary and rare to begin with, this
// should very rarely occur.
if (file->IsInsecure()) {
// `file` is potentially deleted.
file->ValidateInsecureDownload();
} else if (file->IsDangerous()) {
MaybeReportBypassAction(file, WarningSurface::DOWNLOADS_PAGE,
WarningAction::PROCEED);
MaybeTriggerDownloadWarningHatsSurvey(
file, DownloadWarningHatsType::kDownloadsPageBypass);
MaybeTriggerTrustSafetySurvey(file, WarningSurface::DOWNLOADS_PAGE,
WarningAction::PROCEED);
RecordDownloadsPageValidatedHistogram(file);
// `file` is potentially deleted.
file->ValidateDangerousDownload();
}
}
void DownloadsDOMHandler::RecordOpenBypassWarningDialog(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_BYPASS_WARNING_PROMPT);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!CanLogWarningMetrics(file)) {
return;
}
RecordDownloadDangerPromptHistogram("Shown", *file);
MaybeReportBypassAction(file, WarningSurface::DOWNLOADS_PAGE,
WarningAction::KEEP);
}
void DownloadsDOMHandler::SaveDangerousFromDialogRequiringGesture(
const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "SaveDangerousFromDialogRequiringGesture received without "
"recent user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SAVE_DANGEROUS_FROM_PROMPT);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!CanLogWarningMetrics(file)) {
return;
}
RecordDownloadDangerPromptHistogram("Proceed", *file);
MaybeReportBypassAction(file, WarningSurface::DOWNLOAD_PROMPT,
WarningAction::PROCEED);
MaybeTriggerDownloadWarningHatsSurvey(
file, DownloadWarningHatsType::kDownloadsPageBypass);
MaybeTriggerTrustSafetySurvey(file, WarningSurface::DOWNLOAD_PROMPT,
WarningAction::PROCEED);
RecordDownloadsPageValidatedHistogram(file);
// `file` is potentially deleted.
file->ValidateDangerousDownload();
}
void DownloadsDOMHandler::RecordCancelBypassWarningDialog(
const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL_BYPASS_WARNING_PROMPT);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!CanLogWarningMetrics(file)) {
return;
}
MaybeReportBypassAction(file, WarningSurface::DOWNLOAD_PROMPT,
WarningAction::CANCEL);
}
void DownloadsDOMHandler::DiscardDangerous(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DISCARD_DANGEROUS);
download::DownloadItem* download = GetDownloadByStringId(id);
if (download && !download->IsDone() && download->IsDangerous()) {
MaybeReportBypassAction(download, WarningSurface::DOWNLOADS_PAGE,
WarningAction::DISCARD);
MaybeTriggerDownloadWarningHatsSurvey(
download, DownloadWarningHatsType::kDownloadsPageHeed);
MaybeTriggerTrustSafetySurvey(download, WarningSurface::DOWNLOADS_PAGE,
WarningAction::DISCARD);
}
RemoveDownloadInArgs(id);
}
void DownloadsDOMHandler::RetryDownload(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RETRY_DOWNLOAD);
download::DownloadItem* file = GetDownloadByStringId(id);
if (!file) {
return;
}
content::WebContents* web_contents = GetWebUIWebContents();
content::RenderFrameHost* render_frame_host =
web_contents->GetPrimaryMainFrame();
const GURL url = file->GetURL();
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("downloads_dom_handler", R"(
semantics {
sender: "The downloads page."
description: "Retrying a download."
trigger:
"The user selects the 'Retry' button for a cancelled download on "
"the downloads page."
data: "None."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled by settings, but it's only "
"triggered by user request."
policy_exception_justification: "Not implemented."
})");
// For "Retry", we want to use the network isolation key associated with the
// initial download request rather than treating it as initiated from the
// chrome://downloads/ page. Thus we get the NIK from |file|, not from
// |render_frame_host|.
auto dl_params = std::make_unique<download::DownloadUrlParameters>(
url, render_frame_host->GetProcess()->GetDeprecatedID(),
render_frame_host->GetRoutingID(), traffic_annotation);
dl_params->set_content_initiated(true);
dl_params->set_initiator(url::Origin::Create(GURL("chrome://downloads")));
dl_params->set_download_source(download::DownloadSource::RETRY);
web_contents->GetBrowserContext()->GetDownloadManager()->DownloadUrl(
std::move(dl_params));
}
void DownloadsDOMHandler::Show(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_SHOW);
download::DownloadItem* file = GetDownloadByStringId(id);
if (file) {
file->ShowDownloadInShell();
}
}
void DownloadsDOMHandler::Pause(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_PAUSE);
download::DownloadItem* file = GetDownloadByStringId(id);
if (file) {
file->Pause();
}
}
void DownloadsDOMHandler::Resume(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_RESUME);
download::DownloadItem* file = GetDownloadByStringId(id);
if (file) {
file->Resume(true);
}
}
void DownloadsDOMHandler::Remove(const std::string& id) {
if (!IsDeletingHistoryAllowed()) {
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REMOVE);
RemoveDownloadInArgs(id);
}
void DownloadsDOMHandler::Undo() {
// TODO(dbeam): handle more than removed downloads someday?
if (removals_.empty()) {
return;
}
const IdSet last_removed_ids = removals_.back();
removals_.pop_back();
const bool undoing_clear_all = last_removed_ids.size() > 1;
if (undoing_clear_all) {
list_tracker_.Reset();
list_tracker_.Stop();
}
for (auto id : last_removed_ids) {
download::DownloadItem* download = GetDownloadById(id);
if (!download) {
continue;
}
DownloadItemModel model(download);
model.SetShouldShowInUi(true);
model.SetIsBeingRevived(true);
download->UpdateObservers();
model.SetIsBeingRevived(false);
}
if (undoing_clear_all) {
list_tracker_.StartAndSendChunk();
}
}
void DownloadsDOMHandler::Cancel(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CANCEL);
download::DownloadItem* file = GetDownloadByStringId(id);
if (file) {
file->Cancel(true);
}
}
void DownloadsDOMHandler::ClearAll() {
if (!IsDeletingHistoryAllowed()) {
// This should only be reached during tests.
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_CLEAR_ALL);
list_tracker_.Reset();
list_tracker_.Stop();
DownloadVector downloads;
if (GetMainNotifierManager()) {
GetMainNotifierManager()->GetAllDownloads(&downloads);
}
if (GetOriginalNotifierManager()) {
GetOriginalNotifierManager()->GetAllDownloads(&downloads);
}
RemoveDownloads(downloads);
list_tracker_.StartAndSendChunk();
}
void DownloadsDOMHandler::RemoveDownloads(const DownloadVector& to_remove) {
IdSet ids;
for (download::DownloadItem* download : to_remove) {
if (download->IsDangerous() || download->IsInsecure()) {
// Don't allow users to revive dangerous downloads; just nuke 'em.
download->Remove();
continue;
}
DownloadItemModel item_model(download);
if (!item_model.ShouldShowInUi() ||
download->GetState() == download::DownloadItem::IN_PROGRESS) {
continue;
}
item_model.SetShouldShowInUi(false);
ids.insert(download->GetId());
download->UpdateObservers();
}
if (!ids.empty()) {
removals_.push_back(ids);
}
}
void DownloadsDOMHandler::OpenDownloadsFolderRequiringGesture() {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "OpenDownloadsFolderRequiringGesture received without recent "
"user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_FOLDER);
content::DownloadManager* manager = GetMainNotifierManager();
if (manager) {
platform_util::OpenItem(
Profile::FromBrowserContext(manager->GetBrowserContext()),
DownloadPrefs::FromDownloadManager(manager)->DownloadPath(),
platform_util::OPEN_FOLDER, platform_util::OpenOperationCallback());
}
}
void DownloadsDOMHandler::OpenDuringScanningRequiringGesture(
const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "OpenDownloadsFolderRequiringGesture received without recent "
"user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_OPEN_DURING_SCANNING);
download::DownloadItem* download = GetDownloadByStringId(id);
if (download) {
DownloadItemModel model(download);
model.SetOpenWhenComplete(true);
#if BUILDFLAG(SAFE_BROWSING_DOWNLOAD_PROTECTION)
model.CompleteSafeBrowsingScan();
#endif
}
}
void DownloadsDOMHandler::DeepScan(const std::string& id) {
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_DEEP_SCAN);
download::DownloadItem* download = GetDownloadByStringId(id);
if (!download) {
return;
}
if (DownloadItemWarningData::IsTopLevelEncryptedArchive(download)) {
// For encrypted archives, we need a password from the user. We will request
// this in the download bubble.
PromptForScanningInBubble(GetWebUIWebContents(), download);
return;
}
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
LogDeepScanEvent(download,
safe_browsing::DeepScanEvent::kPromptAcceptedFromWebUI);
#endif
DownloadItemWarningData::AddWarningActionEvent(
download, DownloadItemWarningData::WarningSurface::DOWNLOADS_PAGE,
DownloadItemWarningData::WarningAction::ACCEPT_DEEP_SCAN);
DownloadItemModel model(download);
DownloadCommands commands(model.GetWeakPtr());
commands.ExecuteCommand(DownloadCommands::DEEP_SCAN);
}
void DownloadsDOMHandler::BypassDeepScanRequiringGesture(
const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << "BypassDeepScanRequiringGesture received without recent "
"user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_BYPASS_DEEP_SCAN);
download::DownloadItem* download = GetDownloadByStringId(id);
if (download) {
if (CanShowDownloadWarningHatsSurvey(download)) {
MaybeTriggerDownloadWarningHatsSurvey(
download, DownloadWarningHatsType::kDownloadsPageBypass);
}
DownloadItemModel model(download);
DownloadCommands commands(model.GetWeakPtr());
// The button says "Download suspicious file" which does not imply opening
// the file.
commands.ExecuteCommand(DownloadCommands::BYPASS_DEEP_SCANNING);
}
}
void DownloadsDOMHandler::ReviewDangerousRequiringGesture(
const std::string& id) {
if (!GetWebUIWebContents()->HasRecentInteraction()) {
LOG(ERROR) << __func__ << " received without recent user interaction";
return;
}
CountDownloadsDOMEvents(DOWNLOADS_DOM_EVENT_REVIEW_DANGEROUS);
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
download::DownloadItem* download = GetDownloadByStringId(id);
if (download) {
DownloadItemModel model(download);
model.ReviewScanningVerdict(GetWebUIWebContents());
}
#endif
}
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// This function will be called when a user clicks on the ESB
// (Enhanced Safe Browsing) download row promo. It will notify
// the feature engagement backend to record the event that the
// promo was clicked.
void DownloadsDOMHandler::OpenEsbSettings() {
Browser* browser = chrome::FindBrowserWithTab(GetWebUIWebContents());
if (!browser) {
return;
}
chrome::ShowSafeBrowsingEnhancedProtectionWithIph(
browser,
safe_browsing::SafeBrowsingSettingReferralMethod::kDownloadPageRowPromo);
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserContext(
browser->profile());
tracker->NotifyEvent("esb_download_promo_row_clicked");
base::RecordAction(
base::UserMetricsAction("SafeBrowsing.EsbDownloadRowPromo.Click"));
base::UmaHistogramEnumeration(
"SafeBrowsing.EsbDownloadRowPromo.Outcome",
SafeBrowsingEsbDownloadRowPromoOutcome::kClicked);
}
void DownloadsDOMHandler::IsEligibleForEsbPromo(
IsEligibleForEsbPromoCallback callback) {
content::DownloadManager* manager = GetMainNotifierManager();
if (!manager) {
std::move(callback).Run(false);
return;
}
content::BrowserContext* browser_context = manager->GetBrowserContext();
if (!safe_browsing::SafeBrowsingService::IsUserEligibleForESBPromo(
Profile::FromBrowserContext(browser_context))) {
std::move(callback).Run(false);
return;
}
bool should_show_esb_promo = false;
if (feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserContext(
browser_context);
tracker && tracker->ShouldTriggerHelpUI(
feature_engagement::kEsbDownloadRowPromoFeature)) {
should_show_esb_promo = true;
// since the promotion row is not an IPH, it never calls dismissed, so we
// need to do it artificially here or we can trigger a DCHECK.
tracker->Dismissed(feature_engagement::kEsbDownloadRowPromoFeature);
}
std::move(callback).Run(should_show_esb_promo);
}
void DownloadsDOMHandler::LogEsbPromotionRowViewed() {
content::DownloadManager* manager = GetMainNotifierManager();
if (!manager) {
return;
}
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserContext(
manager->GetBrowserContext());
tracker->NotifyEvent("esb_download_promo_row_viewed");
base::UmaHistogramEnumeration("SafeBrowsing.EsbDownloadRowPromo.Outcome",
SafeBrowsingEsbDownloadRowPromoOutcome::kShown);
}
#else
// These next three functions are empty implementations for the non-branded
// chromium build since the ESB download row promo only runs on branded
// google chrome.
void DownloadsDOMHandler::OpenEsbSettings() {
return;
}
void DownloadsDOMHandler::IsEligibleForEsbPromo(
IsEligibleForEsbPromoCallback callback) {
std::move(callback).Run(false);
}
void DownloadsDOMHandler::LogEsbPromotionRowViewed() {
return;
}
#endif
// DownloadsDOMHandler, private: --------------------------------------------
content::DownloadManager* DownloadsDOMHandler::GetMainNotifierManager() const {
return list_tracker_.GetMainNotifierManager();
}
content::DownloadManager* DownloadsDOMHandler::GetOriginalNotifierManager()
const {
return list_tracker_.GetOriginalNotifierManager();
}
void DownloadsDOMHandler::FinalizeRemovals() {
while (!removals_.empty()) {
const IdSet remove = removals_.back();
removals_.pop_back();
for (const auto id : remove) {
download::DownloadItem* download = GetDownloadById(id);
if (download) {
download->Remove();
}
}
}
}
void DownloadsDOMHandler::MaybeTriggerDownloadWarningHatsSurvey(
download::DownloadItem* item,
DownloadWarningHatsType survey_type) {
CHECK(CanShowDownloadWarningHatsSurvey(item));
content::DownloadManager* manager = GetMainNotifierManager();
Profile* profile = Profile::FromBrowserContext(manager->GetBrowserContext());
if (!profile) {
return;
}
auto psd = DownloadWarningHatsProductSpecificData::Create(survey_type, item);
psd.AddNumPageWarnings(list_tracker_.NumDangerousItemsSent());
MaybeLaunchDownloadWarningHatsSurvey(profile, psd);
}
void DownloadsDOMHandler::OnDownloadsPageDismissed() {
// If the chrome://downloads page is closed as part of the browser shutting
// down, do not run the HaTS survey because that would call into the network
// stack and try to use objects that are already being torn down.
if (browser_shutdown::HasShutdownStarted()) {
return;
}
// There's no specific warning associated with navigating away from
// chrome://downloads or closing the tab, so let's just launch the survey on
// the topmost download with a warning.
if (download::DownloadItem* first_dangerous_item =
list_tracker_.GetFirstActiveWarningItem();
first_dangerous_item &&
CanShowDownloadWarningHatsSurvey(first_dangerous_item)) {
MaybeTriggerDownloadWarningHatsSurvey(
first_dangerous_item, DownloadWarningHatsType::kDownloadsPageIgnore);
}
}
bool DownloadsDOMHandler::IsDeletingHistoryAllowed() {
content::DownloadManager* manager = GetMainNotifierManager();
return manager && Profile::FromBrowserContext(manager->GetBrowserContext())
->GetPrefs()
->GetBoolean(prefs::kAllowDeletingBrowserHistory);
}
download::DownloadItem* DownloadsDOMHandler::GetDownloadByStringId(
const std::string& id) {
uint64_t id_num;
if (!base::StringToUint64(id, &id_num)) {
NOTREACHED();
}
return GetDownloadById(static_cast<uint32_t>(id_num));
}
download::DownloadItem* DownloadsDOMHandler::GetDownloadById(uint32_t id) {
download::DownloadItem* item = nullptr;
if (GetMainNotifierManager()) {
item = GetMainNotifierManager()->GetDownload(id);
}
if (!item && GetOriginalNotifierManager()) {
item = GetOriginalNotifierManager()->GetDownload(id);
}
return item;
}
content::WebContents* DownloadsDOMHandler::GetWebUIWebContents() {
return web_ui_->GetWebContents();
}
void DownloadsDOMHandler::CheckForRemovedFiles() {
if (GetMainNotifierManager()) {
GetMainNotifierManager()->CheckForHistoryFilesRemoval();
}
if (GetOriginalNotifierManager()) {
GetOriginalNotifierManager()->CheckForHistoryFilesRemoval();
}
}
void DownloadsDOMHandler::RemoveDownloadInArgs(const std::string& id) {
download::DownloadItem* file = GetDownloadByStringId(id);
if (!file) {
return;
}
DownloadVector downloads;
downloads.push_back(file);
RemoveDownloads(downloads);
}