blob: 2ad72a5d4cbfa1fba260812816bb88d4dd123b9a [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 "content/browser/background_fetch/background_fetch_data_manager.h"
#include <utility>
#include "base/command_line.h"
#include "base/containers/queue.h"
#include "base/guid.h"
#include "base/time/time.h"
#include "content/browser/background_fetch/background_fetch_constants.h"
#include "content/browser/background_fetch/background_fetch_cross_origin_filter.h"
#include "content/browser/background_fetch/background_fetch_request_info.h"
#include "content/browser/background_fetch/storage/cleanup_task.h"
#include "content/browser/background_fetch/storage/create_metadata_task.h"
#include "content/browser/background_fetch/storage/database_task.h"
#include "content/browser/background_fetch/storage/delete_registration_task.h"
#include "content/browser/background_fetch/storage/get_developer_ids_task.h"
#include "content/browser/background_fetch/storage/get_metadata_task.h"
#include "content/browser/background_fetch/storage/get_num_requests_task.h"
#include "content/browser/background_fetch/storage/get_settled_fetches_task.h"
#include "content/browser/background_fetch/storage/mark_registration_for_deletion_task.h"
#include "content/browser/background_fetch/storage/mark_request_complete_task.h"
#include "content/browser/background_fetch/storage/start_next_pending_request_task.h"
#include "content/browser/background_fetch/storage/update_registration_ui_task.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/cache_storage/cache_storage_manager.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
namespace content {
namespace {
// Returns whether the response contained in the Background Fetch |request| is
// considered OK. See https://fetch.spec.whatwg.org/#ok-status aka a successful
// 2xx status per https://tools.ietf.org/html/rfc7231#section-6.3.
bool IsOK(const BackgroundFetchRequestInfo& request) {
int status = request.GetResponseCode();
return status >= 200 && status < 300;
}
// Helper function to convert a BackgroundFetchRegistration proto into a
// BackgroundFetchRegistration struct, and call the appropriate callback.
void GetRegistrationFromMetadata(
BackgroundFetchDataManager::GetRegistrationCallback callback,
blink::mojom::BackgroundFetchError error,
std::unique_ptr<proto::BackgroundFetchMetadata> metadata_proto) {
if (!metadata_proto) {
std::move(callback).Run(error, nullptr);
return;
}
const auto& registration_proto = metadata_proto->registration();
auto registration = std::make_unique<BackgroundFetchRegistration>();
registration->developer_id = registration_proto.developer_id();
registration->unique_id = registration_proto.unique_id();
// TODO(crbug.com/774054): Uploads are not yet supported.
registration->upload_total = registration_proto.upload_total();
registration->uploaded = registration_proto.uploaded();
registration->download_total = registration_proto.download_total();
registration->downloaded = registration_proto.downloaded();
std::move(callback).Run(error, std::move(registration));
}
} // namespace
// The Registration Data class encapsulates the data stored for a particular
// Background Fetch registration. This roughly matches the on-disk format that
// will be adhered to in the future.
class BackgroundFetchDataManager::RegistrationData {
public:
RegistrationData(const BackgroundFetchRegistrationId& registration_id,
const std::vector<ServiceWorkerFetchRequest>& requests,
const BackgroundFetchOptions& options,
const SkBitmap& icon)
: registration_id_(registration_id), options_(options), icon_(icon) {
int request_index = 0;
// Convert the given |requests| to BackgroundFetchRequestInfo objects.
for (const ServiceWorkerFetchRequest& fetch_request : requests) {
pending_requests_.push(base::MakeRefCounted<BackgroundFetchRequestInfo>(
request_index++, fetch_request));
}
}
~RegistrationData() = default;
// Returns whether there are remaining requests on the request queue.
bool HasPendingRequests() const { return !pending_requests_.empty(); }
// Consumes a request from the queue that is to be fetched.
scoped_refptr<BackgroundFetchRequestInfo> PopNextPendingRequest() {
DCHECK(!pending_requests_.empty());
auto request = pending_requests_.front();
pending_requests_.pop();
request->InitializeDownloadGuid();
// The |request| is considered to be active now.
active_requests_.push_back(request);
return request;
}
// Marks the |request| as having completed. Verifies that the |request| is
// currently active and moves it to the |completed_requests_| vector.
void MarkRequestAsComplete(BackgroundFetchRequestInfo* request) {
const auto iter = std::find_if(
active_requests_.begin(), active_requests_.end(),
[&request](scoped_refptr<BackgroundFetchRequestInfo> active_request) {
return active_request->request_index() == request->request_index();
});
// The |request| must have been consumed from this RegistrationData.
DCHECK(iter != active_requests_.end());
completed_requests_.push_back(*iter);
active_requests_.erase(iter);
complete_requests_downloaded_bytes_ += request->GetFileSize();
}
// Returns the vector with all completed requests part of this registration.
const std::vector<scoped_refptr<BackgroundFetchRequestInfo>>&
GetCompletedRequests() const {
return completed_requests_;
}
void SetTitle(const std::string& title) { options_.title = title; }
const BackgroundFetchRegistrationId& registration_id() const {
return registration_id_;
}
const BackgroundFetchOptions& options() const { return options_; }
uint64_t GetDownloaded() const { return complete_requests_downloaded_bytes_; }
size_t GetNumCompletedRequests() const { return completed_requests_.size(); }
private:
BackgroundFetchRegistrationId registration_id_;
BackgroundFetchOptions options_;
SkBitmap icon_;
// Number of bytes downloaded as part of completed downloads. (In-progress
// downloads are tracked elsewhere).
uint64_t complete_requests_downloaded_bytes_ = 0;
base::queue<scoped_refptr<BackgroundFetchRequestInfo>> pending_requests_;
std::vector<scoped_refptr<BackgroundFetchRequestInfo>> active_requests_;
// TODO(peter): Right now it's safe for this to be a vector because we only
// allow a single parallel request. That stops when we start allowing more.
static_assert(kMaximumBackgroundFetchParallelRequests == 1,
"RegistrationData::completed_requests_ assumes no parallelism");
std::vector<scoped_refptr<BackgroundFetchRequestInfo>> completed_requests_;
DISALLOW_COPY_AND_ASSIGN(RegistrationData);
};
BackgroundFetchDataManager::BackgroundFetchDataManager(
BrowserContext* browser_context,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
scoped_refptr<CacheStorageContextImpl> cache_storage_context)
: service_worker_context_(std::move(service_worker_context)),
cache_storage_context_(std::move(cache_storage_context)),
weak_ptr_factory_(this) {
// Constructed on the UI thread, then used on the IO thread.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(browser_context);
// Store the blob storage context for the given |browser_context|.
blob_storage_context_ =
base::WrapRefCounted(ChromeBlobStorageContext::GetFor(browser_context));
DCHECK(blob_storage_context_);
BrowserThread::PostAfterStartupTask(
FROM_HERE, BrowserThread::GetTaskRunnerForThread(BrowserThread::IO),
// Normally weak pointers must be obtained on the IO thread, but it's ok
// here as the factory cannot be destroyed before the constructor ends.
base::BindOnce(&BackgroundFetchDataManager::Cleanup,
weak_ptr_factory_.GetWeakPtr()));
}
void BackgroundFetchDataManager::Cleanup() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::CleanupTask>(
this, GetCacheStorageManager()));
}
}
BackgroundFetchDataManager::~BackgroundFetchDataManager() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
void BackgroundFetchDataManager::CreateRegistration(
const BackgroundFetchRegistrationId& registration_id,
const std::vector<ServiceWorkerFetchRequest>& requests,
const BackgroundFetchOptions& options,
const SkBitmap& icon,
GetRegistrationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
auto registration_callback =
base::BindOnce(&GetRegistrationFromMetadata, std::move(callback));
AddDatabaseTask(std::make_unique<background_fetch::CreateMetadataTask>(
this, registration_id, requests, options,
std::move(registration_callback)));
return;
}
// New registrations should never re-use a |unique_id|.
DCHECK_EQ(0u, registrations_.count(registration_id.unique_id()));
auto developer_id_tuple =
std::make_tuple(registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.developer_id());
if (active_registration_unique_ids_.count(developer_id_tuple)) {
std::move(callback).Run(
blink::mojom::BackgroundFetchError::DUPLICATED_DEVELOPER_ID, nullptr);
return;
}
// Mark |unique_id| as the currently active registration for
// |developer_id_tuple|.
active_registration_unique_ids_.emplace(std::move(developer_id_tuple),
registration_id.unique_id());
// Create the |RegistrationData|, and store it for easy access.
registrations_.emplace(registration_id.unique_id(),
std::make_unique<RegistrationData>(
registration_id, requests, options, icon));
// Re-use GetRegistration to compile the BackgroundFetchRegistration object.
// WARNING: GetRegistration doesn't use the |unique_id| when looking up the
// registration data. That's fine here, since we are calling it synchronously
// immediately after writing the |unique_id| to |active_unique_ids_|. But if
// GetRegistation becomes async, it will no longer be safe to do this.
GetRegistration(registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.developer_id(),
std::move(callback));
}
void BackgroundFetchDataManager::GetMetadata(
int64_t service_worker_registration_id,
const url::Origin& origin,
const std::string& developer_id,
GetMetadataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
return;
}
AddDatabaseTask(std::make_unique<background_fetch::GetMetadataTask>(
this, service_worker_registration_id, origin, developer_id,
std::move(callback)));
}
void BackgroundFetchDataManager::GetRegistration(
int64_t service_worker_registration_id,
const url::Origin& origin,
const std::string& developer_id,
GetRegistrationCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
auto registration_callback =
base::BindOnce(&GetRegistrationFromMetadata, std::move(callback));
GetMetadata(service_worker_registration_id, origin, developer_id,
std::move(registration_callback));
return;
}
auto developer_id_tuple =
std::make_tuple(service_worker_registration_id, origin, developer_id);
auto iter = active_registration_unique_ids_.find(developer_id_tuple);
if (iter == active_registration_unique_ids_.end()) {
std::move(callback).Run(blink::mojom::BackgroundFetchError::INVALID_ID,
nullptr /* registration */);
return;
}
const std::string& unique_id = iter->second;
DCHECK_EQ(1u, registrations_.count(unique_id));
RegistrationData* data = registrations_[unique_id].get();
// Compile the BackgroundFetchRegistration object for the developer.
auto registration = std::make_unique<BackgroundFetchRegistration>();
registration->developer_id = developer_id;
registration->unique_id = unique_id;
// TODO(crbug.com/774054): Uploads are not yet supported.
registration->upload_total = 0;
registration->uploaded = 0;
registration->download_total = data->options().download_total;
registration->downloaded = data->GetDownloaded();
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE,
std::move(registration));
}
void BackgroundFetchDataManager::UpdateRegistrationUI(
const BackgroundFetchRegistrationId& registration_id,
const std::string& title,
blink::mojom::BackgroundFetchService::UpdateUICallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(
std::make_unique<background_fetch::UpdateRegistrationUITask>(
this, registration_id, title, std::move(callback)));
return;
}
auto registrations_iter = registrations_.find(registration_id.unique_id());
if (registrations_iter == registrations_.end()) { // Not found.
std::move(callback).Run(blink::mojom::BackgroundFetchError::INVALID_ID);
return;
}
// Update stored registration.
registrations_iter->second->SetTitle(title);
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE);
}
void BackgroundFetchDataManager::PopNextRequest(
const BackgroundFetchRegistrationId& registration_id,
NextRequestCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
auto start_next_request = base::BindOnce(
&BackgroundFetchDataManager::AddStartNextPendingRequestTask,
weak_ptr_factory_.GetWeakPtr(),
registration_id.service_worker_registration_id(), std::move(callback));
// Get the associated metadata, and add a StartNextPendingRequestTask.
GetMetadata(registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.developer_id(),
std::move(start_next_request));
return;
}
if (!IsActive(registration_id)) {
// Stop giving out requests as registration was aborted (or otherwise
// finished).
std::move(callback).Run(nullptr /* request */);
return;
}
auto registration_iter = registrations_.find(registration_id.unique_id());
DCHECK(registration_iter != registrations_.end());
RegistrationData* registration_data = registration_iter->second.get();
scoped_refptr<BackgroundFetchRequestInfo> next_request;
if (registration_data->HasPendingRequests())
next_request = registration_data->PopNextPendingRequest();
std::move(callback).Run(std::move(next_request));
}
void BackgroundFetchDataManager::AddStartNextPendingRequestTask(
int64_t service_worker_registration_id,
NextRequestCallback callback,
blink::mojom::BackgroundFetchError error,
std::unique_ptr<proto::BackgroundFetchMetadata> metadata) {
if (!metadata) {
// Stop giving out requests as registration aborted (or otherwise finished).
std::move(callback).Run(nullptr /* request */);
return;
}
DCHECK_EQ(error, blink::mojom::BackgroundFetchError::NONE);
AddDatabaseTask(
std::make_unique<background_fetch::StartNextPendingRequestTask>(
this, service_worker_registration_id, std::move(metadata),
std::move(callback)));
}
void BackgroundFetchDataManager::MarkRequestAsComplete(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
BackgroundFetchScheduler::MarkedCompleteCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::MarkRequestCompleteTask>(
this, registration_id, request, GetCacheStorageManager(),
std::move(callback)));
return;
}
auto iter = registrations_.find(registration_id.unique_id());
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
registration_data->MarkRequestAsComplete(request);
std::move(callback).Run();
}
void BackgroundFetchDataManager::GetSettledFetchesForRegistration(
const BackgroundFetchRegistrationId& registration_id,
SettledFetchesCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::GetSettledFetchesTask>(
this, registration_id, GetCacheStorageManager(), std::move(callback)));
return;
}
auto iter = registrations_.find(registration_id.unique_id());
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
const std::vector<scoped_refptr<BackgroundFetchRequestInfo>>& requests =
registration_data->GetCompletedRequests();
bool background_fetch_succeeded = true;
std::vector<BackgroundFetchSettledFetch> settled_fetches;
settled_fetches.reserve(requests.size());
std::vector<std::unique_ptr<storage::BlobDataHandle>> blob_data_handles;
for (const auto& request : requests) {
BackgroundFetchSettledFetch settled_fetch;
settled_fetch.request = request->fetch_request();
// The |filter| decides which values can be passed on to the Service Worker.
BackgroundFetchCrossOriginFilter filter(registration_id.origin(), *request);
background_fetch_succeeded =
FillServiceWorkerResponse(*request, registration_id.origin(),
&settled_fetch.response) &&
background_fetch_succeeded;
settled_fetches.push_back(settled_fetch);
}
std::move(callback).Run(
blink::mojom::BackgroundFetchError::NONE, background_fetch_succeeded,
std::move(settled_fetches), std::move(blob_data_handles));
}
bool BackgroundFetchDataManager::FillServiceWorkerResponse(
const BackgroundFetchRequestInfo& request,
const url::Origin& origin,
ServiceWorkerResponse* response) {
DCHECK(response);
response->url_list = request.GetURLChain();
response->response_type = network::mojom::FetchResponseType::kDefault;
// TODO(crbug.com/838837): settled_fetch.response.error
response->response_time = request.GetResponseTime();
// TODO(crbug.com/838837): settled_fetch.response.cors_exposed_header_names
BackgroundFetchCrossOriginFilter filter(origin, request);
if (!filter.CanPopulateBody()) {
// TODO(crbug.com/711354): Consider Background Fetches as failed when the
// response cannot be relayed to the developer.
return false;
}
// Include the status code, status text and the response's body as a blob
// when this is allowed by the CORS protocol.
response->status_code = request.GetResponseCode();
response->status_text = request.GetResponseText();
response->headers.insert(request.GetResponseHeaders().begin(),
request.GetResponseHeaders().end());
if (request.GetFileSize() > 0) {
DCHECK(!request.GetFilePath().empty());
DCHECK(blob_storage_context_);
auto blob_builder =
std::make_unique<storage::BlobDataBuilder>(base::GenerateGUID());
blob_builder->AppendFile(request.GetFilePath(), 0 /* offset */,
request.GetFileSize(),
base::Time() /* expected_modification_time */);
auto blob_data_handle = GetBlobStorageContext(blob_storage_context_.get())
->AddFinishedBlob(std::move(blob_builder));
// TODO(peter): Appropriately handle !blob_data_handle
if (blob_data_handle) {
response->blob_uuid = blob_data_handle->uuid();
response->blob_size = blob_data_handle->size();
blink::mojom::BlobPtr blob_ptr;
storage::BlobImpl::Create(
std::make_unique<storage::BlobDataHandle>(*blob_data_handle),
MakeRequest(&blob_ptr));
response->blob =
base::MakeRefCounted<storage::BlobHandle>(std::move(blob_ptr));
}
}
return IsOK(request);
}
void BackgroundFetchDataManager::MarkRegistrationForDeletion(
const BackgroundFetchRegistrationId& registration_id,
HandleBackgroundFetchErrorCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(
std::make_unique<background_fetch::MarkRegistrationForDeletionTask>(
this, registration_id, std::move(callback)));
return;
}
auto developer_id_tuple =
std::make_tuple(registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.developer_id());
auto active_unique_id_iter =
active_registration_unique_ids_.find(developer_id_tuple);
// The |unique_id| must also match, as a website can create multiple
// registrations with the same |developer_id_tuple| (even though only one can
// be active at once).
if (active_unique_id_iter == active_registration_unique_ids_.end() ||
active_unique_id_iter->second != registration_id.unique_id()) {
std::move(callback).Run(blink::mojom::BackgroundFetchError::INVALID_ID);
return;
}
active_registration_unique_ids_.erase(active_unique_id_iter);
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE);
}
void BackgroundFetchDataManager::DeleteRegistration(
const BackgroundFetchRegistrationId& registration_id,
HandleBackgroundFetchErrorCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::DeleteRegistrationTask>(
this, registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.unique_id(),
GetCacheStorageManager(), std::move(callback)));
return;
}
DCHECK(!IsActive(registration_id))
<< "MarkRegistrationForDeletion must already have been called";
std::move(callback).Run(registrations_.erase(registration_id.unique_id())
? blink::mojom::BackgroundFetchError::NONE
: blink::mojom::BackgroundFetchError::INVALID_ID);
}
void BackgroundFetchDataManager::GetDeveloperIdsForServiceWorker(
int64_t service_worker_registration_id,
const url::Origin& origin,
blink::mojom::BackgroundFetchService::GetDeveloperIdsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::GetDeveloperIdsTask>(
this, service_worker_registration_id, origin, std::move(callback)));
return;
}
std::vector<std::string> developer_ids;
for (const auto& entry : active_registration_unique_ids_) {
if (service_worker_registration_id == std::get<0>(entry.first) &&
origin == std::get<1>(entry.first)) {
developer_ids.emplace_back(std::get<2>(entry.first));
}
}
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE,
developer_ids);
}
void BackgroundFetchDataManager::GetNumCompletedRequests(
const BackgroundFetchRegistrationId& registration_id,
NumRequestsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBackgroundFetchPersistence)) {
AddDatabaseTask(std::make_unique<background_fetch::GetNumRequestsTask>(
this, registration_id, background_fetch::RequestType::kCompleted,
std::move(callback)));
return;
}
std::move(callback).Run(registrations_.find(registration_id.unique_id())
->second->GetNumCompletedRequests());
}
CacheStorageManager* BackgroundFetchDataManager::GetCacheStorageManager() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorageManager* manager = cache_storage_context_->cache_manager();
DCHECK(manager);
return manager;
}
bool BackgroundFetchDataManager::IsActive(
const BackgroundFetchRegistrationId& registration_id) {
auto developer_id_tuple =
std::make_tuple(registration_id.service_worker_registration_id(),
registration_id.origin(), registration_id.developer_id());
auto active_unique_id_iter =
active_registration_unique_ids_.find(developer_id_tuple);
// The |unique_id| must also match, as a website can create multiple
// registrations with the same |developer_id_tuple| (even though only one can
// be active at once).
return active_unique_id_iter != active_registration_unique_ids_.end() &&
active_unique_id_iter->second == registration_id.unique_id();
}
void BackgroundFetchDataManager::AddDatabaseTask(
std::unique_ptr<background_fetch::DatabaseTask> task) {
database_tasks_.push(std::move(task));
if (database_tasks_.size() == 1)
database_tasks_.front()->Start();
}
void BackgroundFetchDataManager::OnDatabaseTaskFinished(
background_fetch::DatabaseTask* task) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(!database_tasks_.empty());
DCHECK_EQ(database_tasks_.front().get(), task);
database_tasks_.pop();
if (!database_tasks_.empty())
database_tasks_.front()->Start();
}
} // namespace content