blob: 75741638c7401569f5f20411ba90c110e2638249 [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 "components/background_fetch/background_fetch_delegate_base.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/task/post_task.h"
#include "build/build_config.h"
#include "components/background_fetch/job_details.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/download/public/background_service/background_download_service.h"
#include "components/download/public/background_service/blob_context_getter_factory.h"
#include "components/download/public/background_service/download_params.h"
#include "content/public/browser/background_fetch_description.h"
#include "content/public/browser/background_fetch_response.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_manager_delegate.h"
#include "content/public/browser/web_contents.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
#include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
#include "ui/gfx/geometry/size.h"
namespace background_fetch {
BackgroundFetchDelegateBase::BackgroundFetchDelegateBase(
content::BrowserContext* context)
: context_(context) {}
BackgroundFetchDelegateBase::~BackgroundFetchDelegateBase() = default;
void BackgroundFetchDelegateBase::GetIconDisplaySize(
BackgroundFetchDelegate::GetIconDisplaySizeCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// If Android, return 192x192, else return 0x0. 0x0 means not loading an
// icon at all, which is returned for all non-Android platforms as the
// icons can't be displayed on the UI yet.
gfx::Size display_size;
#if defined(OS_ANDROID)
display_size = gfx::Size(192, 192);
#endif
std::move(callback).Run(display_size);
}
void BackgroundFetchDelegateBase::CreateDownloadJob(
base::WeakPtr<Client> client,
std::unique_ptr<content::BackgroundFetchDescription> fetch_description) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const std::string job_id = fetch_description->job_unique_id;
auto inserted = job_details_map_.emplace(std::piecewise_construct,
std::forward_as_tuple(job_id),
std::forward_as_tuple());
DCHECK(inserted.second);
JobDetails* job_details = &inserted.first->second;
job_details->client = std::move(client);
job_details->job_state =
fetch_description->start_paused
? JobDetails::State::kPendingWillStartPaused
: JobDetails::State::kPendingWillStartDownloading;
job_details->fetch_description = std::move(fetch_description);
OnJobDetailsCreated(job_id);
}
void BackgroundFetchDelegateBase::DownloadUrl(
const std::string& job_id,
const std::string& download_guid,
const std::string& method,
const GURL& url,
::network::mojom::CredentialsMode credentials_mode,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const net::HttpRequestHeaders& headers,
bool has_request_body) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!download_job_id_map_.count(download_guid));
download_job_id_map_.emplace(download_guid, job_id);
download::DownloadParams params;
params.guid = download_guid;
params.client = download::DownloadClient::BACKGROUND_FETCH;
params.request_params.method = method;
params.request_params.url = url;
params.request_params.request_headers = headers;
params.request_params.credentials_mode = credentials_mode;
params.callback =
base::BindRepeating(&BackgroundFetchDelegateBase::OnDownloadReceived,
weak_ptr_factory_.GetWeakPtr());
params.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(traffic_annotation);
JobDetails* job_details = GetJobDetails(job_id);
if (job_details->job_state == JobDetails::State::kPendingWillStartPaused ||
job_details->job_state ==
JobDetails::State::kPendingWillStartDownloading) {
DoShowUi(job_id);
job_details->MarkJobAsStarted();
}
params.request_params.isolation_info =
job_details->fetch_description->isolation_info;
if (job_details->job_state == JobDetails::State::kStartedButPaused) {
job_details->on_resume = base::BindOnce(
&BackgroundFetchDelegateBase::StartDownload, GetWeakPtr(), job_id,
std::move(params), has_request_body);
} else {
StartDownload(job_id, std::move(params), has_request_body);
}
DoUpdateUi(job_id);
}
void BackgroundFetchDelegateBase::PauseDownload(const std::string& job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
if (!job_details)
return;
if (job_details->job_state == JobDetails::State::kDownloadsComplete ||
job_details->job_state == JobDetails::State::kJobComplete) {
// The pause event arrived after the fetch was complete; ignore it.
return;
}
job_details->job_state = JobDetails::State::kStartedButPaused;
for (auto& download_guid_pair : job_details->current_fetch_guids)
GetDownloadService()->PauseDownload(download_guid_pair.first);
}
void BackgroundFetchDelegateBase::ResumeDownload(const std::string& job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
if (!job_details)
return;
job_details->job_state = JobDetails::State::kStartedAndDownloading;
for (auto& download_guid_pair : job_details->current_fetch_guids)
GetDownloadService()->ResumeDownload(download_guid_pair.first);
if (job_details->on_resume)
std::move(job_details->on_resume).Run();
}
void BackgroundFetchDelegateBase::CancelDownload(std::string job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
JobDetails* job_details = GetJobDetails(job_id);
if (!job_details ||
job_details->job_state == JobDetails::State::kDownloadsComplete ||
job_details->job_state == JobDetails::State::kJobComplete) {
// The cancel event arrived after the fetch was complete; ignore it.
return;
}
job_details->cancelled_from_ui = true;
Abort(job_id);
if (auto client = GetClient(job_id)) {
client->OnJobCancelled(
job_id, blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI);
}
}
void BackgroundFetchDelegateBase::OnUiFinished(const std::string& job_id,
bool activated) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (activated) {
if (auto client = GetClient(job_id))
client->OnUIActivated(job_id);
}
job_details_map_.erase(job_id);
DoCleanUpUi(job_id);
}
JobDetails* BackgroundFetchDelegateBase::GetJobDetails(
const std::string& job_id,
bool allow_null) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto job_details_iter = job_details_map_.find(job_id);
if (job_details_iter == job_details_map_.end()) {
if (!allow_null)
NOTREACHED();
return nullptr;
}
return &job_details_iter->second;
}
void BackgroundFetchDelegateBase::StartDownload(const std::string& job_id,
download::DownloadParams params,
bool has_request_body) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
GetJobDetails(job_id)->current_fetch_guids.emplace(params.guid,
has_request_body);
GetDownloadService()->StartDownload(std::move(params));
}
void BackgroundFetchDelegateBase::Abort(const std::string& job_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
if (!job_details)
return;
job_details->job_state = JobDetails::State::kCancelled;
for (const auto& download_guid_pair : job_details->current_fetch_guids) {
GetDownloadService()->CancelDownload(download_guid_pair.first);
download_job_id_map_.erase(download_guid_pair.first);
}
DoUpdateUi(job_id);
}
void BackgroundFetchDelegateBase::MarkJobComplete(const std::string& job_id) {
JobDetails* job_details = GetJobDetails(job_id);
if (job_details->job_state == JobDetails::State::kCancelled) {
OnUiFinished(job_id, /*activated=*/false);
return;
}
job_details->job_state = JobDetails::State::kJobComplete;
// Clear the |job_details| internals that are no longer needed.
job_details->current_fetch_guids.clear();
}
void BackgroundFetchDelegateBase::FailFetch(const std::string& job_id) {
// Save a copy before Abort() deletes the reference.
const std::string unique_id = job_id;
Abort(job_id);
if (auto client = GetClient(unique_id)) {
client->OnJobCancelled(
unique_id,
blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED);
}
}
void BackgroundFetchDelegateBase::OnDownloadStarted(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResponse> response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_id_iter = download_job_id_map_.find(download_guid);
// TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
// OnDownload* methods, then this can be a DCHECK.
if (download_job_id_iter == download_job_id_map_.end())
return;
const std::string& job_id = download_job_id_iter->second;
JobDetails* job_details = GetJobDetails(job_id);
if (job_details->client) {
job_details->client->OnDownloadStarted(job_id, download_guid,
std::move(response));
}
// Update the upload progress.
auto it = job_details->current_fetch_guids.find(download_guid);
DCHECK(it != job_details->current_fetch_guids.end());
job_details->fetch_description->uploaded_bytes += it->second.body_size_bytes;
}
void BackgroundFetchDelegateBase::OnDownloadUpdated(
const std::string& download_guid,
uint64_t bytes_uploaded,
uint64_t bytes_downloaded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_id_iter = download_job_id_map_.find(download_guid);
// TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
// OnDownload* methods, then this can be a DCHECK.
if (download_job_id_iter == download_job_id_map_.end())
return;
const std::string job_id = download_job_id_iter->second;
JobDetails* job_details = GetJobDetails(job_id);
job_details->UpdateInProgressBytes(download_guid, bytes_uploaded,
bytes_downloaded);
if (job_details->fetch_description->download_total_bytes &&
job_details->fetch_description->download_total_bytes <
job_details->GetDownloadedBytes()) {
// Fail the fetch if total download size was set too low.
// We only do this if total download size is specified. If not specified,
// this check is skipped. This is to allow for situations when the
// total download size cannot be known when invoking fetch.
FailFetch(job_id);
return;
}
DoUpdateUi(job_id);
if (job_details->client) {
job_details->client->OnDownloadUpdated(job_id, download_guid,
bytes_uploaded, bytes_downloaded);
}
}
void BackgroundFetchDelegateBase::OnDownloadFailed(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResult> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_id_iter = download_job_id_map_.find(download_guid);
// TODO(crbug.com/779012): When DownloadService fixes cancelled jobs
// potentially calling OnDownloadFailed with a reason other than
// CANCELLED/ABORTED, we should add a DCHECK here.
if (download_job_id_iter == download_job_id_map_.end())
return;
const std::string& job_id = download_job_id_iter->second;
JobDetails* job_details = GetJobDetails(job_id);
job_details->UpdateJobOnDownloadComplete(download_guid);
DoUpdateUi(job_id);
// The client cancelled or aborted the download so no need to notify it.
if (result->failure_reason ==
content::BackgroundFetchResult::FailureReason::CANCELLED) {
return;
}
if (job_details->client) {
job_details->client->OnDownloadComplete(job_id, download_guid,
std::move(result));
}
download_job_id_map_.erase(download_guid);
}
void BackgroundFetchDelegateBase::OnDownloadSucceeded(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResult> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_id_iter = download_job_id_map_.find(download_guid);
// TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
// OnDownload* methods, then this can be a DCHECK.
if (download_job_id_iter == download_job_id_map_.end())
return;
const std::string& job_id = download_job_id_iter->second;
JobDetails* job_details = GetJobDetails(job_id);
job_details->UpdateJobOnDownloadComplete(download_guid);
job_details->fetch_description->downloaded_bytes +=
context_->IsOffTheRecord() ? result->blob_handle->size()
: result->file_size;
DoUpdateUi(job_id);
if (job_details->client) {
job_details->client->OnDownloadComplete(job_id, download_guid,
std::move(result));
}
download_job_id_map_.erase(download_guid);
}
void BackgroundFetchDelegateBase::OnDownloadReceived(
const std::string& download_guid,
download::DownloadParams::StartResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
using StartResult = download::DownloadParams::StartResult;
switch (result) {
case StartResult::ACCEPTED:
// Nothing to do.
break;
case StartResult::UNEXPECTED_GUID:
// The download started in a previous session. Nothing to do.
break;
case StartResult::BACKOFF:
// TODO(delphick): try again later?
NOTREACHED();
break;
case StartResult::UNEXPECTED_CLIENT:
// This really should never happen since we're supplying the
// DownloadClient.
NOTREACHED();
break;
case StartResult::CLIENT_CANCELLED:
// TODO(delphick): do we need to do anything here, since we will have
// cancelled it?
break;
case StartResult::INTERNAL_ERROR:
// TODO(delphick): We need to handle this gracefully.
NOTREACHED();
break;
case StartResult::COUNT:
NOTREACHED();
break;
}
}
bool BackgroundFetchDelegateBase::IsGuidOutstanding(
const std::string& guid) const {
auto job_id_iter = download_job_id_map_.find(guid);
if (job_id_iter == download_job_id_map_.end())
return false;
auto job_details_iter = job_details_map_.find(job_id_iter->second);
if (job_details_iter == job_details_map_.end())
return false;
const std::vector<std::string>& outstanding_guids =
job_details_iter->second.fetch_description->outstanding_guids;
return std::find(outstanding_guids.begin(), outstanding_guids.end(), guid) !=
outstanding_guids.end();
}
void BackgroundFetchDelegateBase::RestartPausedDownload(
const std::string& download_guid) {
auto job_it = download_job_id_map_.find(download_guid);
if (job_it == download_job_id_map_.end())
return;
const std::string& job_id = job_it->second;
GetJobDetails(job_id)->job_state = JobDetails::State::kStartedButPaused;
DoUpdateUi(job_id);
}
std::set<std::string> BackgroundFetchDelegateBase::TakeOutstandingGuids() {
std::set<std::string> outstanding_guids;
for (auto& job_id_details : job_details_map_) {
auto& job_details = job_id_details.second;
// If the job is loaded at this point, then it already started
// in a previous session.
job_details.MarkJobAsStarted();
std::vector<std::string>& job_outstanding_guids =
job_details.fetch_description->outstanding_guids;
for (std::string& outstanding_guid : job_outstanding_guids)
outstanding_guids.insert(std::move(outstanding_guid));
job_outstanding_guids.clear();
}
return outstanding_guids;
}
void BackgroundFetchDelegateBase::GetUploadData(
const std::string& download_guid,
download::GetUploadDataCallback callback) {
auto job_it = download_job_id_map_.find(download_guid);
// TODO(crbug.com/779012): When DownloadService fixes cancelled jobs calling
// client methods, then this can be a DCHECK.
if (job_it == download_job_id_map_.end()) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), /* request_body= */ nullptr));
return;
}
const std::string& job_id = job_it->second;
JobDetails* job_details = GetJobDetails(job_id);
if (job_details->current_fetch_guids.at(download_guid).status ==
JobDetails::RequestData::Status::kAbsent) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), /* request_body= */ nullptr));
return;
}
if (job_details->client) {
job_details->client->GetUploadData(
job_id, download_guid,
base::BindOnce(&BackgroundFetchDelegateBase::DidGetUploadData,
weak_ptr_factory_.GetWeakPtr(), job_id, download_guid,
std::move(callback)));
}
}
void BackgroundFetchDelegateBase::DidGetUploadData(
const std::string& job_id,
const std::string& download_guid,
download::GetUploadDataCallback callback,
blink::mojom::SerializedBlobPtr blob) {
if (!blob || blob->uuid.empty()) {
std::move(callback).Run(/* request_body= */ nullptr);
return;
}
JobDetails* job_details = GetJobDetails(job_id, /*allow_null=*/true);
if (!job_details) {
std::move(callback).Run(/* request_body= */ nullptr);
return;
}
DCHECK(job_details->current_fetch_guids.count(download_guid));
auto& request_data = job_details->current_fetch_guids.at(download_guid);
request_data.body_size_bytes = blob->size;
// Use a Data Pipe to transfer the blob.
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter_remote;
mojo::Remote<blink::mojom::Blob> blob_remote(std::move(blob->blob));
blob_remote->AsDataPipeGetter(
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
request_body->AppendDataPipe(std::move(data_pipe_getter_remote));
std::move(callback).Run(request_body);
}
base::WeakPtr<content::BackgroundFetchDelegate::Client>
BackgroundFetchDelegateBase::GetClient(const std::string& job_id) {
auto it = job_details_map_.find(job_id);
if (it == job_details_map_.end())
return nullptr;
return it->second.client;
}
} // namespace background_fetch