blob: 9ba3d405cb0ef12b3cdf6d0333194a51bb7cc777 [file] [log] [blame]
// 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_initialization_data_task.h"
#include "base/barrier_closure.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "content/browser/background_fetch/background_fetch.pb.h"
#include "content/browser/background_fetch/background_fetch_data_manager.h"
#include "content/browser/background_fetch/background_fetch_request_info.h"
#include "content/browser/background_fetch/storage/database_helpers.h"
#include "content/browser/background_fetch/storage/image_helpers.h"
#include "content/browser/background_fetch/storage/mark_registration_for_deletion_task.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "ui/gfx/image/image.h"
#include "url/origin.h"
namespace content {
namespace background_fetch {
namespace {
// Base class with all the common implementation for the SubTasks
// needed in this file.
class InitializationSubTask : public DatabaseTask {
public:
// Holds data used by all SubTasks.
struct SubTaskInit {
SubTaskInit() = delete;
~SubTaskInit() = default;
// Service Worker Database metadata.
int64_t service_worker_registration_id;
std::string unique_id;
// The results to report.
BackgroundFetchInitializationData* initialization_data;
};
InitializationSubTask(DatabaseTaskHost* host,
const SubTaskInit& sub_task_init,
base::OnceClosure done_closure)
: DatabaseTask(host),
sub_task_init_(sub_task_init),
done_closure_(std::move(done_closure)) {
DCHECK(sub_task_init_.initialization_data);
}
~InitializationSubTask() override = default;
protected:
void FinishWithError(blink::mojom::BackgroundFetchError error) override {
if (error != blink::mojom::BackgroundFetchError::NONE)
sub_task_init_.initialization_data->error = error;
std::move(done_closure_).Run();
Finished(); // Destroys |this|.
}
SubTaskInit& sub_task_init() { return sub_task_init_; }
private:
SubTaskInit sub_task_init_;
base::OnceClosure done_closure_;
DISALLOW_COPY_AND_ASSIGN(InitializationSubTask);
};
// Fills the BackgroundFetchInitializationData with the most recent UI title.
class GetUIOptionsTask : public InitializationSubTask {
public:
GetUIOptionsTask(DatabaseTaskHost* host,
const SubTaskInit& sub_task_init,
base::OnceClosure done_closure)
: InitializationSubTask(host, sub_task_init, std::move(done_closure)),
weak_factory_(this) {}
~GetUIOptionsTask() override = default;
void Start() override {
service_worker_context()->GetRegistrationUserData(
sub_task_init().service_worker_registration_id,
{UIOptionsKey(sub_task_init().unique_id)},
base::BindOnce(&GetUIOptionsTask::DidGetUIOptions,
weak_factory_.GetWeakPtr()));
}
private:
void DidGetUIOptions(const std::vector<std::string>& data,
blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
if (data.size() != 1u) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
proto::BackgroundFetchUIOptions ui_options;
if (!ui_options.ParseFromString(data[0])) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
if (!ui_options.title().empty())
sub_task_init().initialization_data->ui_title = ui_options.title();
if (!ui_options.icon().empty()) {
// Start an icon deserialization SubTask on another thread, then finish.
DeserializeIcon(std::unique_ptr<std::string>(ui_options.release_icon()),
base::BindOnce(&GetUIOptionsTask::DidDeserializeIcon,
weak_factory_.GetWeakPtr()));
} else {
FinishWithError(blink::mojom::BackgroundFetchError::NONE);
}
}
void DidDeserializeIcon(SkBitmap icon) {
sub_task_init().initialization_data->icon = std::move(icon);
FinishWithError(blink::mojom::BackgroundFetchError::NONE);
}
base::WeakPtrFactory<GetUIOptionsTask> weak_factory_; // Keep as last.
};
// Gets the number of completed fetches, the number of active fetches,
// and deletes inconsistencies in state transitions.
// 1. Get all completed requests.
// 2. Delete matching active requests.
// 3. Get active requests.
// 4. Delete matching pending requests.
class GetRequestsTask : public InitializationSubTask {
public:
GetRequestsTask(DatabaseTaskHost* host,
const SubTaskInit& sub_task_init,
base::OnceClosure done_closure)
: InitializationSubTask(host, sub_task_init, std::move(done_closure)),
weak_factory_(this) {}
~GetRequestsTask() override = default;
void Start() override {
service_worker_context()->GetRegistrationUserDataByKeyPrefix(
sub_task_init().service_worker_registration_id,
CompletedRequestKeyPrefix(sub_task_init().unique_id),
base::BindOnce(&GetRequestsTask::DidGetCompletedRequests,
weak_factory_.GetWeakPtr()));
}
private:
void DidGetCompletedRequests(const std::vector<std::string>& data,
blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
sub_task_init().initialization_data->num_completed_requests = data.size();
std::vector<std::string> active_requests_to_delete;
active_requests_to_delete.reserve(data.size());
for (const std::string& serialized_completed_request : data) {
proto::BackgroundFetchCompletedRequest completed_request;
if (!completed_request.ParseFromString(serialized_completed_request) ||
sub_task_init().unique_id != completed_request.unique_id()) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
active_requests_to_delete.push_back(ActiveRequestKey(
completed_request.unique_id(), completed_request.request_index()));
}
if (active_requests_to_delete.empty()) {
DidClearActiveRequests(blink::ServiceWorkerStatusCode::kOk);
return;
}
service_worker_context()->ClearRegistrationUserData(
sub_task_init().service_worker_registration_id,
std::move(active_requests_to_delete),
base::BindOnce(&GetRequestsTask::DidClearActiveRequests,
weak_factory_.GetWeakPtr()));
}
void DidClearActiveRequests(blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
service_worker_context()->GetRegistrationUserDataByKeyPrefix(
sub_task_init().service_worker_registration_id,
ActiveRequestKeyPrefix(sub_task_init().unique_id),
base::BindOnce(&GetRequestsTask::DidGetRemainingActiveRequests,
weak_factory_.GetWeakPtr()));
}
void DidGetRemainingActiveRequests(const std::vector<std::string>& data,
blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
std::vector<std::string> pending_requests_to_delete;
pending_requests_to_delete.reserve(data.size());
for (const std::string& serialized_active_request : data) {
proto::BackgroundFetchActiveRequest active_request;
if (!active_request.ParseFromString(serialized_active_request)) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
DCHECK_EQ(sub_task_init().unique_id, active_request.unique_id());
auto request_info = base::MakeRefCounted<BackgroundFetchRequestInfo>(
active_request.request_index(),
ServiceWorkerUtils::DeserializeFetchRequestFromString(
active_request.serialized_request()),
active_request.request_body_size());
request_info->SetDownloadGuid(active_request.download_guid());
sub_task_init().initialization_data->active_fetch_requests.push_back(
std::move(request_info));
pending_requests_to_delete.push_back(PendingRequestKey(
active_request.unique_id(), active_request.request_index()));
}
if (pending_requests_to_delete.empty()) {
DidClearPendingRequests(blink::ServiceWorkerStatusCode::kOk);
return;
}
service_worker_context()->ClearRegistrationUserData(
sub_task_init().service_worker_registration_id,
std::move(pending_requests_to_delete),
base::BindOnce(&GetRequestsTask::DidClearPendingRequests,
weak_factory_.GetWeakPtr()));
}
void DidClearPendingRequests(blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
FinishWithError(blink::mojom::BackgroundFetchError::NONE);
}
base::WeakPtrFactory<GetRequestsTask> weak_factory_; // Keep as last.
DISALLOW_COPY_AND_ASSIGN(GetRequestsTask);
};
// Fills the BackgroundFetchInitializationData with all the relevant information
// stored in the BackgroundFetchMetadata proto.
class FillFromMetadataTask : public InitializationSubTask {
public:
FillFromMetadataTask(DatabaseTaskHost* host,
const SubTaskInit& sub_task_init,
base::OnceClosure done_closure)
: InitializationSubTask(host, sub_task_init, std::move(done_closure)),
weak_factory_(this) {}
~FillFromMetadataTask() override = default;
void Start() override {
service_worker_context()->GetRegistrationUserDataByKeyPrefix(
sub_task_init().service_worker_registration_id,
{RegistrationKey(sub_task_init().unique_id)},
base::BindOnce(&FillFromMetadataTask::DidGetMetadata,
weak_factory_.GetWeakPtr()));
}
private:
void DidGetMetadata(const std::vector<std::string>& data,
blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
case DatabaseStatus::kNotFound:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kOk:
break;
}
if (data.size() != 1u) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
proto::BackgroundFetchMetadata metadata;
if (!metadata.ParseFromString(data[0])) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
if (sub_task_init().unique_id != metadata.registration().unique_id()) {
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
}
// Fill BackgroundFetchRegistrationId.
sub_task_init().initialization_data->registration_id =
BackgroundFetchRegistrationId(
sub_task_init().service_worker_registration_id,
url::Origin::Create(GURL(metadata.origin())),
metadata.registration().developer_id(),
metadata.registration().unique_id());
// Fill BackgroundFetchRegistration.
auto& registration = sub_task_init().initialization_data->registration;
ToBackgroundFetchRegistration(metadata, registration.get());
// Total number of requests.
sub_task_init().initialization_data->num_requests = metadata.num_fetches();
// Fill BackgroundFetchOptions.
auto& options = sub_task_init().initialization_data->options;
options->title = metadata.options().title();
options->download_total = metadata.options().download_total();
options->icons.reserve(metadata.options().icons_size());
for (const auto& icon : metadata.options().icons()) {
blink::Manifest::ImageResource ir;
ir.src = GURL(icon.src());
ir.type = base::ASCIIToUTF16(icon.type());
ir.sizes.reserve(icon.sizes_size());
for (const auto& size : icon.sizes())
ir.sizes.emplace_back(size.width(), size.height());
ir.purpose.reserve(icon.purpose_size());
for (auto purpose : icon.purpose()) {
switch (purpose) {
case proto::BackgroundFetchOptions_ImageResource_Purpose_ANY:
ir.purpose.push_back(blink::Manifest::ImageResource::Purpose::ANY);
break;
case proto::BackgroundFetchOptions_ImageResource_Purpose_BADGE:
ir.purpose.push_back(
blink::Manifest::ImageResource::Purpose::BADGE);
break;
}
}
}
FinishWithError(blink::mojom::BackgroundFetchError::NONE);
}
base::WeakPtrFactory<FillFromMetadataTask> weak_factory_; // Keep as last.
DISALLOW_COPY_AND_ASSIGN(FillFromMetadataTask);
};
// Asynchronously calls the SubTasks required to collect all the information for
// the BackgroundFetchInitializationData.
class FillBackgroundFetchInitializationDataTask : public InitializationSubTask {
public:
FillBackgroundFetchInitializationDataTask(DatabaseTaskHost* host,
const SubTaskInit& sub_task_init,
base::OnceClosure done_closure)
: InitializationSubTask(host, sub_task_init, std::move(done_closure)),
weak_factory_(this) {}
~FillBackgroundFetchInitializationDataTask() override = default;
void Start() override {
// We need 3 queries to get the initialization data. These are wrapped
// in a BarrierClosure to avoid querying them serially.
// 1. Metadata
// 2. Request statuses and state sanitization
// 3. UI Options (+ icon deserialization)
base::RepeatingClosure barrier_closure = base::BarrierClosure(
3u,
base::BindOnce(
[](base::WeakPtr<FillBackgroundFetchInitializationDataTask> task) {
if (task)
task->FinishWithError(
task->sub_task_init().initialization_data->error);
},
weak_factory_.GetWeakPtr()));
AddSubTask(std::make_unique<FillFromMetadataTask>(this, sub_task_init(),
barrier_closure));
AddSubTask(std::make_unique<GetRequestsTask>(this, sub_task_init(),
barrier_closure));
AddSubTask(std::make_unique<GetUIOptionsTask>(this, sub_task_init(),
barrier_closure));
}
private:
base::WeakPtrFactory<FillBackgroundFetchInitializationDataTask>
weak_factory_; // Keep as last.
DISALLOW_COPY_AND_ASSIGN(FillBackgroundFetchInitializationDataTask);
};
} // namespace
BackgroundFetchInitializationData::BackgroundFetchInitializationData() =
default;
BackgroundFetchInitializationData::BackgroundFetchInitializationData(
BackgroundFetchInitializationData&&) = default;
BackgroundFetchInitializationData::~BackgroundFetchInitializationData() =
default;
GetInitializationDataTask::GetInitializationDataTask(
DatabaseTaskHost* host,
GetInitializationDataCallback callback)
: DatabaseTask(host), callback_(std::move(callback)), weak_factory_(this) {}
GetInitializationDataTask::~GetInitializationDataTask() = default;
void GetInitializationDataTask::Start() {
service_worker_context()->GetUserDataForAllRegistrationsByKeyPrefix(
kActiveRegistrationUniqueIdKeyPrefix,
base::BindOnce(&GetInitializationDataTask::DidGetRegistrations,
weak_factory_.GetWeakPtr()));
}
void GetInitializationDataTask::DidGetRegistrations(
const std::vector<std::pair<int64_t, std::string>>& user_data,
blink::ServiceWorkerStatusCode status) {
switch (ToDatabaseStatus(status)) {
case DatabaseStatus::kFailed:
FinishWithError(blink::mojom::BackgroundFetchError::STORAGE_ERROR);
return;
case DatabaseStatus::kNotFound:
case DatabaseStatus::kOk:
break;
}
if (user_data.empty()) {
FinishWithError(blink::mojom::BackgroundFetchError::NONE);
return;
}
base::RepeatingClosure barrier_closure = base::BarrierClosure(
user_data.size(),
base::BindOnce(&GetInitializationDataTask::FinishWithError,
weak_factory_.GetWeakPtr(),
blink::mojom::BackgroundFetchError::NONE));
for (const auto& ud : user_data) {
auto insertion_result = initialization_data_map_.emplace(
ud.second, BackgroundFetchInitializationData());
DCHECK(insertion_result.second); // Check unique_id is in fact unique.
AddSubTask(std::make_unique<FillBackgroundFetchInitializationDataTask>(
this,
InitializationSubTask::SubTaskInit{
ud.first, ud.second,
&insertion_result.first->second /* initialization_data */},
barrier_closure));
}
}
void GetInitializationDataTask::FinishWithError(
blink::mojom::BackgroundFetchError error) {
std::vector<BackgroundFetchInitializationData> results;
results.reserve(initialization_data_map_.size());
for (auto& data : initialization_data_map_) {
if (data.second.error == blink::mojom::BackgroundFetchError::NONE) {
// If we successfully extracted all the data, move it to the
// initialization vector to be handed over to create a controller.
results.emplace_back(std::move(data.second));
} else if (!data.second.registration_id.developer_id().empty()) {
// There was an error in getting the initialization data
// (e.g. corrupt data, SWDB error). If the Developer ID of the fetch
// is available, mark the registration for deletion.
// Note that the Developer ID isn't available if the metadata extraction
// failed.
// TODO(crbug.com/865388): Getting the Developer ID should be possible
// since it is part of the key for when we got the Unique ID.
AddDatabaseTask(std::make_unique<MarkRegistrationForDeletionTask>(
data_manager(), data.second.registration_id,
/* check_for_failure= */ false, base::DoNothing()));
}
if (data.second.error ==
blink::mojom::BackgroundFetchError::STORAGE_ERROR) {
// The subtasks only access the Service Worker storage, so if there is
// a storage error, that would be the cause.
SetStorageError(BackgroundFetchStorageError::kServiceWorkerStorageError);
}
}
ReportStorageError();
std::move(callback_).Run(error, std::move(results));
Finished(); // Destroys |this|.
}
std::string GetInitializationDataTask::HistogramName() const {
return "GetInitializationDataTask";
}
} // namespace background_fetch
} // namespace content