blob: 179c9f51f57a961acf4949decc3d9969c216ed75 [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_scheduler.h"
#include "base/guid.h"
#include "base/stl_util.h"
#include "content/browser/background_fetch/background_fetch_data_manager.h"
#include "content/browser/background_fetch/background_fetch_delegate_proxy.h"
#include "content/browser/background_fetch/background_fetch_job_controller.h"
#include "content/browser/background_fetch/background_fetch_metrics.h"
#include "content/browser/background_fetch/background_fetch_registration_notifier.h"
#include "content/browser/service_worker/service_worker_context_core_observer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
namespace content {
using blink::mojom::BackgroundFetchError;
using blink::mojom::BackgroundFetchFailureReason;
BackgroundFetchScheduler::BackgroundFetchScheduler(
BackgroundFetchDataManager* data_manager,
BackgroundFetchRegistrationNotifier* registration_notifier,
BackgroundFetchDelegateProxy* delegate_proxy,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
: data_manager_(data_manager),
registration_notifier_(registration_notifier),
delegate_proxy_(delegate_proxy),
event_dispatcher_(std::move(service_worker_context)),
weak_ptr_factory_(this) {
DCHECK(delegate_proxy_);
delegate_proxy_->SetClickEventDispatcher(
base::BindRepeating(&BackgroundFetchScheduler::DispatchClickEvent,
weak_ptr_factory_.GetWeakPtr()));
}
BackgroundFetchScheduler::~BackgroundFetchScheduler() = default;
void BackgroundFetchScheduler::ScheduleDownload() {
DCHECK(!active_controller_);
if (controller_ids_.empty())
return;
DCHECK(!job_controllers_.empty());
std::string controller_id = controller_ids_.front();
controller_ids_.pop_front();
active_controller_ = job_controllers_[controller_id].get();
DCHECK(active_controller_);
data_manager_->PopNextRequest(
active_controller_->registration_id(),
base::BindOnce(&BackgroundFetchJobController::DidPopNextRequest,
active_controller_->GetWeakPtr()));
}
void BackgroundFetchScheduler::Abort(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchFailureReason failure_reason,
blink::mojom::BackgroundFetchService::AbortCallback callback) {
DCHECK_EQ(failure_reason,
BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER);
base::Erase(controller_ids_, registration_id.unique_id());
auto it = job_controllers_.find(registration_id.unique_id());
if (it == job_controllers_.end()) {
std::move(callback).Run(BackgroundFetchError::INVALID_ID);
return;
}
it->second->Abort(failure_reason, std::move(callback));
}
void BackgroundFetchScheduler::FinishJob(
const BackgroundFetchRegistrationId& registration_id,
BackgroundFetchFailureReason failure_reason,
base::OnceCallback<void(BackgroundFetchError)> callback) {
bool job_started = false;
if (active_controller_ &&
active_controller_->registration_id() == registration_id) {
active_controller_ = nullptr;
job_started = true;
}
data_manager_->MarkRegistrationForDeletion(
registration_id,
/* check_for_failure= */ failure_reason ==
BackgroundFetchFailureReason::NONE,
base::BindOnce(&BackgroundFetchScheduler::DidMarkForDeletion,
weak_ptr_factory_.GetWeakPtr(), registration_id,
job_started, std::move(callback)));
auto it = job_controllers_.find(registration_id.unique_id());
if (it != job_controllers_.end()) {
completed_fetches_[it->first] = {registration_id,
it->second->NewRegistration()};
// Destroying the controller will stop all in progress tasks.
job_controllers_.erase(it);
}
if (!active_controller_)
ScheduleDownload();
}
void BackgroundFetchScheduler::DidMarkForDeletion(
const BackgroundFetchRegistrationId& registration_id,
bool job_started,
base::OnceCallback<void(BackgroundFetchError)> callback,
BackgroundFetchError error,
BackgroundFetchFailureReason failure_reason) {
DCHECK(callback);
std::move(callback).Run(error);
// It's normal to get INVALID_ID errors here - it means the registration was
// already inactive (marked for deletion). This happens when an abort (from
// developer or from user) races with the download completing/failing, or even
// when two aborts race.
if (error != BackgroundFetchError::NONE)
return;
auto it = completed_fetches_.find(registration_id.unique_id());
DCHECK(it != completed_fetches_.end());
BackgroundFetchRegistration* registration = it->second.second.get();
// Include any other failure reasons the marking for deletion may have found.
if (registration->failure_reason == BackgroundFetchFailureReason::NONE)
registration->failure_reason = failure_reason;
registration->result =
registration->failure_reason == BackgroundFetchFailureReason::NONE
? blink::mojom::BackgroundFetchResult::SUCCESS
: blink::mojom::BackgroundFetchResult::FAILURE;
registration_notifier_->Notify(*registration);
event_dispatcher_.DispatchBackgroundFetchCompletionEvent(
registration_id,
std::make_unique<BackgroundFetchRegistration>(*registration),
base::BindOnce(&BackgroundFetchScheduler::CleanupRegistration,
weak_ptr_factory_.GetWeakPtr(), registration_id));
if (!job_started ||
registration->failure_reason ==
BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
registration->failure_reason ==
BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER) {
// No need to keep the controller around since there won't be dispatch
// events.
completed_fetches_.erase(it);
}
}
void BackgroundFetchScheduler::CleanupRegistration(
const BackgroundFetchRegistrationId& registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Indicate to the renderer that the records for this fetch are no longer
// available.
registration_notifier_->NotifyRecordsUnavailable(registration_id.unique_id());
// Delete the data associated with this fetch. Cache storage will keep the
// downloaded data around so long as there are references to it, and delete
// it once there is none. We don't need to do that accounting.
data_manager_->DeleteRegistration(registration_id, base::DoNothing());
}
void BackgroundFetchScheduler::DispatchClickEvent(
const std::string& unique_id) {
// Case 1: The active fetch received a click event.
if (active_controller_ &&
active_controller_->registration_id().unique_id() == unique_id) {
event_dispatcher_.DispatchBackgroundFetchClickEvent(
active_controller_->registration_id(),
active_controller_->NewRegistration(), base::DoNothing());
return;
}
// Case 2: A completed fetch received a click event.
auto it = completed_fetches_.find(unique_id);
if (it == completed_fetches_.end())
return;
event_dispatcher_.DispatchBackgroundFetchClickEvent(
it->second.first, std::move(it->second.second), base::DoNothing());
completed_fetches_.erase(unique_id);
}
std::unique_ptr<BackgroundFetchJobController>
BackgroundFetchScheduler::CreateInitializedController(
const BackgroundFetchRegistrationId& registration_id,
const BackgroundFetchRegistration& registration,
const BackgroundFetchOptions& options,
const SkBitmap& icon,
int num_completed_requests,
int num_requests,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests,
bool start_paused) {
// TODO(rayankans): Only create a controller when the fetch starts.
auto controller = std::make_unique<BackgroundFetchJobController>(
data_manager_, delegate_proxy_, registration_id, options, icon,
registration.downloaded,
// Safe because JobControllers are destroyed before RegistrationNotifier.
base::BindRepeating(&BackgroundFetchRegistrationNotifier::Notify,
base::Unretained(registration_notifier_)),
base::BindOnce(&BackgroundFetchScheduler::FinishJob,
weak_ptr_factory_.GetWeakPtr()));
controller->InitializeRequestStatus(num_completed_requests, num_requests,
std::move(active_fetch_requests),
start_paused);
return controller;
}
void BackgroundFetchScheduler::OnRegistrationCreated(
const BackgroundFetchRegistrationId& registration_id,
const BackgroundFetchRegistration& registration,
const BackgroundFetchOptions& options,
const SkBitmap& icon,
int num_requests,
bool start_paused) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto controller = CreateInitializedController(
registration_id, registration, options, icon, 0 /* completed_requests */,
num_requests, {} /* active_fetch_requests */, start_paused);
DCHECK_EQ(job_controllers_.count(registration_id.unique_id()), 0u);
job_controllers_[registration_id.unique_id()] = std::move(controller);
controller_ids_.push_back(registration_id.unique_id());
if (!active_controller_)
ScheduleDownload();
}
void BackgroundFetchScheduler::OnRegistrationLoadedAtStartup(
const BackgroundFetchRegistrationId& registration_id,
const BackgroundFetchRegistration& registration,
const BackgroundFetchOptions& options,
const SkBitmap& icon,
int num_completed_requests,
int num_requests,
std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
active_fetch_requests) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto controller = CreateInitializedController(
registration_id, registration, options, icon, num_completed_requests,
num_requests, active_fetch_requests, /* start_paused= */ false);
// The current assumption is that there can be only one active job with one
// active fetch.
DCHECK(!active_controller_);
active_controller_ = controller.get();
job_controllers_[registration_id.unique_id()] = std::move(controller);
DCHECK_LE(active_fetch_requests.size(), 1u);
if (active_fetch_requests.empty()) {
DCHECK_LT(num_completed_requests, num_requests);
// Start processing the next request.
data_manager_->PopNextRequest(
active_controller_->registration_id(),
base::BindOnce(&BackgroundFetchJobController::DidPopNextRequest,
active_controller_->GetWeakPtr()));
return;
}
for (auto& request_info : active_fetch_requests) {
active_controller_->StartRequest(
std::move(request_info),
base::BindOnce(&BackgroundFetchJobController::MarkRequestAsComplete,
active_controller_->GetWeakPtr()));
}
}
void BackgroundFetchScheduler::AbortFetches(
int64_t service_worker_registration_id) {
// Abandon all active associated with this service worker.
// BackgroundFetchJobController::Abort() will eventually lead to deletion of
// the controller from job_controllers, so the IDs need to be copied over.
std::vector<BackgroundFetchJobController*> to_abort;
for (const auto& controller : job_controllers_) {
if (service_worker_registration_id !=
blink::mojom::kInvalidServiceWorkerRegistrationId &&
service_worker_registration_id !=
controller.second->registration_id()
.service_worker_registration_id()) {
continue;
}
to_abort.push_back(controller.second.get());
}
for (auto* controller : to_abort) {
// Erase it from |controller_ids_| first to avoid rescheduling.
base::Erase(controller_ids_, controller->registration_id().unique_id());
controller->Abort(BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE,
base::DoNothing());
}
}
void BackgroundFetchScheduler::OnRegistrationQueried(
BackgroundFetchRegistration* registration) {
DCHECK(registration);
if (!active_controller_)
return;
// The data manager only has the number of bytes from completed downloads, so
// augment this with the number of downloaded bytes from in-progress jobs.
if (active_controller_->registration_id().unique_id() ==
registration->unique_id) {
registration->downloaded +=
active_controller_->GetInProgressDownloadedBytes();
}
}
void BackgroundFetchScheduler::OnServiceWorkerDatabaseCorrupted(
int64_t service_worker_registration_id) {
AbortFetches(service_worker_registration_id);
}
void BackgroundFetchScheduler::OnRegistrationDeleted(int64_t registration_id,
const GURL& pattern) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
AbortFetches(registration_id);
}
void BackgroundFetchScheduler::OnStorageWiped() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
AbortFetches(blink::mojom::kInvalidServiceWorkerRegistrationId);
}
} // namespace content