blob: d8bff9355e25f50cd2cdc4b0087e2bbd46c4865e [file] [log] [blame]
// Copyright 2017 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/background_fetch/background_fetch_delegate_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/download/download_request_limiter.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/download/public/background_service/download_params.h"
#include "components/download/public/background_service/download_service.h"
#include "components/offline_items_collection/core/offline_content_aggregator.h"
#include "components/offline_items_collection/core/offline_item.h"
#include "content/public/browser/background_fetch_description.h"
#include "content/public/browser/background_fetch_response.h"
#include "content/public/browser/browser_thread.h"
#include "services/network/public/cpp/features.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/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
#include "url/origin.h"
BackgroundFetchDelegateImpl::BackgroundFetchDelegateImpl(
Profile* profile,
const std::string& provider_namespace)
: profile_(profile),
provider_namespace_(provider_namespace),
offline_content_aggregator_(
OfflineContentAggregatorFactory::GetForBrowserContext(profile)),
weak_ptr_factory_(this) {
DCHECK(profile_);
DCHECK(!provider_namespace_.empty());
offline_content_aggregator_->RegisterProvider(provider_namespace_, this);
// Ensure that downloads UI components are initialized to handle the UI
// updates.
content::BrowserContext::GetDownloadManager(profile_);
}
BackgroundFetchDelegateImpl::~BackgroundFetchDelegateImpl() {
offline_content_aggregator_->UnregisterProvider(provider_namespace_);
}
download::DownloadService* BackgroundFetchDelegateImpl::GetDownloadService() {
if (download_service_)
return download_service_;
download_service_ =
DownloadServiceFactory::GetInstance()->GetForBrowserContext(profile_);
return download_service_;
}
void BackgroundFetchDelegateImpl::Shutdown() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
BackgroundFetchDelegateImpl::JobDetails::UploadData::UploadData(
bool has_upload_data) {
if (has_upload_data)
status = Status::kIncluded;
else
status = Status::kAbsent;
}
BackgroundFetchDelegateImpl::JobDetails::UploadData::~UploadData() = default;
BackgroundFetchDelegateImpl::JobDetails::JobDetails(JobDetails&&) = default;
BackgroundFetchDelegateImpl::JobDetails::JobDetails(
base::WeakPtr<Client> client,
std::unique_ptr<content::BackgroundFetchDescription> fetch_description,
const std::string& provider_namespace,
bool is_off_the_record)
: client(std::move(client)),
offline_item(offline_items_collection::ContentId(
provider_namespace,
fetch_description->job_unique_id)),
job_state(fetch_description->start_paused
? State::kPendingWillStartPaused
: State::kPendingWillStartDownloading),
fetch_description(std::move(fetch_description)) {
offline_item.is_off_the_record = is_off_the_record;
offline_item.original_url = this->fetch_description->origin.GetURL();
UpdateOfflineItem();
}
BackgroundFetchDelegateImpl::JobDetails::~JobDetails() = default;
void BackgroundFetchDelegateImpl::JobDetails::MarkJobAsStarted() {
if (job_state == State::kPendingWillStartDownloading)
job_state = State::kStartedAndDownloading;
else if (job_state == State::kPendingWillStartPaused)
job_state = State::kStartedButPaused;
}
void BackgroundFetchDelegateImpl::JobDetails::UpdateOfflineItem() {
DCHECK_GT(fetch_description->total_parts, 0);
if (ShouldReportProgressBySize()) {
offline_item.progress.value = fetch_description->completed_parts_size;
// If we have completed all downloads, update progress max to
// completed_parts_size in case total_parts_size was set too high. This
// avoid unnecessary jumping in the progress bar.
offline_item.progress.max =
(fetch_description->completed_parts == fetch_description->total_parts)
? fetch_description->completed_parts_size
: fetch_description->total_parts_size;
} else {
offline_item.progress.value = fetch_description->completed_parts;
offline_item.progress.max = fetch_description->total_parts;
}
offline_item.progress.unit =
offline_items_collection::OfflineItemProgressUnit::PERCENTAGE;
offline_item.title = fetch_description->title;
offline_item.promote_origin = true;
offline_item.is_transient = true;
offline_item.is_resumable = true;
using OfflineItemState = offline_items_collection::OfflineItemState;
if (job_state == State::kCancelled) {
offline_item.state = OfflineItemState::CANCELLED;
} else if (fetch_description->completed_parts ==
fetch_description->total_parts) {
// This includes cases when the download failed, or completed but the
// response was an HTTP error, e.g. 404.
offline_item.state = OfflineItemState::COMPLETE;
offline_item.is_openable = true;
} else if (job_state == State::kPendingWillStartPaused ||
job_state == State::kStartedButPaused) {
offline_item.state = OfflineItemState::PAUSED;
} else {
offline_item.state = OfflineItemState::IN_PROGRESS;
}
}
bool BackgroundFetchDelegateImpl::JobDetails::ShouldReportProgressBySize() {
if (!fetch_description->total_parts_size) {
// total_parts_size was not set. Cannot report by size.
return false;
}
if (fetch_description->completed_parts < fetch_description->total_parts &&
fetch_description->completed_parts_size >
fetch_description->total_parts_size) {
// total_parts_size was set too low.
return false;
}
return true;
}
void BackgroundFetchDelegateImpl::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.
// TODO(nator): Move this logic to OfflineItemsCollection, and return icon
// size based on display.
gfx::Size display_size;
#if defined(OS_ANDROID)
display_size = gfx::Size(192, 192);
#endif
std::move(callback).Run(display_size);
}
void BackgroundFetchDelegateImpl::GetPermissionForOrigin(
const url::Origin& origin,
const content::ResourceRequestInfo::WebContentsGetter& wc_getter,
GetPermissionForOriginCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (wc_getter) {
// There is an associated frame so we might need to expose some permission
// UI using the DownloadRequestLimiter.
DownloadRequestLimiter* limiter =
g_browser_process->download_request_limiter();
DCHECK(limiter);
// The fetch should be thought of as one download. So the origin will be
// used as the URL, and the |request_method| is set to GET.
limiter->CanDownload(
wc_getter, origin.GetURL(), "GET",
base::AdaptCallbackForRepeating(base::BindOnce(
&BackgroundFetchDelegateImpl::
DidGetPermissionFromDownloadRequestLimiter,
weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
return;
}
auto* host_content_settings_map =
HostContentSettingsMapFactory::GetForProfile(profile_);
DCHECK(host_content_settings_map);
// This is running from a non-top level frame, use the Automatic Downloads
// content setting.
ContentSetting content_setting = host_content_settings_map->GetContentSetting(
origin.GetURL(), origin.GetURL(),
CONTENT_SETTINGS_TYPE_AUTOMATIC_DOWNLOADS,
std::string() /* resource_identifier */);
// The set of valid settings for automatic downloads is set to
// {CONTENT_SETTING_ALLOW, CONTENT_SETTING_ASK, CONTENT_SETTING_BLOCK}.
switch (content_setting) {
case CONTENT_SETTING_ALLOW:
case CONTENT_SETTING_ASK:
std::move(callback).Run(content::BackgroundFetchPermission::ASK);
return;
case CONTENT_SETTING_BLOCK:
std::move(callback).Run(content::BackgroundFetchPermission::BLOCKED);
return;
case CONTENT_SETTING_DEFAULT:
case CONTENT_SETTING_SESSION_ONLY:
case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT:
case CONTENT_SETTING_NUM_SETTINGS:
NOTREACHED();
}
}
void BackgroundFetchDelegateImpl::DidGetPermissionFromDownloadRequestLimiter(
GetPermissionForOriginCallback callback,
bool has_permission) {
std::move(callback).Run(has_permission
? content::BackgroundFetchPermission::ALLOWED
: content::BackgroundFetchPermission::BLOCKED);
}
void BackgroundFetchDelegateImpl::CreateDownloadJob(
base::WeakPtr<Client> client,
std::unique_ptr<content::BackgroundFetchDescription> fetch_description) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string job_unique_id = fetch_description->job_unique_id;
DCHECK(!job_details_map_.count(job_unique_id));
job_details_map_.emplace(
job_unique_id,
JobDetails(std::move(client), std::move(fetch_description),
provider_namespace_, profile_->IsOffTheRecord()));
}
void BackgroundFetchDelegateImpl::DownloadUrl(
const std::string& job_unique_id,
const std::string& download_guid,
const std::string& method,
const GURL& url,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
const net::HttpRequestHeaders& headers,
bool has_request_body) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(job_details_map_.count(job_unique_id));
DCHECK(!download_job_unique_id_map_.count(download_guid));
download_job_unique_id_map_.emplace(download_guid, job_unique_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.callback = base::Bind(&BackgroundFetchDelegateImpl::OnDownloadReceived,
weak_ptr_factory_.GetWeakPtr());
params.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(traffic_annotation);
JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
if (job_details.job_state == JobDetails::State::kPendingWillStartPaused ||
job_details.job_state ==
JobDetails::State::kPendingWillStartDownloading) {
// Create a notification.
for (auto* observer : observers_)
observer->OnItemsAdded({job_details.offline_item});
job_details.MarkJobAsStarted();
}
if (job_details.job_state == JobDetails::State::kStartedButPaused) {
job_details.on_resume =
base::BindOnce(&BackgroundFetchDelegateImpl::StartDownload,
GetWeakPtr(), job_unique_id, params, has_request_body);
} else {
StartDownload(job_unique_id, params, has_request_body);
}
UpdateOfflineItemAndUpdateObservers(&job_details);
}
void BackgroundFetchDelegateImpl::StartDownload(
const std::string& job_unique_id,
const download::DownloadParams& params,
bool has_request_body) {
DCHECK(job_details_map_.count(job_unique_id));
JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
job_details.current_fetch_guids.emplace(params.guid, has_request_body);
GetDownloadService()->StartDownload(params);
}
void BackgroundFetchDelegateImpl::Abort(const std::string& job_unique_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto job_details_iter = job_details_map_.find(job_unique_id);
if (job_details_iter == job_details_map_.end())
return;
JobDetails& job_details = job_details_iter->second;
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_unique_id_map_.erase(download_guid_pair.first);
}
UpdateOfflineItemAndUpdateObservers(&job_details);
}
void BackgroundFetchDelegateImpl::MarkJobComplete(
const std::string& job_unique_id) {
job_details_map_.erase(job_unique_id);
}
void BackgroundFetchDelegateImpl::UpdateUI(
const std::string& job_unique_id,
const base::Optional<std::string>& title,
const base::Optional<SkBitmap>& icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(title || icon); // One of the UI options must be updatable.
DCHECK(!icon || !icon->isNull()); // The |icon|, if provided, is not null.
auto job_details_iter = job_details_map_.find(job_unique_id);
if (job_details_iter == job_details_map_.end())
return;
JobDetails& job_details = job_details_iter->second;
// Update the title, if it's different.
if (title && job_details.fetch_description->title != *title)
job_details.fetch_description->title = *title;
if (icon) {
job_details.fetch_description->icon = *icon;
job_details.offline_item.refresh_visuals = true;
}
UpdateOfflineItemAndUpdateObservers(&job_details);
}
void BackgroundFetchDelegateImpl::OnDownloadStarted(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResponse> response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_unique_id_iter =
download_job_unique_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_unique_id_iter == download_job_unique_id_map_.end())
return;
const std::string& job_unique_id = download_job_unique_id_iter->second;
auto& job_details = job_details_map_.find(job_unique_id)->second;
if (job_details.client) {
job_details.client->OnDownloadStarted(job_unique_id, download_guid,
std::move(response));
}
// Release the request body blob, if any.
DCHECK(job_details.current_fetch_guids.count(download_guid));
job_details.current_fetch_guids.at(download_guid).request_body_blob.reset();
}
void BackgroundFetchDelegateImpl::OnDownloadUpdated(
const std::string& download_guid,
uint64_t bytes_uploaded,
uint64_t bytes_downloaded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_unique_id_iter =
download_job_unique_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_unique_id_iter == download_job_unique_id_map_.end())
return;
const std::string& job_unique_id = download_job_unique_id_iter->second;
// This will update the progress bar.
DCHECK(job_details_map_.count(job_unique_id));
JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
job_details.fetch_description->completed_parts_size = bytes_downloaded;
if (job_details.fetch_description->total_parts_size &&
job_details.fetch_description->total_parts_size <
job_details.fetch_description->completed_parts_size) {
// 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_unique_id);
return;
}
UpdateOfflineItemAndUpdateObservers(&job_details);
if (job_details.client)
job_details.client->OnDownloadUpdated(job_unique_id, download_guid,
bytes_uploaded, bytes_downloaded);
}
void BackgroundFetchDelegateImpl::OnDownloadFailed(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResult> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_unique_id_iter =
download_job_unique_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_unique_id_iter == download_job_unique_id_map_.end())
return;
const std::string& job_unique_id = download_job_unique_id_iter->second;
JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
++job_details.fetch_description->completed_parts;
UpdateOfflineItemAndUpdateObservers(&job_details);
// 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_unique_id, download_guid,
std::move(result));
}
job_details.current_fetch_guids.erase(download_guid);
download_job_unique_id_map_.erase(download_guid);
}
void BackgroundFetchDelegateImpl::OnDownloadSucceeded(
const std::string& download_guid,
std::unique_ptr<content::BackgroundFetchResult> result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
auto download_job_unique_id_iter =
download_job_unique_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_unique_id_iter == download_job_unique_id_map_.end())
return;
const std::string& job_unique_id = download_job_unique_id_iter->second;
JobDetails& job_details = job_details_map_.find(job_unique_id)->second;
++job_details.fetch_description->completed_parts;
job_details.fetch_description->completed_parts_size =
profile_->IsOffTheRecord() ? result->blob_handle->size()
: result->file_size;
UpdateOfflineItemAndUpdateObservers(&job_details);
if (job_details.client) {
job_details.client->OnDownloadComplete(job_unique_id, download_guid,
std::move(result));
}
job_details.current_fetch_guids.erase(download_guid);
download_job_unique_id_map_.erase(download_guid);
}
void BackgroundFetchDelegateImpl::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;
}
}
// Much of the code in offline_item_collection is not re-entrant, so this should
// not be called from any of the OfflineContentProvider-inherited methods.
void BackgroundFetchDelegateImpl::UpdateOfflineItemAndUpdateObservers(
JobDetails* job_details) {
job_details->UpdateOfflineItem();
for (auto* observer : observers_)
observer->OnItemUpdated(job_details->offline_item);
}
void BackgroundFetchDelegateImpl::OpenItem(
offline_items_collection::LaunchLocation location,
const offline_items_collection::ContentId& id) {
if (auto client = GetClient(id.id))
client->OnUIActivated(id.id);
}
void BackgroundFetchDelegateImpl::RemoveItem(
const offline_items_collection::ContentId& id) {
// TODO(delphick): Support removing items. (Not sure when this would actually
// get called though).
NOTIMPLEMENTED();
}
void BackgroundFetchDelegateImpl::FailFetch(const std::string& job_unique_id) {
// Save a copy before Abort() deletes the reference.
const std::string unique_id = job_unique_id;
Abort(job_unique_id);
if (auto client = GetClient(unique_id)) {
client->OnJobCancelled(
unique_id,
blink::mojom::BackgroundFetchFailureReason::DOWNLOAD_TOTAL_EXCEEDED);
}
}
void BackgroundFetchDelegateImpl::CancelDownload(
const offline_items_collection::ContentId& id) {
// Save a copy before Abort() deletes the reference.
const std::string unique_id = id.id;
Abort(unique_id);
if (auto client = GetClient(unique_id)) {
client->OnJobCancelled(
unique_id,
blink::mojom::BackgroundFetchFailureReason::CANCELLED_FROM_UI);
}
}
void BackgroundFetchDelegateImpl::PauseDownload(
const offline_items_collection::ContentId& id) {
auto job_details_iter = job_details_map_.find(id.id);
if (job_details_iter == job_details_map_.end())
return;
JobDetails& job_details = job_details_iter->second;
job_details.job_state = JobDetails::State::kStartedButPaused;
job_details.UpdateOfflineItem();
for (auto& download_guid_pair : job_details.current_fetch_guids)
GetDownloadService()->PauseDownload(download_guid_pair.first);
}
void BackgroundFetchDelegateImpl::ResumeDownload(
const offline_items_collection::ContentId& id,
bool has_user_gesture) {
auto job_details_iter = job_details_map_.find(id.id);
if (job_details_iter == job_details_map_.end())
return;
JobDetails& job_details = job_details_iter->second;
job_details.job_state = JobDetails::State::kStartedAndDownloading;
job_details.UpdateOfflineItem();
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 BackgroundFetchDelegateImpl::GetItemById(
const offline_items_collection::ContentId& id,
SingleItemCallback callback) {
auto it = job_details_map_.find(id.id);
base::Optional<offline_items_collection::OfflineItem> offline_item;
if (it != job_details_map_.end())
offline_item = it->second.offline_item;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), offline_item));
}
void BackgroundFetchDelegateImpl::GetAllItems(MultipleItemCallback callback) {
OfflineItemList item_list;
for (auto& entry : job_details_map_)
item_list.push_back(entry.second.offline_item);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), item_list));
}
void BackgroundFetchDelegateImpl::GetVisualsForItem(
const offline_items_collection::ContentId& id,
VisualsCallback callback) {
// GetVisualsForItem mustn't be called directly since offline_items_collection
// is not re-entrant and it must be called even if there are no visuals.
auto visuals =
std::make_unique<offline_items_collection::OfflineItemVisuals>();
auto it = job_details_map_.find(id.id);
if (it != job_details_map_.end()) {
visuals->icon =
gfx::Image::CreateFrom1xBitmap(it->second.fetch_description->icon);
it->second.offline_item.refresh_visuals = false;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), id, std::move(visuals)));
}
void BackgroundFetchDelegateImpl::GetShareInfoForItem(
const offline_items_collection::ContentId& id,
ShareCallback callback) {
// TODO(xingliu): Provide OfflineItemShareInfo to |callback|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), id,
nullptr /* OfflineItemShareInfo */));
}
void BackgroundFetchDelegateImpl::AddObserver(Observer* observer) {
DCHECK(!observers_.count(observer));
observers_.insert(observer);
}
void BackgroundFetchDelegateImpl::RemoveObserver(Observer* observer) {
observers_.erase(observer);
}
bool BackgroundFetchDelegateImpl::IsGuidOutstanding(
const std::string& guid) const {
auto unique_id_it = download_job_unique_id_map_.find(guid);
if (unique_id_it == download_job_unique_id_map_.end())
return false;
auto job_details_it = job_details_map_.find(unique_id_it->second);
if (job_details_it == job_details_map_.end())
return false;
std::vector<std::string>& outstanding_guids =
job_details_it->second.fetch_description->outstanding_guids;
return std::find(outstanding_guids.begin(), outstanding_guids.end(), guid) !=
outstanding_guids.end();
}
void BackgroundFetchDelegateImpl::RestartPausedDownload(
const std::string& download_guid) {
auto job_it = download_job_unique_id_map_.find(download_guid);
if (job_it == download_job_unique_id_map_.end())
return;
const std::string& unique_id = job_it->second;
DCHECK(job_details_map_.find(unique_id) != job_details_map_.end());
JobDetails& job_details = job_details_map_.find(unique_id)->second;
job_details.job_state = JobDetails::State::kStartedButPaused;
UpdateOfflineItemAndUpdateObservers(&job_details);
}
std::set<std::string> BackgroundFetchDelegateImpl::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 BackgroundFetchDelegateImpl::GetUploadData(
const std::string& download_guid,
download::GetUploadDataCallback callback) {
auto job_it = download_job_unique_id_map_.find(download_guid);
DCHECK(job_it != download_job_unique_id_map_.end());
JobDetails& job_details = job_details_map_.find(job_it->second)->second;
if (job_details.current_fetch_guids.at(download_guid).status ==
JobDetails::UploadData::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_it->second, download_guid,
base::BindOnce(&BackgroundFetchDelegateImpl::DidGetUploadData,
weak_ptr_factory_.GetWeakPtr(), job_it->second,
download_guid, std::move(callback)));
}
}
void BackgroundFetchDelegateImpl::DidGetUploadData(
const std::string& unique_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;
}
auto job_it = job_details_map_.find(unique_id);
if (job_it == job_details_map_.end()) {
std::move(callback).Run(/* request_body= */ nullptr);
return;
}
auto request_body = base::MakeRefCounted<network::ResourceRequestBody>();
if (base::FeatureList::IsEnabled(network::features::kNetworkService) ||
profile_->IsOffTheRecord()) {
// Use a Data Pipe to transfer the blob.
network::mojom::DataPipeGetterPtr data_pipe_getter_ptr;
blink::mojom::BlobPtr blob_ptr(std::move(blob->blob));
blob_ptr->AsDataPipeGetter(MakeRequest(&data_pipe_getter_ptr));
request_body->AppendDataPipe(std::move(data_pipe_getter_ptr));
} else {
// Use the blob itself and store the handle for the duration of the upload.
request_body->AppendBlob(blob->uuid);
auto& job_details = job_it->second;
DCHECK(job_details.current_fetch_guids.count(download_guid));
job_details.current_fetch_guids.at(download_guid).request_body_blob =
std::move(blob);
}
std::move(callback).Run(request_body);
}
base::WeakPtr<content::BackgroundFetchDelegate::Client>
BackgroundFetchDelegateImpl::GetClient(const std::string& job_unique_id) {
auto it = job_details_map_.find(job_unique_id);
if (it == job_details_map_.end())
return nullptr;
return it->second.client;
}