blob: a344fc2329d3f024242976865b28e4b8aacc6bab [file] [log] [blame]
// Copyright 2023 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/download/bubble/download_bubble_utils.h"
#include "base/containers/fixed_flat_map.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/download/download_ui_model.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/download/download_item_mode.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/grit/generated_resources.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/download_item.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "components/offline_items_collection/core/offline_item_state.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/base/l10n/l10n_util.h"
base::Time GetItemStartTime(const download::DownloadItem* item) {
return item->GetStartTime();
}
base::Time GetItemStartTime(const offline_items_collection::OfflineItem& item) {
return item.creation_time;
}
const std::string& GetItemId(const download::DownloadItem* item) {
return item->GetGuid();
}
const offline_items_collection::ContentId& GetItemId(
const offline_items_collection::OfflineItem& item) {
return item.id;
}
bool ItemIsRecent(const download::DownloadItem* item, base::Time cutoff_time) {
return ((item->GetStartTime().is_null() && !item->IsDone()) ||
item->GetStartTime() > cutoff_time);
}
bool ItemIsRecent(const offline_items_collection::OfflineItem& item,
base::Time cutoff_time) {
// TODO(chlily): Deduplicate this code from OfflineItemModel::IsDone().
bool is_done = false;
switch (item.state) {
case offline_items_collection::OfflineItemState::IN_PROGRESS:
case offline_items_collection::OfflineItemState::PAUSED:
case offline_items_collection::OfflineItemState::PENDING:
break;
case offline_items_collection::OfflineItemState::INTERRUPTED:
is_done = item.is_resumable;
break;
case offline_items_collection::OfflineItemState::FAILED:
case offline_items_collection::OfflineItemState::COMPLETE:
case offline_items_collection::OfflineItemState::CANCELLED:
is_done = true;
break;
case offline_items_collection::OfflineItemState::NUM_ENTRIES:
NOTREACHED();
}
return ((item.creation_time.is_null() && !is_done) ||
item.creation_time > cutoff_time);
}
bool DownloadUIModelIsRecent(const DownloadUIModel* model,
base::Time cutoff_time) {
return ((model->GetStartTime().is_null() && !model->IsDone()) ||
model->GetStartTime() > cutoff_time);
}
bool IsPendingDeepScanning(const download::DownloadItem* item) {
return item->GetState() == download::DownloadItem::IN_PROGRESS &&
item->GetDangerType() ==
download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
}
bool IsPendingDeepScanning(const DownloadUIModel* model) {
return model->GetState() == download::DownloadItem::IN_PROGRESS &&
model->GetDangerType() ==
download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING;
}
bool IsItemInProgress(const download::DownloadItem* item) {
if (item->IsDangerous() || item->IsInsecure() ||
IsPendingDeepScanning(item)) {
return false;
}
return item->GetState() == download::DownloadItem::IN_PROGRESS;
}
bool IsItemInProgress(const offline_items_collection::OfflineItem& item) {
// Offline items cannot be pending deep scanning, and insecure warnings are
// not shown for them.
if (item.is_dangerous) {
return false;
}
return item.state ==
offline_items_collection::OfflineItemState::IN_PROGRESS ||
item.state == offline_items_collection::OfflineItemState::PAUSED;
}
bool IsModelInProgress(const DownloadUIModel* model) {
if (model->IsDangerous() || model->IsInsecure() ||
IsPendingDeepScanning(model)) {
return false;
}
return model->GetState() == download::DownloadItem::IN_PROGRESS;
}
bool IsItemPaused(const download::DownloadItem* item) {
return item->IsPaused();
}
bool IsItemPaused(const offline_items_collection::OfflineItem& item) {
return item.state == offline_items_collection::OfflineItemState::PAUSED;
}
DownloadBubbleAccessibleAlertsMap::Alert GetAccessibleAlertForModel(
const DownloadUIModel& model) {
using State = download::DownloadItem::DownloadState;
using Alert = DownloadBubbleAccessibleAlertsMap::Alert;
const download::DownloadItemMode mode =
download::GetDesiredDownloadItemMode(&model);
const State state = model.GetState();
const std::u16string filename =
model.GetFileNameToReportUser().LossyDisplayName();
switch (mode) {
case download::DownloadItemMode::kNormal: {
if (state == State::IN_PROGRESS && !model.IsPaused()) {
return Alert{
Alert::Urgency::kAlertWhenAppropriate,
model.GetInProgressAccessibleAlertText(),
};
}
static constexpr auto kMap = base::MakeFixedFlatMap<State, int>({
{State::INTERRUPTED, IDS_DOWNLOAD_FAILED_ACCESSIBLE_ALERT},
{State::COMPLETE, IDS_DOWNLOAD_COMPLETE_ACCESSIBLE_ALERT},
{State::CANCELLED, IDS_DOWNLOAD_CANCELLED_ACCESSIBLE_ALERT},
// If state is IN_PROGRESS but we got here to the map lookup, the
// download is paused.
{State::IN_PROGRESS, IDS_DOWNLOAD_PAUSED_ACCESSIBLE_ALERT},
});
if (const auto it = kMap.find(state); it != kMap.end()) {
return Alert{Alert::Urgency::kAlertSoon,
l10n_util::GetStringFUTF16(it->second, filename)};
}
break;
}
case download::DownloadItemMode::kDangerous:
case download::DownloadItemMode::kMalicious: {
if (model.GetDangerType() ==
download::DOWNLOAD_DANGER_TYPE_PROMPT_FOR_SCANNING) {
return Alert{Alert::Urgency::kAlertSoon,
l10n_util::GetStringFUTF16(
IDS_PROMPT_DEEP_SCANNING_ACCESSIBLE_ALERT, filename)};
}
size_t ignored;
return Alert{Alert::Urgency::kAlertSoon,
model.GetWarningText(filename, &ignored)};
}
case download::DownloadItemMode::kInsecureDownloadWarn:
case download::DownloadItemMode::kInsecureDownloadBlock:
return Alert{
Alert::Urgency::kAlertSoon,
l10n_util::GetStringFUTF16(
IDS_PROMPT_DOWNLOAD_INSECURE_BLOCKED_ACCESSIBLE_ALERT, filename)};
case download::DownloadItemMode::kDeepScanning:
// Note: kAlertSoon (as opposed to kAlertWhenAppropriate) is correct here,
// because observers are updated when a deep scan begins, and not
// continuously during a deep scan.
return Alert{Alert::Urgency::kAlertSoon,
l10n_util::GetStringFUTF16(
IDS_DEEP_SCANNING_ACCESSIBLE_ALERT, filename)};
}
// An empty alert will not be added.
return Alert{Alert::Urgency::kAlertWhenAppropriate, u""};
}
Browser* FindBrowserToShowAnimation(download::DownloadItem* item,
Profile* profile) {
content::WebContents* web_contents =
content::DownloadItemUtils::GetWebContents(item);
// For the case of DevTools web contents, we'd like to use target browser
// shelf although saving from the DevTools web contents.
if (web_contents && DevToolsWindow::IsDevToolsWindow(web_contents)) {
DevToolsWindow* devtools_window =
DevToolsWindow::AsDevToolsWindow(web_contents);
content::WebContents* inspected =
devtools_window->GetInspectedWebContents();
// Do not overwrite web contents for the case of remote debugging.
if (inspected) {
web_contents = inspected;
}
}
Browser* browser_to_show_animation =
web_contents ? chrome::FindBrowserWithTab(web_contents) : nullptr;
// As a last resort, use the last active browser for this profile. Not ideal,
// but better than not showing the download at all.
if (browser_to_show_animation == nullptr) {
browser_to_show_animation = chrome::FindLastActiveWithProfile(profile);
}
return browser_to_show_animation;
}
const webapps::AppId* GetWebAppIdForBrowser(const Browser* browser) {
return web_app::AppBrowserController::IsWebApp(browser)
? &browser->app_controller()->app_id()
: nullptr;
}