| // 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_registration_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_registration_task.h" |
| #include "content/browser/background_fetch/storage/mark_registration_for_deletion_task.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.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/WebKit/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; |
| } |
| |
| } // 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) |
| : registration_id_(registration_id), options_(options) { |
| 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_; } |
| |
| int GetTotalNumberOfRequests() const { |
| return pending_requests_.size() + active_requests_.size() + |
| completed_requests_.size(); |
| } |
| |
| private: |
| BackgroundFetchRegistrationId registration_id_; |
| BackgroundFetchOptions options_; |
| // 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) |
| : service_worker_context_(std::move(service_worker_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)); |
| } |
| } |
| |
| BackgroundFetchDataManager::~BackgroundFetchDataManager() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| } |
| |
| void BackgroundFetchDataManager::CreateRegistration( |
| const BackgroundFetchRegistrationId& registration_id, |
| const std::vector<ServiceWorkerFetchRequest>& requests, |
| const BackgroundFetchOptions& options, |
| GetRegistrationCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableBackgroundFetchPersistence)) { |
| AddDatabaseTask(std::make_unique<background_fetch::CreateRegistrationTask>( |
| this, registration_id, requests, options, std::move(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)); |
| |
| // 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::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)) { |
| AddDatabaseTask(std::make_unique<background_fetch::GetRegistrationTask>( |
| this, service_worker_registration_id, origin, developer_id, |
| std::move(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 std::string& unique_id, |
| const std::string& title, |
| blink::mojom::BackgroundFetchService::UpdateUICallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| auto registrations_iter = registrations_.find(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 (!IsActive(registration_id)) { |
| // Stop giving out requests as registration 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::MarkRequestAsComplete( |
| const BackgroundFetchRegistrationId& registration_id, |
| BackgroundFetchRequestInfo* request, |
| BackgroundFetchScheduler::MarkedCompleteCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| 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); |
| |
| auto iter = registrations_.find(registration_id.unique_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<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); |
| |
| settled_fetch.response.url_list = request->GetURLChain(); |
| settled_fetch.response.response_type = |
| network::mojom::FetchResponseType::kDefault; |
| |
| // 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()); |
| 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) { |
| settled_fetch.response.blob_uuid = blob_data_handle->uuid(); |
| settled_fetch.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)); |
| |
| settled_fetch.response.blob = |
| base::MakeRefCounted<storage::BlobHandle>(std::move(blob_ptr)); |
| blob_data_handles.push_back(std::move(blob_data_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(delphick): settled_fetch.response.error |
| settled_fetch.response.response_time = request->GetResponseTime(); |
| // TODO(delphick): 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_data_handles)); |
| } |
| |
| 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.unique_id(), 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); |
| } |
| |
| int BackgroundFetchDataManager::GetTotalNumberOfRequests( |
| const BackgroundFetchRegistrationId& registration_id) const { |
| return registrations_.find(registration_id.unique_id()) |
| ->second->GetTotalNumberOfRequests(); |
| } |
| |
| 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 |