blob: 2fcb85e21e19ebd5937f96b55a6da7ec01e73cbb [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 <algorithm>
#include <queue>
#include "base/memory/ptr_util.h"
#include "content/browser/background_fetch/background_fetch_constants.h"
#include "content/browser/background_fetch/background_fetch_context.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/blob_storage/chrome_blob_storage_context.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/download_item.h"
#include "third_party/WebKit/public/platform/modules/serviceworker/WebServiceWorkerResponseType.h"
namespace content {
// 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;
}
// 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 std::vector<ServiceWorkerFetchRequest>& requests,
const BackgroundFetchOptions& options)
: options_(options) {
int request_index = 0;
// Convert the given |requests| to BackgroundFetchRequestInfo objects.
for (const ServiceWorkerFetchRequest& fetch_request : requests) {
scoped_refptr<BackgroundFetchRequestInfo> request =
new BackgroundFetchRequestInfo(request_index++, fetch_request);
pending_requests_.push(std::move(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> GetPendingRequest() {
DCHECK(!pending_requests_.empty());
auto request = pending_requests_.front();
pending_requests_.pop();
// The |request| is considered to be active now.
active_requests_.push_back(request);
return request;
}
// Marks the |request| as having started with the given |download_guid|.
// Persistent storage needs to store the association so we can resume fetches
// after a browser restart, here we just verify that the |request| is active.
void MarkRequestAsStarted(BackgroundFetchRequestInfo* request,
const std::string& download_guid) {
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());
}
// 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);
}
// Returns the vector with all completed requests part of this registration.
const std::vector<scoped_refptr<BackgroundFetchRequestInfo>>&
GetCompletedRequests() const {
return completed_requests_;
}
private:
BackgroundFetchOptions options_;
std::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)
: weak_ptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(browser_context);
// Store the blob storage context for the given |browser_context|.
blob_storage_context_ =
make_scoped_refptr(ChromeBlobStorageContext::GetFor(browser_context));
DCHECK(blob_storage_context_);
}
BackgroundFetchDataManager::~BackgroundFetchDataManager() = default;
void BackgroundFetchDataManager::CreateRegistration(
const BackgroundFetchRegistrationId& registration_id,
const std::vector<ServiceWorkerFetchRequest>& requests,
const BackgroundFetchOptions& options,
CreateRegistrationCallback callback) {
if (registrations_.find(registration_id) != registrations_.end()) {
std::move(callback).Run(
blink::mojom::BackgroundFetchError::DUPLICATED_TAG,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>());
return;
}
std::unique_ptr<RegistrationData> registration_data =
base::MakeUnique<RegistrationData>(requests, options);
// Create a vector with the initial requests to feed the Job Controller with.
std::vector<scoped_refptr<BackgroundFetchRequestInfo>> initial_requests;
for (size_t i = 0; i < kMaximumBackgroundFetchParallelRequests; ++i) {
if (!registration_data->HasPendingRequests())
break;
initial_requests.push_back(registration_data->GetPendingRequest());
}
// Store the created |registration_data| so that we can easily access it.
registrations_.insert(
std::make_pair(registration_id, std::move(registration_data)));
// Inform the |callback| of the newly created registration.
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE,
std::move(initial_requests));
}
void BackgroundFetchDataManager::MarkRequestAsStarted(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
const std::string& download_guid) {
auto iter = registrations_.find(registration_id);
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
registration_data->MarkRequestAsStarted(request, download_guid);
}
void BackgroundFetchDataManager::MarkRequestAsCompleteAndGetNextRequest(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchRequestInfo* request,
NextRequestCallback callback) {
auto iter = registrations_.find(registration_id);
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
registration_data->MarkRequestAsComplete(request);
scoped_refptr<BackgroundFetchRequestInfo> next_request;
if (registration_data->HasPendingRequests())
next_request = registration_data->GetPendingRequest();
std::move(callback).Run(std::move(next_request));
}
void BackgroundFetchDataManager::GetSettledFetchesForRegistration(
const BackgroundFetchRegistrationId& registration_id,
SettledFetchesCallback callback) {
auto iter = registrations_.find(registration_id);
DCHECK(iter != registrations_.end());
RegistrationData* registration_data = iter->second.get();
DCHECK(!registration_data->HasPendingRequests());
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<BlobHandle>> blob_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);
settled_fetch.response.url_list = request->GetURLChain();
settled_fetch.response.response_type =
blink::kWebServiceWorkerResponseTypeDefault;
// Include the status code, status text and the response's body as a blob
// when this is allowed by the CORS protocol.
if (filter.CanPopulateBody()) {
settled_fetch.response.status_code = request->GetResponseCode();
settled_fetch.response.status_text = request->GetResponseText();
settled_fetch.response.headers.insert(
request->GetResponseHeaders().begin(),
request->GetResponseHeaders().end());
if (request->GetFileSize() > 0) {
DCHECK(!request->GetFilePath().empty());
std::unique_ptr<BlobHandle> blob_handle =
blob_storage_context_->CreateFileBackedBlob(
request->GetFilePath(), 0 /* offset */, request->GetFileSize(),
base::Time() /* expected_modification_time */);
// TODO(peter): Appropriately handle !blob_handle
if (blob_handle) {
settled_fetch.response.blob_uuid = blob_handle->GetUUID();
settled_fetch.response.blob_size = request->GetFileSize();
blob_handles.push_back(std::move(blob_handle));
}
}
} else {
// TODO(crbug.com/711354): Consider Background Fetches as failed when the
// response cannot be relayed to the developer.
background_fetch_succeeded = false;
}
// TODO: settled_fetch.response.error
settled_fetch.response.response_time = request->GetResponseTime();
// TODO: settled_fetch.response.cors_exposed_header_names
background_fetch_succeeded = background_fetch_succeeded && IsOK(*request);
settled_fetches.push_back(settled_fetch);
}
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE,
background_fetch_succeeded,
std::move(settled_fetches), std::move(blob_handles));
}
void BackgroundFetchDataManager::DeleteRegistration(
const BackgroundFetchRegistrationId& registration_id,
DeleteRegistrationCallback callback) {
auto iter = registrations_.find(registration_id);
if (iter == registrations_.end()) {
std::move(callback).Run(blink::mojom::BackgroundFetchError::INVALID_TAG);
return;
}
registrations_.erase(iter);
std::move(callback).Run(blink::mojom::BackgroundFetchError::NONE);
}
} // namespace content