// 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 "build/branding_buildflags.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/non_iph_promo.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_element_identifiers.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.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/views/frame/browser_view.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_danger_type.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 "components/tabs/public/tab_interface.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"
#include "ui/views/interaction/element_tracker_views.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)
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents);
  if (!browser) {
    return;
  }
  BrowserWindow::FromBrowser(browser)
      ->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/41253133
  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) {
    if (auto* tab_interface =
            tabs::TabInterface::MaybeGetFromContents(GetWebUIWebContents())) {
      if (auto* browser_view = BrowserView::GetBrowserViewForBrowser(
              tab_interface->GetBrowserWindowInterface())) {
        views::ElementTrackerViews::GetInstance()->NotifyCustomEvent(
            kDownloadedFileOpenedCustomEventId, browser_view);
      }
    }
    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 =
      render_frame_host->CreateDownloadUrlParameters(url, traffic_annotation);
  dl_params->set_content_initiated(true);
  dl_params->set_initiator(file->GetRequestInitiator());
  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) {
    bool should_remove = download->IsDangerous() || download->IsInsecure();
    if (!should_remove) {
      download::DownloadDangerType danger_type = download->GetDangerType();
      should_remove =
          danger_type == download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING ||
          danger_type ==
              download::DOWNLOAD_DANGER_TYPE_ASYNC_LOCAL_PASSWORD_SCANNING;
    }
    if (should_remove) {
      // 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() {
  BrowserWindowInterface* browser =
      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
          GetWebUIWebContents());
  if (!browser) {
    return;
  }
  chrome::ShowSafeBrowsingEnhancedProtectionWithIph(
      browser,
      safe_browsing::SafeBrowsingSettingReferralMethod::kDownloadPageRowPromo);

  feature_engagement::Tracker* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(
          browser->GetProfile());
  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;
  }
  const bool should_show_esb_promo =
      feature_engagement::NonIphPromo::RequestPermissionToShow(
          browser_context, 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);
}
