blob: 5d9172ca0af6eaff00be3d162f1309900ff0718e [file] [log] [blame]
// Copyright 2021 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/webui/download_shelf/download_shelf_ui.h"
#include "base/location.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/download/download_commands.h"
#include "chrome/browser/ui/webui/download_shelf/download_shelf_page_handler.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/download_shelf_resources.h"
#include "chrome/grit/download_shelf_resources_map.h"
#include "chrome/grit/generated_resources.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui_data_source.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
namespace {
bool isDownloading(DownloadItem* download) {
return !download->IsPaused() && download->PercentComplete() != 100;
}
} // namespace
DownloadShelfUI::DownloadShelfUI(content::WebUI* web_ui)
: ui::MojoWebUIController(web_ui, true),
progress_timer_(std::make_unique<base::RetainingOneShotTimer>(
FROM_HERE,
base::Milliseconds(30),
base::BindRepeating(&DownloadShelfUI::NotifyDownloadProgress,
base::Unretained(this)))),
download_manager_(Profile::FromWebUI(web_ui)->GetDownloadManager()),
webui_load_timer_(web_ui->GetWebContents(),
"Download.Shelf.WebUI.LoadDocumentTime",
"Download.Shelf.WebUI.LoadCompletedTime") {
content::WebUIDataSource* source =
content::WebUIDataSource::Create(chrome::kChromeUIDownloadShelfHost);
static constexpr webui::LocalizedString kStrings[] = {
{"close", IDS_ACCNAME_CLOSE},
{"discardButtonText", IDS_DISCARD_DOWNLOAD},
{"downloadStatusOpeningText", IDS_DOWNLOAD_STATUS_OPENING},
{"showAll", IDS_SHOW_ALL_DOWNLOADS}};
source->AddLocalizedStrings(kStrings);
webui::SetupWebUIDataSource(
source,
base::make_span(kDownloadShelfResources, kDownloadShelfResourcesSize),
IDR_DOWNLOAD_SHELF_DOWNLOAD_SHELF_HTML);
content::WebUIDataSource::Add(web_ui->GetWebContents()->GetBrowserContext(),
source);
}
DownloadShelfUI::~DownloadShelfUI() {
// The destructor can take place before DownloadItem calls to
// OnDownloadDestroyed.
for (const auto& download_entry : items_)
download_entry.second->download()->RemoveObserver(this);
}
WEB_UI_CONTROLLER_TYPE_IMPL(DownloadShelfUI)
void DownloadShelfUI::BindInterface(
mojo::PendingReceiver<download_shelf::mojom::PageHandlerFactory> receiver) {
page_factory_receiver_.reset();
page_factory_receiver_.Bind(std::move(receiver));
}
void DownloadShelfUI::CreatePageHandler(
mojo::PendingRemote<download_shelf::mojom::Page> page,
mojo::PendingReceiver<download_shelf::mojom::PageHandler> receiver) {
page_handler_ = std::make_unique<DownloadShelfPageHandler>(
std::move(receiver), std::move(page), this);
}
void DownloadShelfUI::DoClose() {
if (embedder())
embedder()->DoClose();
}
void DownloadShelfUI::DoShowAll() {
if (embedder())
embedder()->DoShowAll();
}
void DownloadShelfUI::DiscardDownload(uint32_t download_id) {
DownloadUIModel* download_ui_model = FindDownloadById(download_id);
// WebUI's view is updated asynchronously via Mojo IPC, so the
// corresponding C++ DownloadUIModel might already be gone due
// to races with other UI surfaces.
if (!download_ui_model)
return;
DownloadCommands(download_ui_model->GetWeakPtr())
.ExecuteCommand(DownloadCommands::DISCARD);
}
void DownloadShelfUI::KeepDownload(uint32_t download_id) {
DownloadUIModel* download_ui_model = FindDownloadById(download_id);
// WebUI's view is updated asynchronously via Mojo IPC, so the
// corresponding C++ DownloadUIModel might already be gone due
// to races with other UI surfaces.
if (!download_ui_model)
return;
DownloadCommands(download_ui_model->GetWeakPtr())
.ExecuteCommand(DownloadCommands::KEEP);
}
void DownloadShelfUI::ShowContextMenu(
uint32_t download_id,
int32_t client_x,
int32_t client_y,
base::OnceClosure on_menu_will_show_callback) {
DownloadUIModel* download_ui_model = FindDownloadById(download_id);
// WebUI's view is updated asynchronously via Mojo IPC, so the
// corresponding C++ DownloadUIModel might already be gone due
// to races with other UI surfaces.
if (!download_ui_model)
return;
if (embedder()) {
embedder()->ShowDownloadContextMenu(download_ui_model,
gfx::Point(client_x, client_y),
std::move(on_menu_will_show_callback));
}
}
void DownloadShelfUI::OpenDownload(uint32_t download_id) {
DownloadUIModel* download_ui_model = FindDownloadById(download_id);
// DownloadUIModel can be updated/removed from somewhere else, e.g extension
// API or chrome://downloads, checking if download_ui_model exists makes it
// safer for edges cases such as a download item is removed during a mojo
// IPC call.
if (!download_ui_model)
return;
download_ui_model->OpenDownload();
}
void DownloadShelfUI::DoShowDownload(
DownloadUIModel::DownloadUIModelPtr download_model,
base::Time show_download_start_time) {
DownloadUIModel* download = AddDownload(std::move(download_model));
show_download_time_map_.insert_or_assign(download->download()->GetId(),
show_download_start_time);
// Observe any changes on the download item in order to propagate such changes
// to the UI.
download->download()->AddObserver(this);
progress_timer_->Reset();
if (page_handler_)
page_handler_->DoShowDownload(download);
}
std::vector<DownloadUIModel*> DownloadShelfUI::GetDownloads() {
std::vector<DownloadUIModel*> downloads;
for (const auto& download_entry : items_) {
DownloadUIModel* download_model = download_entry.second.get();
if (download_model->ShouldShowInShelf())
downloads.push_back(download_model);
}
return downloads;
}
base::Time DownloadShelfUI::GetShowDownloadTime(uint32_t download_id) {
return show_download_time_map_[download_id];
}
void DownloadShelfUI::RemoveDownload(uint32_t download_id) {
DownloadUIModel* download_ui_model = items_.at(download_id).get();
download_ui_model->download()->RemoveObserver(this);
items_.erase(download_id);
if (show_download_time_map_.count(download_id)) {
show_download_time_map_.erase(download_id);
}
}
void DownloadShelfUI::OnDownloadOpened(DownloadItem* download) {
if (page_handler_)
page_handler_->OnDownloadOpened(download->GetId());
}
void DownloadShelfUI::OnDownloadUpdated(DownloadItem* download) {
if (page_handler_) {
DownloadUIModel* download_model = FindDownloadById(download->GetId());
DCHECK(download_model);
if (!download_model->ShouldShowInShelf()) {
page_handler_->OnDownloadErased(download->GetId());
return;
}
page_handler_->OnDownloadUpdated(download_model);
}
if (isDownloading(download) && !progress_timer_->IsRunning())
progress_timer_->Reset();
}
void DownloadShelfUI::OnDownloadRemoved(DownloadItem* download) {
if (page_handler_)
page_handler_->OnDownloadErased(download->GetId());
}
void DownloadShelfUI::OnDownloadDestroyed(DownloadItem* download) {
download->RemoveObserver(this);
items_.erase(download->GetId());
}
DownloadUIModel* DownloadShelfUI::AddDownload(
DownloadUIModel::DownloadUIModelPtr download) {
DownloadUIModel* pointer = download.get();
items_.insert_or_assign(download->download()->GetId(), std::move(download));
return pointer;
}
DownloadUIModel* DownloadShelfUI::FindDownloadById(uint32_t download_id) const {
return items_.count(download_id) ? items_.at(download_id).get() : nullptr;
}
void DownloadShelfUI::NotifyDownloadProgress() {
bool download_in_progress = false;
for (const auto& download_entry : items_) {
DownloadUIModel* download_model = download_entry.second.get();
if (isDownloading(download_model->download())) {
download_in_progress = true;
// TODO(romanarora): Optimize by introducing a new OnDownloadProgress()
// method.
if (page_handler_)
page_handler_->OnDownloadUpdated(download_model);
}
}
if (download_in_progress)
progress_timer_->Reset();
}
void DownloadShelfUI::SetPageHandlerForTesting(
std::unique_ptr<DownloadShelfHandler> page_handler) {
page_handler_ = std::move(page_handler);
}
void DownloadShelfUI::SetProgressTimerForTesting(
std::unique_ptr<base::RetainingOneShotTimer> timer) {
progress_timer_ = std::move(timer);
progress_timer_->Start(
FROM_HERE, base::Milliseconds(30),
base::BindRepeating(&DownloadShelfUI::NotifyDownloadProgress,
base::Unretained(this)));
}