| // Copyright 2018 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/storage/get_settled_fetches_task.h" |
| |
| #include "base/barrier_closure.h" |
| #include "content/browser/background_fetch/background_fetch_data_manager.h" |
| #include "content/browser/background_fetch/storage/database_helpers.h" |
| #include "content/browser/cache_storage/cache_storage_manager.h" |
| #include "content/browser/service_worker/service_worker_context_wrapper.h" |
| #include "content/common/service_worker/service_worker_utils.h" |
| #include "services/network/public/cpp/cors/cors.h" |
| |
| namespace content { |
| |
| namespace background_fetch { |
| |
| GetSettledFetchesTask::GetSettledFetchesTask( |
| DatabaseTaskHost* host, |
| BackgroundFetchRegistrationId registration_id, |
| std::unique_ptr<BackgroundFetchRequestMatchParams> match_params, |
| SettledFetchesCallback callback) |
| : DatabaseTask(host), |
| registration_id_(registration_id), |
| match_params_(std::move(match_params)), |
| settled_fetches_callback_(std::move(callback)), |
| weak_factory_(this) {} |
| |
| GetSettledFetchesTask::~GetSettledFetchesTask() = default; |
| |
| void GetSettledFetchesTask::Start() { |
| base::RepeatingClosure barrier_closure = base::BarrierClosure( |
| 2u, base::BindOnce(&GetSettledFetchesTask::GetResponses, |
| weak_factory_.GetWeakPtr())); |
| |
| cache_manager()->OpenCache( |
| registration_id_.origin(), CacheStorageOwner::kBackgroundFetch, |
| registration_id_.unique_id() /* cache_name */, |
| base::BindOnce(&GetSettledFetchesTask::DidOpenCache, |
| weak_factory_.GetWeakPtr(), barrier_closure)); |
| |
| service_worker_context()->GetRegistrationUserDataByKeyPrefix( |
| registration_id_.service_worker_registration_id(), |
| {CompletedRequestKeyPrefix(registration_id_.unique_id())}, |
| base::BindOnce(&GetSettledFetchesTask::DidGetCompletedRequests, |
| weak_factory_.GetWeakPtr(), barrier_closure)); |
| } |
| |
| void GetSettledFetchesTask::DidOpenCache( |
| base::OnceClosure done_closure, |
| CacheStorageCacheHandle handle, |
| blink::mojom::CacheStorageError error) { |
| if (error != blink::mojom::CacheStorageError::kSuccess) { |
| SetStorageError(BackgroundFetchStorageError::kCacheStorageError); |
| } else { |
| DCHECK(handle.value()); |
| handle_ = std::move(handle); |
| } |
| std::move(done_closure).Run(); |
| } |
| |
| void GetSettledFetchesTask::DidGetCompletedRequests( |
| base::OnceClosure done_closure, |
| const std::vector<std::string>& data, |
| blink::ServiceWorkerStatusCode status) { |
| switch (ToDatabaseStatus(status)) { |
| case DatabaseStatus::kOk: |
| break; |
| case DatabaseStatus::kFailed: |
| SetStorageError(BackgroundFetchStorageError::kServiceWorkerStorageError); |
| break; |
| case DatabaseStatus::kNotFound: |
| failure_reason_ = blink::mojom::BackgroundFetchFailureReason::FETCH_ERROR; |
| error_ = blink::mojom::BackgroundFetchError::INVALID_ID; |
| break; |
| } |
| |
| completed_requests_.reserve(data.size()); |
| for (const std::string& serialized_completed_request : data) { |
| completed_requests_.emplace_back(); |
| if (!completed_requests_.back().ParseFromString( |
| serialized_completed_request)) { |
| // Service worker database has been corrupted. Abandon fetches. |
| SetStorageError(BackgroundFetchStorageError::kServiceWorkerStorageError); |
| failure_reason_ = blink::mojom::BackgroundFetchFailureReason:: |
| SERVICE_WORKER_UNAVAILABLE; |
| AbandonFetches(registration_id_.service_worker_registration_id()); |
| break; |
| } |
| } |
| std::move(done_closure).Run(); |
| } |
| |
| void GetSettledFetchesTask::GetResponses() { |
| // Handle potential errors. |
| if (HasStorageError()) { |
| FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR); |
| return; |
| } |
| if (error_ != blink::mojom::BackgroundFetchError::NONE) { |
| FinishWithError(error_); |
| return; |
| } |
| if (completed_requests_.empty()) { |
| FinishWithError(blink::mojom::BackgroundFetchError::NONE); |
| return; |
| } |
| |
| if (!match_params_->FilterByRequest()) { |
| // No request to match against has been specified. Process all completed |
| // requests. |
| // TODO(crbug.com/863016): Process all requests here, not just the |
| // completed ones. |
| base::RepeatingClosure barrier_closure = base::BarrierClosure( |
| completed_requests_.size(), |
| base::BindOnce(&GetSettledFetchesTask::FinishWithError, |
| weak_factory_.GetWeakPtr(), |
| blink::mojom::BackgroundFetchError::NONE)); |
| settled_fetches_.reserve(completed_requests_.size()); |
| for (const auto& completed_request : completed_requests_) { |
| settled_fetches_.emplace_back(); |
| settled_fetches_.back().request = |
| ServiceWorkerUtils::DeserializeFetchRequestFromString( |
| completed_request.serialized_request()); |
| FillResponse(&settled_fetches_.back(), barrier_closure); |
| } |
| return; |
| } |
| |
| // Get response(s) only for the relevant fetch. |
| settled_fetches_.emplace_back(); |
| settled_fetches_.back().request = match_params_->request_to_match(); |
| |
| if (match_params_->match_all()) { |
| FillResponses(base::BindOnce(&GetSettledFetchesTask::FinishWithError, |
| weak_factory_.GetWeakPtr(), |
| blink::mojom::BackgroundFetchError::NONE)); |
| return; |
| } else { |
| FillResponse(&settled_fetches_.back(), |
| base::BindOnce(&GetSettledFetchesTask::FinishWithError, |
| weak_factory_.GetWeakPtr(), |
| blink::mojom::BackgroundFetchError::NONE)); |
| return; |
| } |
| } |
| |
| void GetSettledFetchesTask::FillResponse( |
| BackgroundFetchSettledFetch* settled_fetch, |
| base::OnceClosure callback) { |
| DCHECK(settled_fetch); |
| DCHECK(handle_.value()); |
| |
| auto request = |
| std::make_unique<ServiceWorkerFetchRequest>(settled_fetch->request); |
| handle_.value()->Match(std::move(request), |
| match_params_->cloned_cache_query_params(), |
| base::BindOnce(&GetSettledFetchesTask::DidMatchRequest, |
| weak_factory_.GetWeakPtr(), |
| settled_fetch, std::move(callback))); |
| } |
| |
| void GetSettledFetchesTask::FillResponses(base::OnceClosure callback) { |
| DCHECK(match_params_->match_all()); |
| DCHECK(match_params_->FilterByRequest()); |
| DCHECK(!settled_fetches_.empty()); |
| DCHECK(handle_.value()); |
| |
| // Make a copy. |
| auto request = std::make_unique<ServiceWorkerFetchRequest>( |
| match_params_->request_to_match()); |
| |
| handle_.value()->MatchAll( |
| std::move(request), match_params_->cloned_cache_query_params(), |
| base::BindOnce(&GetSettledFetchesTask::DidMatchAllResponsesForRequest, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void GetSettledFetchesTask::DidMatchRequest( |
| BackgroundFetchSettledFetch* settled_fetch, |
| base::OnceClosure callback, |
| blink::mojom::CacheStorageError error, |
| blink::mojom::FetchAPIResponsePtr cache_response) { |
| DCHECK(settled_fetch); |
| |
| // Handle error cases. |
| if (error == blink::mojom::CacheStorageError::kErrorNotFound) { |
| // This is currently being called once a fetch finishes, or when match() is |
| // called. |
| // In the first case, not finding a response is an error state. In the |
| // second case, it just means the developer passed a non-matching request. |
| // The if condition below picks the first one. |
| // TODO(crbug.com/863016): Once we stop sending settled_fetches with |
| // BackgroundFetch events, this won't be a storage error. |
| if (!match_params_->FilterByRequest()) |
| SetStorageError(BackgroundFetchStorageError::kCacheStorageError); |
| } else if (error != blink::mojom::CacheStorageError::kSuccess) { |
| SetStorageError(BackgroundFetchStorageError::kCacheStorageError); |
| } |
| |
| if (!cache_response) { |
| FillUncachedResponse(settled_fetch, std::move(callback)); |
| return; |
| } |
| settled_fetch->response = std::move(cache_response); |
| std::move(callback).Run(); |
| } |
| |
| void GetSettledFetchesTask::DidMatchAllResponsesForRequest( |
| base::OnceClosure callback, |
| blink::mojom::CacheStorageError error, |
| std::vector<blink::mojom::FetchAPIResponsePtr> cache_responses) { |
| if (error != blink::mojom::CacheStorageError::kSuccess && |
| error != blink::mojom::CacheStorageError::kErrorNotFound) { |
| SetStorageError(BackgroundFetchStorageError::kCacheStorageError); |
| } |
| |
| if (error != blink::mojom::CacheStorageError::kSuccess) { |
| DCHECK(!settled_fetches_.empty()); |
| FillUncachedResponse(&settled_fetches_.back(), std::move(callback)); |
| return; |
| } |
| |
| settled_fetches_.clear(); |
| settled_fetches_.reserve(cache_responses.size()); |
| for (size_t i = 0; i < cache_responses.size(); ++i) { |
| settled_fetches_.emplace_back(); |
| settled_fetches_.back().request = match_params_->request_to_match(); |
| settled_fetches_.back().response = std::move(cache_responses[i]); |
| } |
| std::move(callback).Run(); |
| } |
| |
| // TODO(crbug.com/863016): Get rid of this method. |
| void GetSettledFetchesTask::FillUncachedResponse( |
| BackgroundFetchSettledFetch* settled_fetch, |
| base::OnceClosure callback) { |
| failure_reason_ = blink::mojom::BackgroundFetchFailureReason::FETCH_ERROR; |
| |
| // TODO(rayankans): Fill unmatched response with error reports. |
| DCHECK(!settled_fetch->response); |
| settled_fetch->response = blink::mojom::FetchAPIResponse::New(); |
| settled_fetch->response->response_type = |
| network::mojom::FetchResponseType::kError; |
| settled_fetch->response->url_list.push_back(settled_fetch->request.url); |
| |
| std::move(callback).Run(); |
| } |
| |
| void GetSettledFetchesTask::FinishWithError( |
| blink::mojom::BackgroundFetchError error) { |
| if (HasStorageError()) |
| error = blink::mojom::BackgroundFetchError::STORAGE_ERROR; |
| ReportStorageError(); |
| |
| if (error == blink::mojom::BackgroundFetchError::NONE) { |
| for (const auto& settled_fetch : settled_fetches_) { |
| if (!settled_fetch.response->status_code) { |
| // A status_code of 0 means no headers were returned. |
| failure_reason_ = |
| blink::mojom::BackgroundFetchFailureReason::FETCH_ERROR; |
| break; |
| } |
| if (!network::cors::IsOkStatus(settled_fetch.response->status_code)) { |
| failure_reason_ = |
| blink::mojom::BackgroundFetchFailureReason::BAD_STATUS; |
| break; |
| } |
| } |
| } |
| std::move(settled_fetches_callback_) |
| .Run(error, failure_reason_, std::move(settled_fetches_), |
| {} /* blob_data_handles */); |
| Finished(); // Destroys |this|. |
| } |
| |
| std::string GetSettledFetchesTask::HistogramName() const { |
| return "GetSettledFetchesTask"; |
| }; |
| |
| } // namespace background_fetch |
| |
| } // namespace content |