// Copyright 2015 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_sync/background_sync_manager.h"

#include <algorithm>
#include <utility>

#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/browser/background_sync/background_sync_metrics.h"
#include "content/browser/background_sync/background_sync_network_observer.h"
#include "content/browser/service_worker/embedded_worker_status.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_storage.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/background_sync_controller.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_type.h"
#include "third_party/blink/public/common/service_worker/service_worker_type_converters.h"
#include "third_party/blink/public/mojom/service_worker/service_worker.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_event_status.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"

#if defined(OS_ANDROID)
#include "content/browser/android/background_sync_network_observer_android.h"
#include "content/browser/background_sync/background_sync_launcher.h"
#endif

using blink::mojom::BackgroundSyncType;
using blink::mojom::PermissionStatus;
using SyncAndNotificationPermissions =
    std::pair<PermissionStatus, PermissionStatus>;

namespace content {

// TODO(crbug.com/932591): Use blink::mojom::BackgroundSyncError
// directly and eliminate these checks.
#define COMPILE_ASSERT_MATCHING_ENUM(mojo_name, manager_name) \
  static_assert(static_cast<int>(blink::mojo_name) ==         \
                    static_cast<int>(content::manager_name),  \
                "mojo and manager enums must match")

COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::NONE,
                             BACKGROUND_SYNC_STATUS_OK);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::STORAGE,
                             BACKGROUND_SYNC_STATUS_STORAGE_ERROR);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::NOT_FOUND,
                             BACKGROUND_SYNC_STATUS_NOT_FOUND);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::NO_SERVICE_WORKER,
                             BACKGROUND_SYNC_STATUS_NO_SERVICE_WORKER);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::NOT_ALLOWED,
                             BACKGROUND_SYNC_STATUS_NOT_ALLOWED);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::PERMISSION_DENIED,
                             BACKGROUND_SYNC_STATUS_PERMISSION_DENIED);
COMPILE_ASSERT_MATCHING_ENUM(mojom::BackgroundSyncError::MAX,
                             BACKGROUND_SYNC_STATUS_PERMISSION_DENIED);

namespace {

// The only allowed value of min_interval for one shot Background Sync
// registrations.
constexpr int kMinIntervalForOneShotSync = -1;

// The key used to index the background sync data in ServiceWorkerStorage.
const char kBackgroundSyncUserDataKey[] = "BackgroundSyncUserData";

void RecordFailureAndPostError(
    BackgroundSyncType sync_type,
    BackgroundSyncStatus status,
    BackgroundSyncManager::StatusAndRegistrationCallback callback) {
  BackgroundSyncMetrics::CountRegisterFailure(sync_type, status);

  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), status, nullptr));
}

// Returns nullptr if the browser context cannot be accessed for any reason.
BrowserContext* GetBrowserContextOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (!service_worker_context)
    return nullptr;
  StoragePartitionImpl* storage_partition_impl =
      service_worker_context->storage_partition();
  if (!storage_partition_impl)  // may be null in tests
    return nullptr;

  return storage_partition_impl->browser_context();
}

// Returns nullptr if the controller cannot be accessed for any reason.
BackgroundSyncController* GetBackgroundSyncControllerOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BrowserContext* browser_context =
      GetBrowserContextOnUIThread(std::move(service_worker_context));
  if (!browser_context)
    return nullptr;

  return browser_context->GetBackgroundSyncController();
}

SyncAndNotificationPermissions GetBackgroundSyncPermissionOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
    const url::Origin& origin,
    BackgroundSyncType sync_type) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BrowserContext* browser_context =
      GetBrowserContextOnUIThread(std::move(service_worker_context));
  if (!browser_context)
    return {PermissionStatus::DENIED, PermissionStatus::DENIED};

  PermissionController* permission_controller =
      BrowserContext::GetPermissionController(browser_context);
  DCHECK(permission_controller);

  // The requesting origin always matches the embedding origin.
  GURL origin_url = origin.GetURL();
  auto sync_permission = permission_controller->GetPermissionStatus(
      sync_type == BackgroundSyncType::ONE_SHOT
          ? PermissionType::BACKGROUND_SYNC
          : PermissionType::PERIODIC_BACKGROUND_SYNC,
      origin_url, origin_url);
  auto notification_permission = permission_controller->GetPermissionStatus(
      PermissionType::NOTIFICATIONS, origin_url, origin_url);
  return {sync_permission, notification_permission};
}

void NotifyOneShotBackgroundSyncRegisteredOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const url::Origin& origin,
    bool can_fire,
    bool is_reregistered) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(std::move(sw_context_wrapper));

  if (!background_sync_controller)
    return;

  background_sync_controller->NotifyOneShotBackgroundSyncRegistered(
      origin, can_fire, is_reregistered);
}

void NotifyPeriodicBackgroundSyncRegisteredOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const url::Origin& origin,
    int min_interval,
    bool is_reregistered) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(std::move(sw_context_wrapper));

  if (!background_sync_controller)
    return;

  background_sync_controller->NotifyPeriodicBackgroundSyncRegistered(
      origin, min_interval, is_reregistered);
}

void NotifyOneShotBackgroundSyncCompletedOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const url::Origin& origin,
    blink::ServiceWorkerStatusCode status_code,
    int num_attempts,
    int max_attempts) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(std::move(sw_context_wrapper));

  if (!background_sync_controller)
    return;

  background_sync_controller->NotifyOneShotBackgroundSyncCompleted(
      origin, status_code, num_attempts, max_attempts);
}

void NotifyPeriodicBackgroundSyncCompletedOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const url::Origin& origin,
    blink::ServiceWorkerStatusCode status_code,
    int num_attempts,
    int max_attempts) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(std::move(sw_context_wrapper));

  if (!background_sync_controller)
    return;

  background_sync_controller->NotifyPeriodicBackgroundSyncCompleted(
      origin, status_code, num_attempts, max_attempts);
}

std::unique_ptr<BackgroundSyncParameters> GetControllerParameters(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    std::unique_ptr<BackgroundSyncParameters> parameters) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(sw_context_wrapper);

  if (!background_sync_controller) {
    // If there is no controller then BackgroundSync can't run in the
    // background, disable it.
    parameters->disable = true;
    return parameters;
  }

  background_sync_controller->GetParameterOverrides(parameters.get());
  return parameters;
}

base::TimeDelta GetNextEventDelay(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const BackgroundSyncRegistration& registration,
    std::unique_ptr<BackgroundSyncParameters> parameters,
    base::TimeDelta time_till_soonest_scheduled_event_for_origin) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* background_sync_controller =
      GetBackgroundSyncControllerOnUIThread(sw_context_wrapper);

  if (!background_sync_controller)
    return base::TimeDelta::Max();

  return background_sync_controller->GetNextEventDelay(
      registration, parameters.get(),
      time_till_soonest_scheduled_event_for_origin);
}

void OnSyncEventFinished(scoped_refptr<ServiceWorkerVersion> active_version,
                         int request_id,
                         ServiceWorkerVersion::StatusCallback callback,
                         blink::mojom::ServiceWorkerEventStatus status) {
  if (!active_version->FinishRequest(
          request_id,
          status == blink::mojom::ServiceWorkerEventStatus::COMPLETED)) {
    return;
  }
  std::move(callback).Run(
      mojo::ConvertTo<blink::ServiceWorkerStatusCode>(status));
}

void DidStartWorkerForSyncEvent(
    base::OnceCallback<void(ServiceWorkerVersion::StatusCallback)> task,
    ServiceWorkerVersion::StatusCallback callback,
    blink::ServiceWorkerStatusCode start_worker_status) {
  if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) {
    std::move(callback).Run(start_worker_status);
    return;
  }
  std::move(task).Run(std::move(callback));
}

BackgroundSyncType GetBackgroundSyncType(
    const blink::mojom::SyncRegistrationOptions& options) {
  return options.min_interval == -1 ? BackgroundSyncType::ONE_SHOT
                                    : BackgroundSyncType::PERIODIC;
}

std::string GetSyncEventName(const BackgroundSyncType sync_type) {
  if (sync_type == BackgroundSyncType::ONE_SHOT)
    return "sync";
  else
    return "periodicsync";
}

DevToolsBackgroundService GetDevToolsBackgroundService(
    BackgroundSyncType sync_type) {
  if (sync_type == BackgroundSyncType::ONE_SHOT)
    return DevToolsBackgroundService::kBackgroundSync;
  else
    return DevToolsBackgroundService::kPeriodicBackgroundSync;
}

std::string GetDelayAsString(base::TimeDelta delay) {
  if (delay.is_max())
    return "infinite";
  return base::NumberToString(delay.InMilliseconds());
}

std::string GetEventStatusString(blink::ServiceWorkerStatusCode status_code) {
  // The |status_code| is derived from blink::mojom::ServiceWorkerEventStatus.
  switch (status_code) {
    case blink::ServiceWorkerStatusCode::kOk:
      return "succeeded";
    case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected:
      return "waitUntil rejected";
    case blink::ServiceWorkerStatusCode::kErrorFailed:
      return "failed";
    case blink::ServiceWorkerStatusCode::kErrorAbort:
      return "aborted";
    case blink::ServiceWorkerStatusCode::kErrorTimeout:
      return "timeout";
    default:
      NOTREACHED();
      return "unknown error";
  }
}

int GetNumAttemptsAfterEvent(BackgroundSyncType sync_type,
                             int current_num_attempts,
                             int max_attempts,
                             blink::mojom::BackgroundSyncState sync_state,
                             bool succeeded) {
  int num_attempts = ++current_num_attempts;

  if (sync_type == BackgroundSyncType::PERIODIC) {
    if (succeeded)
      return 0;
    if (num_attempts == max_attempts)
      return 0;
  }

  if (sync_state ==
      blink::mojom::BackgroundSyncState::REREGISTERED_WHILE_FIRING) {
    return 0;
  }

  return num_attempts;
}

// This prevents the browser process from shutting down when the last browser
// window is closed and there are one-shot Background Sync events ready to fire.
std::unique_ptr<BackgroundSyncController::BackgroundSyncEventKeepAlive>
CreateBackgroundSyncEventKeepAliveOnUIThread(
    scoped_refptr<ServiceWorkerContextWrapper> sw_context_wrapper,
    const blink::mojom::BackgroundSyncRegistrationInfo& registration_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  BackgroundSyncController* controller =
      GetBackgroundSyncControllerOnUIThread(sw_context_wrapper);
  if (!controller ||
      registration_info.sync_type != BackgroundSyncType::ONE_SHOT) {
    return nullptr;
  }

  return controller->CreateBackgroundSyncEventKeepAlive();
}

}  // namespace

BackgroundSyncManager::BackgroundSyncRegistrations::
    BackgroundSyncRegistrations() = default;

BackgroundSyncManager::BackgroundSyncRegistrations::BackgroundSyncRegistrations(
    const BackgroundSyncRegistrations& other) = default;

BackgroundSyncManager::BackgroundSyncRegistrations::
    ~BackgroundSyncRegistrations() = default;

// static
std::unique_ptr<BackgroundSyncManager> BackgroundSyncManager::Create(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
    scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  BackgroundSyncManager* sync_manager = new BackgroundSyncManager(
      std::move(service_worker_context), std::move(devtools_context));
  sync_manager->Init();
  return base::WrapUnique(sync_manager);
}

BackgroundSyncManager::~BackgroundSyncManager() {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  service_worker_context_->RemoveObserver(this);
}

void BackgroundSyncManager::Register(
    int64_t sw_registration_id,
    blink::mojom::SyncRegistrationOptions options,
    StatusAndRegistrationCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_STORAGE_ERROR,
                              std::move(callback));
    return;
  }

  DCHECK(options.min_interval >= 0 ||
         options.min_interval == kMinIntervalForOneShotSync);

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(
          &BackgroundSyncManager::RegisterCheckIfHasMainFrame,
          weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
          std::move(options),
          op_scheduler_.WrapCallbackToRunNext(id, std::move(callback))));
}

void BackgroundSyncManager::UnregisterPeriodicSync(
    int64_t sw_registration_id,
    const std::string& tag,
    BackgroundSyncManager::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback),
                                  BACKGROUND_SYNC_STATUS_STORAGE_ERROR));
    return;
  }

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(
          &BackgroundSyncManager::UnregisterPeriodicSyncImpl,
          weak_ptr_factory_.GetWeakPtr(), sw_registration_id, tag,
          op_scheduler_.WrapCallbackToRunNext(id, std::move(callback))));
}

void BackgroundSyncManager::DidResolveRegistration(
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_)
    return;
  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::DidResolveRegistrationImpl,
                     weak_ptr_factory_.GetWeakPtr(),
                     std::move(registration_info), id));
}

void BackgroundSyncManager::GetOneShotSyncRegistrations(
    int64_t sw_registration_id,
    StatusAndRegistrationsCallback callback) {
  GetRegistrations(BackgroundSyncType::ONE_SHOT, sw_registration_id,
                   std::move(callback));
}

void BackgroundSyncManager::GetPeriodicSyncRegistrations(
    int64_t sw_registration_id,
    StatusAndRegistrationsCallback callback) {
  GetRegistrations(BackgroundSyncType::PERIODIC, sw_registration_id,
                   std::move(callback));
}

void BackgroundSyncManager::UnregisterPeriodicSyncForOrigin(
    const url::Origin& origin) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::UnregisterForOriginImpl,
                     weak_ptr_factory_.GetWeakPtr(), std::move(origin),
                     MakeEmptyCompletion(id)));
}

void BackgroundSyncManager::UnregisterForOriginImpl(
    const url::Origin& origin,
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  std::vector<int64_t> service_worker_registrations_affected;

  for (const auto& service_worker_and_registration : active_registrations_) {
    const auto registrations = service_worker_and_registration.second;
    if (registrations.origin != origin)
      continue;

    service_worker_registrations_affected.emplace_back(
        service_worker_and_registration.first);
  }

  for (auto service_worker_registration_id :
       service_worker_registrations_affected) {
    active_registrations_.erase(service_worker_registration_id);
  }

  if (service_worker_registrations_affected.empty()) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  base::RepeatingClosure barrier_closure = base::BarrierClosure(
      service_worker_registrations_affected.size(),
      base::BindOnce(
          &BackgroundSyncManager::UnregisterForOriginScheduleDelayedProcessing,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));

  for (int64_t service_worker_registration_id :
       service_worker_registrations_affected) {
    StoreRegistrations(
        service_worker_registration_id,
        base::BindOnce(&BackgroundSyncManager::UnregisterForOriginDidStore,
                       weak_ptr_factory_.GetWeakPtr(), barrier_closure));
  }
}

void BackgroundSyncManager::UnregisterForOriginDidStore(
    base::OnceClosure done_closure,
    blink::ServiceWorkerStatusCode status) {
  if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
    // The service worker registration is gone.
    std::move(done_closure).Run();
    return;
  }

  if (status != blink::ServiceWorkerStatusCode::kOk) {
    DisableAndClearManager(std::move(done_closure));
    return;
  }

  std::move(done_closure).Run();
}

void BackgroundSyncManager::UnregisterForOriginScheduleDelayedProcessing(
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  ScheduleOrCancelDelayedProcessing(BackgroundSyncType::PERIODIC);
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

void BackgroundSyncManager::GetRegistrations(
    BackgroundSyncType sync_type,
    int64_t sw_registration_id,
    StatusAndRegistrationsCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::BindOnce(
            std::move(callback), BACKGROUND_SYNC_STATUS_STORAGE_ERROR,
            std::vector<std::unique_ptr<BackgroundSyncRegistration>>()));
    return;
  }

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(
          &BackgroundSyncManager::GetRegistrationsImpl,
          weak_ptr_factory_.GetWeakPtr(), sync_type, sw_registration_id,
          op_scheduler_.WrapCallbackToRunNext(id, std::move(callback))));
}

void BackgroundSyncManager::OnRegistrationDeleted(int64_t sw_registration_id,
                                                  const GURL& pattern) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // Operations already in the queue will either fail when they write to storage
  // or return stale results based on registrations loaded in memory. This is
  // inconsequential since the service worker is gone.
  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::OnRegistrationDeletedImpl,
                     weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
                     MakeEmptyCompletion(id)));
}

void BackgroundSyncManager::OnStorageWiped() {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // Operations already in the queue will either fail when they write to storage
  // or return stale results based on registrations loaded in memory. This is
  // inconsequential since the service workers are gone.
  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::OnStorageWipedImpl,
                     weak_ptr_factory_.GetWeakPtr(), MakeEmptyCompletion(id)));
}

void BackgroundSyncManager::EmulateDispatchSyncEvent(
    const std::string& tag,
    scoped_refptr<ServiceWorkerVersion> active_version,
    bool last_chance,
    ServiceWorkerVersion::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  blink::ServiceWorkerStatusCode code = CanEmulateSyncEvent(active_version);
  if (code != blink::ServiceWorkerStatusCode::kOk) {
    std::move(callback).Run(code);
    return;
  }

  DispatchSyncEvent(tag, std::move(active_version), last_chance,
                    std::move(callback));
}

void BackgroundSyncManager::EmulateDispatchPeriodicSyncEvent(
    const std::string& tag,
    scoped_refptr<ServiceWorkerVersion> active_version,
    ServiceWorkerVersion::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  blink::ServiceWorkerStatusCode code = CanEmulateSyncEvent(active_version);
  if (code != blink::ServiceWorkerStatusCode::kOk) {
    std::move(callback).Run(code);
    return;
  }

  DispatchPeriodicSyncEvent(tag, std::move(active_version),
                            std::move(callback));
}

void BackgroundSyncManager::EmulateServiceWorkerOffline(
    int64_t service_worker_id,
    bool is_offline) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  // Multiple DevTools sessions may want to set the same SW offline, which
  // is supposed to disable the background sync. For consistency with the
  // network stack, SW remains offline until all DevTools sessions disable
  // the offline mode.
  emulated_offline_sw_[service_worker_id] += is_offline ? 1 : -1;
  if (emulated_offline_sw_[service_worker_id] > 0)
    return;
  emulated_offline_sw_.erase(service_worker_id);
  FireReadyEvents(BackgroundSyncType::ONE_SHOT, /* reschedule= */ true,
                  base::DoNothing::Once());
}

BackgroundSyncManager::BackgroundSyncManager(
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
    scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context)
    : op_scheduler_(CacheStorageSchedulerClient::kBackgroundSync,
                    base::ThreadTaskRunnerHandle::Get()),
      service_worker_context_(std::move(service_worker_context)),
      proxy_(std::make_unique<BackgroundSyncProxy>(service_worker_context_)),
      devtools_context_(std::move(devtools_context)),
      parameters_(std::make_unique<BackgroundSyncParameters>()),
      disabled_(false),
      num_firing_registrations_one_shot_(0),
      num_firing_registrations_periodic_(0),
      clock_(base::DefaultClock::GetInstance()) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  DCHECK(devtools_context_);
  DCHECK(service_worker_context_);

  service_worker_context_->AddObserver(this);

#if defined(OS_ANDROID)
  network_observer_ = std::make_unique<BackgroundSyncNetworkObserverAndroid>(
      base::BindRepeating(&BackgroundSyncManager::OnNetworkChanged,
                          weak_ptr_factory_.GetWeakPtr()));
#else
  network_observer_ = std::make_unique<BackgroundSyncNetworkObserver>(
      base::BindRepeating(&BackgroundSyncManager::OnNetworkChanged,
                          weak_ptr_factory_.GetWeakPtr()));
#endif
}

void BackgroundSyncManager::Init() {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  DCHECK(!op_scheduler_.ScheduledOperations());
  DCHECK(!disabled_);

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::InitImpl,
                     weak_ptr_factory_.GetWeakPtr(), MakeEmptyCompletion(id)));
}

void BackgroundSyncManager::InitImpl(base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  // TODO(crbug.com/824858): Remove the else branch after the feature is
  // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
  // function so the if/else isn't needed.
  if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
    InitDidGetControllerParameters(
        std::move(callback),
        GetControllerParameters(
            service_worker_context_,
            std::make_unique<BackgroundSyncParameters>(*parameters_)));
  } else {
    GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(
            &GetControllerParameters, service_worker_context_,
            std::make_unique<BackgroundSyncParameters>(*parameters_)),
        base::BindOnce(&BackgroundSyncManager::InitDidGetControllerParameters,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }
}

void BackgroundSyncManager::InitDidGetControllerParameters(
    base::OnceClosure callback,
    std::unique_ptr<BackgroundSyncParameters> updated_parameters) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  parameters_ = std::move(updated_parameters);
  if (parameters_->disable) {
    disabled_ = true;
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  network_observer_->Init();

  GetDataFromBackend(
      kBackgroundSyncUserDataKey,
      base::BindOnce(&BackgroundSyncManager::InitDidGetDataFromBackend,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void BackgroundSyncManager::InitDidGetDataFromBackend(
    base::OnceClosure callback,
    const std::vector<std::pair<int64_t, std::string>>& user_data,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status != blink::ServiceWorkerStatusCode::kOk &&
      status != blink::ServiceWorkerStatusCode::kErrorNotFound) {
    DisableAndClearManager(std::move(callback));
    return;
  }

  std::set<url::Origin> suspended_periodic_sync_origins;
  std::set<url::Origin> registered_origins;
  for (const std::pair<int64_t, std::string>& data : user_data) {
    BackgroundSyncRegistrationsProto registrations_proto;
    if (registrations_proto.ParseFromString(data.second)) {
      BackgroundSyncRegistrations* registrations =
          &active_registrations_[data.first];
      registrations->origin =
          url::Origin::Create(GURL(registrations_proto.origin()));

      for (const auto& registration_proto :
           registrations_proto.registration()) {
        BackgroundSyncType sync_type =
            registration_proto.has_periodic_sync_options()
                ? BackgroundSyncType::PERIODIC
                : BackgroundSyncType::ONE_SHOT;
        BackgroundSyncRegistration* registration =
            &registrations
                 ->registration_map[{registration_proto.tag(), sync_type}];

        blink::mojom::SyncRegistrationOptions* options =
            registration->options();
        options->tag = registration_proto.tag();
        if (sync_type == BackgroundSyncType::PERIODIC) {
          options->min_interval =
              registration_proto.periodic_sync_options().min_interval();
          if (options->min_interval < 0) {
            DisableAndClearManager(std::move(callback));
            return;
          }
        } else {
          options->min_interval = kMinIntervalForOneShotSync;
        }

        registration->set_num_attempts(registration_proto.num_attempts());
        registration->set_delay_until(
            base::Time::FromInternalValue(registration_proto.delay_until()));
        registration->set_origin(registrations->origin);
        registered_origins.insert(registration->origin());
        if (registration->is_suspended()) {
          suspended_periodic_sync_origins.insert(registration->origin());
        }
        registration->set_resolved();
        if (registration_proto.has_max_attempts())
          registration->set_max_attempts(registration_proto.max_attempts());
        else
          registration->set_max_attempts(parameters_->max_sync_attempts);
      }
    }
  }

  FireReadyEvents(BackgroundSyncType::ONE_SHOT, /* reschedule= */ true,
                  base::DoNothing::Once());
  FireReadyEvents(BackgroundSyncType::PERIODIC, /* reschedule= */ true,
                  base::DoNothing::Once());
  proxy_->SendSuspendedPeriodicSyncOrigins(
      std::move(suspended_periodic_sync_origins));
  proxy_->SendRegisteredPeriodicSyncOrigins(std::move(registered_origins));
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

void BackgroundSyncManager::RegisterCheckIfHasMainFrame(
    int64_t sw_registration_id,
    blink::mojom::SyncRegistrationOptions options,
    StatusAndRegistrationCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  ServiceWorkerRegistration* sw_registration =
      service_worker_context_->GetLiveRegistration(sw_registration_id);
  if (!sw_registration || !sw_registration->active_version()) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_NO_SERVICE_WORKER,
                              std::move(callback));
    return;
  }

  HasMainFrameWindowClient(
      url::Origin::Create(sw_registration->scope().GetOrigin()),
      base::BindOnce(&BackgroundSyncManager::RegisterDidCheckIfMainFrame,
                     weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
                     std::move(options), std::move(callback)));
}

void BackgroundSyncManager::RegisterDidCheckIfMainFrame(
    int64_t sw_registration_id,
    blink::mojom::SyncRegistrationOptions options,
    StatusAndRegistrationCallback callback,
    bool has_main_frame_client) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (!has_main_frame_client) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_NOT_ALLOWED,
                              std::move(callback));
    return;
  }
  RegisterImpl(sw_registration_id, std::move(options), std::move(callback));
}

void BackgroundSyncManager::RegisterImpl(
    int64_t sw_registration_id,
    blink::mojom::SyncRegistrationOptions options,
    StatusAndRegistrationCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_STORAGE_ERROR,
                              std::move(callback));
    return;
  }

  if (options.tag.length() > kMaxTagLength) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_NOT_ALLOWED,
                              std::move(callback));
    return;
  }

  ServiceWorkerRegistration* sw_registration =
      service_worker_context_->GetLiveRegistration(sw_registration_id);
  if (!sw_registration || !sw_registration->active_version()) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_NO_SERVICE_WORKER,
                              std::move(callback));
    return;
  }

  BackgroundSyncType sync_type = GetBackgroundSyncType(options);

  if (parameters_->skip_permissions_check_for_testing) {
    RegisterDidAskForPermission(
        sw_registration_id, std::move(options), std::move(callback),
        {PermissionStatus::GRANTED, PermissionStatus::GRANTED});
    return;
  }

  // TODO(crbug.com/824858): Remove the else branch after the feature is
  // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
  // function so the if/else isn't needed.
  if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
    SyncAndNotificationPermissions permission =
        GetBackgroundSyncPermissionOnUIThread(
            service_worker_context_,
            url::Origin::Create(sw_registration->scope().GetOrigin()),
            sync_type);
    RegisterDidAskForPermission(sw_registration_id, std::move(options),
                                std::move(callback), permission);
  } else {
    GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(
            &GetBackgroundSyncPermissionOnUIThread, service_worker_context_,
            url::Origin::Create(sw_registration->scope().GetOrigin()),
            sync_type),
        base::BindOnce(&BackgroundSyncManager::RegisterDidAskForPermission,
                       weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
                       std::move(options), std::move(callback)));
  }
}

void BackgroundSyncManager::RegisterDidAskForPermission(
    int64_t sw_registration_id,
    blink::mojom::SyncRegistrationOptions options,
    StatusAndRegistrationCallback callback,
    SyncAndNotificationPermissions permission_statuses) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (permission_statuses.first == PermissionStatus::DENIED) {
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_PERMISSION_DENIED,
                              std::move(callback));
    return;
  }
  DCHECK_EQ(permission_statuses.first, PermissionStatus::GRANTED);

  ServiceWorkerRegistration* sw_registration =
      service_worker_context_->GetLiveRegistration(sw_registration_id);
  if (!sw_registration || !sw_registration->active_version()) {
    // The service worker was shut down in the interim.
    RecordFailureAndPostError(GetBackgroundSyncType(options),
                              BACKGROUND_SYNC_STATUS_NO_SERVICE_WORKER,
                              std::move(callback));
    return;
  }

  BackgroundSyncRegistration* existing_registration =
      LookupActiveRegistration(blink::mojom::BackgroundSyncRegistrationInfo(
          sw_registration_id, options.tag, GetBackgroundSyncType(options)));

  url::Origin origin =
      url::Origin::Create(sw_registration->scope().GetOrigin());

  if (GetBackgroundSyncType(options) ==
      blink::mojom::BackgroundSyncType::ONE_SHOT) {
    bool is_reregistered =
        existing_registration && existing_registration->IsFiring();
    RunOrPostTaskOnThread(
        FROM_HERE, BrowserThread::UI,
        base::BindOnce(&NotifyOneShotBackgroundSyncRegisteredOnUIThread,
                       service_worker_context_, origin,
                       /* can_fire= */ AreOptionConditionsMet(),
                       is_reregistered));
  } else {
    RunOrPostTaskOnThread(
        FROM_HERE, BrowserThread::UI,
        base::BindOnce(
            &NotifyPeriodicBackgroundSyncRegisteredOnUIThread,
            service_worker_context_, origin, options.min_interval,
            /* is_reregistered= */ static_cast<bool>(existing_registration)));
  }

  if (existing_registration) {
    DCHECK_EQ(existing_registration->options()->tag, options.tag);
    DCHECK_EQ(existing_registration->sync_type(),
              GetBackgroundSyncType(options));

    if (existing_registration->options()->Equals(options)) {
      BackgroundSyncMetrics::RegistrationCouldFire registration_could_fire =
          AreOptionConditionsMet()
              ? BackgroundSyncMetrics::REGISTRATION_COULD_FIRE
              : BackgroundSyncMetrics::REGISTRATION_COULD_NOT_FIRE;
      BackgroundSyncMetrics::CountRegisterSuccess(
          existing_registration->sync_type(), options.min_interval,
          registration_could_fire,
          BackgroundSyncMetrics::REGISTRATION_IS_DUPLICATE);

      if (existing_registration->IsFiring()) {
        existing_registration->set_sync_state(
            blink::mojom::BackgroundSyncState::REREGISTERED_WHILE_FIRING);
      }

      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE,
          base::BindOnce(std::move(callback), BACKGROUND_SYNC_STATUS_OK,
                         std::make_unique<BackgroundSyncRegistration>(
                             *existing_registration)));
      return;
    }
  }

  BackgroundSyncRegistration registration;

  registration.set_origin(origin);
  *registration.options() = std::move(options);

  // TODO(crbug.com/963487): This section below is really confusing. Add a
  // comment explaining what's going on here, or annotate permission_statuses.
  registration.set_max_attempts(
      permission_statuses.second == PermissionStatus::GRANTED
          ? parameters_->max_sync_attempts_with_notification_permission
          : parameters_->max_sync_attempts);

  // Skip the current registration when getting time till next scheduled
  // periodic sync event for the origin. This is because we'll be updating the
  // schedule time of this registration soon anyway, so considering its
  // schedule time would cause us to calculate incorrect delay.
  if (registration.sync_type() == BackgroundSyncType::PERIODIC) {
    // TODO(crbug.com/824858): Remove the else branch after the feature is
    // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
    // function so the if/else isn't needed.
    if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
      base::TimeDelta delay = GetNextEventDelay(
          service_worker_context_, registration,
          std::make_unique<BackgroundSyncParameters>(*parameters_),
          GetSmallestPeriodicSyncEventDelayForOrigin(
              origin, registration.options()->tag));
      RegisterDidGetDelay(sw_registration_id, registration, std::move(callback),
                          delay);
    } else {
      GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
          FROM_HERE,
          base::BindOnce(
              &GetNextEventDelay, service_worker_context_, registration,
              std::make_unique<BackgroundSyncParameters>(*parameters_),
              GetSmallestPeriodicSyncEventDelayForOrigin(
                  origin, registration.options()->tag)),
          base::BindOnce(&BackgroundSyncManager::RegisterDidGetDelay,
                         weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
                         registration, std::move(callback)));
    }
    return;
  }

  RegisterDidGetDelay(sw_registration_id, registration, std::move(callback),
                      base::TimeDelta());
}

void BackgroundSyncManager::RegisterDidGetDelay(
    int64_t sw_registration_id,
    BackgroundSyncRegistration registration,
    StatusAndRegistrationCallback callback,
    base::TimeDelta delay) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // We don't fire periodic Background Sync registrations immediately after
  // registration, so set delay_until to override its default value.
  if (registration.sync_type() == BackgroundSyncType::PERIODIC)
    registration.set_delay_until(clock_->Now() + delay);

  ServiceWorkerRegistration* sw_registration =
      service_worker_context_->GetLiveRegistration(sw_registration_id);
  if (!sw_registration || !sw_registration->active_version()) {
    // The service worker was shut down in the interim.
    RecordFailureAndPostError(registration.sync_type(),
                              BACKGROUND_SYNC_STATUS_NO_SERVICE_WORKER,
                              std::move(callback));
    return;
  }

  if (registration.sync_type() == BackgroundSyncType::PERIODIC &&
      ShouldLogToDevTools(registration.sync_type())) {
    devtools_context_->LogBackgroundServiceEventOnCoreThread(
        sw_registration_id, registration.origin(),
        DevToolsBackgroundService::kPeriodicBackgroundSync,
        /* event_name= */ "Got next event delay",
        /* instance_id= */ registration.options()->tag,
        {{"Next Attempt Delay (ms)",
          GetDelayAsString(registration.delay_until() - clock_->Now())}});
  }

  AddOrUpdateActiveRegistration(
      sw_registration_id,
      url::Origin::Create(sw_registration->scope().GetOrigin()), registration);

  StoreRegistrations(
      sw_registration_id,
      base::BindOnce(&BackgroundSyncManager::RegisterDidStore,
                     weak_ptr_factory_.GetWeakPtr(), sw_registration_id,
                     registration, std::move(callback)));
}

void BackgroundSyncManager::UnregisterPeriodicSyncImpl(
    int64_t sw_registration_id,
    const std::string& tag,
    BackgroundSyncManager::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  auto registration_info = blink::mojom::BackgroundSyncRegistrationInfo(
      sw_registration_id, tag, BackgroundSyncType::PERIODIC);

  if (!LookupActiveRegistration(registration_info)) {
    // It's okay to not find a matching tag.
    UnregisterPeriodicSyncDidStore(std::move(callback),
                                   blink::ServiceWorkerStatusCode::kOk);
    return;
  }

  RemoveActiveRegistration(std::move(registration_info));
  StoreRegistrations(
      sw_registration_id,
      base::BindOnce(&BackgroundSyncManager::UnregisterPeriodicSyncDidStore,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void BackgroundSyncManager::UnregisterPeriodicSyncDidStore(
    BackgroundSyncManager::StatusCallback callback,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status != blink::ServiceWorkerStatusCode::kOk) {
    BackgroundSyncMetrics::CountUnregisterPeriodicSync(
        BACKGROUND_SYNC_STATUS_STORAGE_ERROR);
    DisableAndClearManager(base::BindOnce(
        std::move(callback), BACKGROUND_SYNC_STATUS_STORAGE_ERROR));
    return;
  }

  BackgroundSyncMetrics::CountUnregisterPeriodicSync(BACKGROUND_SYNC_STATUS_OK);
  ScheduleOrCancelDelayedProcessing(BackgroundSyncType::PERIODIC);
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(callback), BACKGROUND_SYNC_STATUS_OK));
}

void BackgroundSyncManager::DisableAndClearManager(base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  disabled_ = true;

  active_registrations_.clear();

  // Delete all backend entries. The memory representation of registered syncs
  // may be out of sync with storage (e.g., due to corruption detection on
  // loading from storage), so reload the registrations from storage again.
  GetDataFromBackend(
      kBackgroundSyncUserDataKey,
      base::BindOnce(&BackgroundSyncManager::DisableAndClearDidGetRegistrations,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void BackgroundSyncManager::DisableAndClearDidGetRegistrations(
    base::OnceClosure callback,
    const std::vector<std::pair<int64_t, std::string>>& user_data,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status != blink::ServiceWorkerStatusCode::kOk || user_data.empty()) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  base::RepeatingClosure barrier_closure =
      base::BarrierClosure(user_data.size(), std::move(callback));

  for (const auto& sw_id_and_regs : user_data) {
    service_worker_context_->ClearRegistrationUserData(
        sw_id_and_regs.first, {kBackgroundSyncUserDataKey},
        base::BindOnce(&BackgroundSyncManager::DisableAndClearManagerClearedOne,
                       weak_ptr_factory_.GetWeakPtr(), barrier_closure));
  }
}

void BackgroundSyncManager::DisableAndClearManagerClearedOne(
    base::OnceClosure barrier_closure,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // The status doesn't matter at this point, there is nothing else to be done.
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                std::move(barrier_closure));
}

BackgroundSyncRegistration* BackgroundSyncManager::LookupActiveRegistration(
    const blink::mojom::BackgroundSyncRegistrationInfo& registration_info) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  auto it = active_registrations_.find(
      registration_info.service_worker_registration_id);
  if (it == active_registrations_.end())
    return nullptr;

  BackgroundSyncRegistrations& registrations = it->second;
  DCHECK(!registrations.origin.opaque());

  auto key_and_registration_iter = registrations.registration_map.find(
      {registration_info.tag, registration_info.sync_type});
  if (key_and_registration_iter == registrations.registration_map.end())
    return nullptr;

  return &key_and_registration_iter->second;
}

void BackgroundSyncManager::StoreRegistrations(
    int64_t sw_registration_id,
    ServiceWorkerRegistry::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // Serialize the data.
  const BackgroundSyncRegistrations& registrations =
      active_registrations_[sw_registration_id];
  BackgroundSyncRegistrationsProto registrations_proto;
  registrations_proto.set_origin(registrations.origin.Serialize());

  for (const auto& key_and_registration : registrations.registration_map) {
    const BackgroundSyncRegistration& registration =
        key_and_registration.second;
    BackgroundSyncRegistrationProto* registration_proto =
        registrations_proto.add_registration();
    registration_proto->set_tag(registration.options()->tag);
    if (registration.options()->min_interval >= 0) {
      registration_proto->mutable_periodic_sync_options()->set_min_interval(
          registration.options()->min_interval);
    }
    registration_proto->set_num_attempts(registration.num_attempts());
    registration_proto->set_max_attempts(registration.max_attempts());
    registration_proto->set_delay_until(
        registration.delay_until().ToInternalValue());
  }
  std::string serialized;
  bool success = registrations_proto.SerializeToString(&serialized);
  DCHECK(success);

  StoreDataInBackend(sw_registration_id, registrations.origin,
                     kBackgroundSyncUserDataKey, serialized,
                     std::move(callback));
}

void BackgroundSyncManager::RegisterDidStore(
    int64_t sw_registration_id,
    const BackgroundSyncRegistration& registration,
    StatusAndRegistrationCallback callback,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
    // The service worker registration is gone.
    active_registrations_.erase(sw_registration_id);
    RecordFailureAndPostError(registration.sync_type(),
                              BACKGROUND_SYNC_STATUS_STORAGE_ERROR,
                              std::move(callback));
    return;
  }

  if (status != blink::ServiceWorkerStatusCode::kOk) {
    BackgroundSyncMetrics::CountRegisterFailure(
        registration.sync_type(), BACKGROUND_SYNC_STATUS_STORAGE_ERROR);
    DisableAndClearManager(base::BindOnce(
        std::move(callback), BACKGROUND_SYNC_STATUS_STORAGE_ERROR, nullptr));
    return;
  }

  // Update controller of this new origin.
  if (registration.sync_type() == BackgroundSyncType::PERIODIC)
    proxy_->AddToTrackedOrigins(registration.origin());

  BackgroundSyncMetrics::RegistrationCouldFire registration_could_fire =
      AreOptionConditionsMet()
          ? BackgroundSyncMetrics::REGISTRATION_COULD_FIRE
          : BackgroundSyncMetrics::REGISTRATION_COULD_NOT_FIRE;
  BackgroundSyncMetrics::CountRegisterSuccess(
      registration.sync_type(), registration.options()->min_interval,
      registration_could_fire,
      BackgroundSyncMetrics::REGISTRATION_IS_NOT_DUPLICATE);

  ScheduleOrCancelDelayedProcessing(BackgroundSyncType::PERIODIC);

  // Tell the client that the registration is ready. We won't fire it until the
  // client has resolved the registration event.
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), BACKGROUND_SYNC_STATUS_OK,
                                std::make_unique<BackgroundSyncRegistration>(
                                    registration)));
}

void BackgroundSyncManager::DidResolveRegistrationImpl(
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info,
    CacheStorageSchedulerId id) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  BackgroundSyncRegistration* registration =
      LookupActiveRegistration(*registration_info);
  if (!registration) {
    // There might not be a registration if the client ack's a registration that
    // was a duplicate in the first place and was already firing and finished by
    // the time the client acknowledged the second registration.
    op_scheduler_.CompleteOperationAndRunNext(id);
    return;
  }

  registration->set_resolved();

  // TODO(crbug.com/824858): Remove the else branch after the feature is
  // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
  // function so the if/else isn't needed.
  if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
    ResolveRegistrationDidCreateKeepAlive(
        id, CreateBackgroundSyncEventKeepAliveOnUIThread(
                service_worker_context_, std::move(*registration_info)));
  } else {
    GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&CreateBackgroundSyncEventKeepAliveOnUIThread,
                       service_worker_context_, std::move(*registration_info)),
        base::BindOnce(
            &BackgroundSyncManager::ResolveRegistrationDidCreateKeepAlive,
            weak_ptr_factory_.GetWeakPtr(), id));
  }
}

void BackgroundSyncManager::ResolveRegistrationDidCreateKeepAlive(
    CacheStorageSchedulerId id,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  FireReadyEvents(BackgroundSyncType::ONE_SHOT, /* reschedule= */ true,
                  base::DoNothing::Once(), std::move(keepalive));
  op_scheduler_.CompleteOperationAndRunNext(id);
}

void BackgroundSyncManager::RemoveActiveRegistration(
    const blink::mojom::BackgroundSyncRegistrationInfo& registration_info) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  DCHECK(LookupActiveRegistration(registration_info));

  BackgroundSyncRegistrations* registrations =
      &active_registrations_[registration_info.service_worker_registration_id];
  const url::Origin& origin = registrations->origin;

  registrations->registration_map.erase(
      {registration_info.tag, registration_info.sync_type});

  // Update controller's list of registered origin if necessary.
  if (registrations->registration_map.empty())
    proxy_->RemoveFromTrackedOrigins(origin);
  else {
    bool no_more_periodic_sync_registrations = true;
    for (auto& key_and_registration : registrations->registration_map) {
      if (key_and_registration.second.sync_type() ==
          BackgroundSyncType::PERIODIC) {
        no_more_periodic_sync_registrations = false;
        break;
      }
    }
    if (no_more_periodic_sync_registrations)
      proxy_->RemoveFromTrackedOrigins(origin);
  }

  if (registration_info.sync_type == BackgroundSyncType::PERIODIC &&
      ShouldLogToDevTools(registration_info.sync_type)) {
    devtools_context_->LogBackgroundServiceEventOnCoreThread(
        registration_info.service_worker_registration_id, origin,
        DevToolsBackgroundService::kPeriodicBackgroundSync,
        /* event_name= */ "Unregistered periodicsync",
        /* instance_id= */ registration_info.tag,
        /* event_metadata= */ {});
  }
}

void BackgroundSyncManager::AddOrUpdateActiveRegistration(
    int64_t sw_registration_id,
    const url::Origin& origin,
    const BackgroundSyncRegistration& sync_registration) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  BackgroundSyncRegistrations* registrations =
      &active_registrations_[sw_registration_id];
  registrations->origin = origin;

  BackgroundSyncType sync_type = sync_registration.sync_type();
  registrations
      ->registration_map[{sync_registration.options()->tag, sync_type}] =
      sync_registration;

  if (ShouldLogToDevTools(sync_registration.sync_type())) {
    std::map<std::string, std::string> event_metadata;
    if (sync_registration.sync_type() == BackgroundSyncType::PERIODIC) {
      event_metadata["minInterval"] =
          base::NumberToString(sync_registration.options()->min_interval);
    }
    devtools_context_->LogBackgroundServiceEventOnCoreThread(
        sw_registration_id, origin, GetDevToolsBackgroundService(sync_type),
        /* event_name= */ "Registered " + GetSyncEventName(sync_type),
        /* instance_id= */ sync_registration.options()->tag, event_metadata);
  }
}

void BackgroundSyncManager::StoreDataInBackend(
    int64_t sw_registration_id,
    const url::Origin& origin,
    const std::string& backend_key,
    const std::string& data,
    ServiceWorkerRegistry::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  service_worker_context_->StoreRegistrationUserData(
      sw_registration_id, origin, {{backend_key, data}}, std::move(callback));
}

void BackgroundSyncManager::GetDataFromBackend(
    const std::string& backend_key,
    ServiceWorkerRegistry::GetUserDataForAllRegistrationsCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  service_worker_context_->GetUserDataForAllRegistrations(backend_key,
                                                          std::move(callback));
}

void BackgroundSyncManager::DispatchSyncEvent(
    const std::string& tag,
    scoped_refptr<ServiceWorkerVersion> active_version,
    bool last_chance,
    ServiceWorkerVersion::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  DCHECK(active_version);

  if (active_version->running_status() != EmbeddedWorkerStatus::RUNNING) {
    active_version->RunAfterStartWorker(
        ServiceWorkerMetrics::EventType::SYNC,
        base::BindOnce(&DidStartWorkerForSyncEvent,
                       base::BindOnce(&BackgroundSyncManager::DispatchSyncEvent,
                                      weak_ptr_factory_.GetWeakPtr(), tag,
                                      active_version, last_chance),
                       std::move(callback)));
    return;
  }

  auto repeating_callback =
      base::AdaptCallbackForRepeating(std::move(callback));

  int request_id = active_version->StartRequestWithCustomTimeout(
      ServiceWorkerMetrics::EventType::SYNC, repeating_callback,
      parameters_->max_sync_event_duration,
      ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);

  active_version->endpoint()->DispatchSyncEvent(
      tag, last_chance, parameters_->max_sync_event_duration,
      base::BindOnce(&OnSyncEventFinished, active_version, request_id,
                     std::move(repeating_callback)));

  if (devtools_context_->IsRecording(
          DevToolsBackgroundService::kBackgroundSync)) {
    devtools_context_->LogBackgroundServiceEventOnCoreThread(
        active_version->registration_id(), active_version->origin(),
        DevToolsBackgroundService::kBackgroundSync,
        /* event_name= */ "Dispatched sync event",
        /* instance_id= */ tag,
        /* event_metadata= */
        {{"Last Chance", last_chance ? "Yes" : "No"}});
  }
}

void BackgroundSyncManager::DispatchPeriodicSyncEvent(
    const std::string& tag,
    scoped_refptr<ServiceWorkerVersion> active_version,
    ServiceWorkerVersion::StatusCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  DCHECK(active_version);

  if (active_version->running_status() != EmbeddedWorkerStatus::RUNNING) {
    active_version->RunAfterStartWorker(
        ServiceWorkerMetrics::EventType::PERIODIC_SYNC,
        base::BindOnce(
            &DidStartWorkerForSyncEvent,
            base::BindOnce(&BackgroundSyncManager::DispatchPeriodicSyncEvent,
                           weak_ptr_factory_.GetWeakPtr(), tag, active_version),
            std::move(callback)));
    return;
  }

  auto repeating_callback =
      base::AdaptCallbackForRepeating(std::move(callback));

  int request_id = active_version->StartRequestWithCustomTimeout(
      ServiceWorkerMetrics::EventType::PERIODIC_SYNC, repeating_callback,
      parameters_->max_sync_event_duration,
      ServiceWorkerVersion::CONTINUE_ON_TIMEOUT);

  active_version->endpoint()->DispatchPeriodicSyncEvent(
      tag, parameters_->max_sync_event_duration,
      base::BindOnce(&OnSyncEventFinished, active_version, request_id,
                     std::move(repeating_callback)));

  if (devtools_context_->IsRecording(
          DevToolsBackgroundService::kPeriodicBackgroundSync)) {
    devtools_context_->LogBackgroundServiceEventOnCoreThread(
        active_version->registration_id(), active_version->origin(),
        DevToolsBackgroundService::kPeriodicBackgroundSync,
        /* event_name= */ "Dispatched periodicsync event",
        /* instance_id= */ tag,
        /* event_metadata= */ {});
  }
}

void BackgroundSyncManager::HasMainFrameWindowClient(const url::Origin& origin,
                                                     BoolCallback callback) {
  service_worker_context_->HasMainFrameWindowClient(origin.GetURL(),
                                                    std::move(callback));
}

void BackgroundSyncManager::GetRegistrationsImpl(
    BackgroundSyncType sync_type,
    int64_t sw_registration_id,
    StatusAndRegistrationsCallback callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  std::vector<std::unique_ptr<BackgroundSyncRegistration>> out_registrations;

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback),
                                  BACKGROUND_SYNC_STATUS_STORAGE_ERROR,
                                  std::move(out_registrations)));
    return;
  }

  auto it = active_registrations_.find(sw_registration_id);

  if (it != active_registrations_.end()) {
    const BackgroundSyncRegistrations& registrations = it->second;
    for (const auto& key_and_registration : registrations.registration_map) {
      const BackgroundSyncRegistration& registration =
          key_and_registration.second;
      if (registration.sync_type() != sync_type)
        continue;
      out_registrations.push_back(
          std::make_unique<BackgroundSyncRegistration>(registration));
    }
  }

  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback), BACKGROUND_SYNC_STATUS_OK,
                                std::move(out_registrations)));
}

bool BackgroundSyncManager::AreOptionConditionsMet() {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  return network_observer_->NetworkSufficient();
}

bool BackgroundSyncManager::AllConditionsExceptConnectivitySatisfied(
    const BackgroundSyncRegistration& registration,
    int64_t service_worker_id) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // Don't fire the registration if the client hasn't yet resolved its
  // registration promise.
  if (!registration.resolved() &&
      registration.sync_type() == BackgroundSyncType::ONE_SHOT) {
    return false;
  }

  if (registration.sync_state() != blink::mojom::BackgroundSyncState::PENDING)
    return false;

  if (registration.is_suspended())
    return false;

  if (base::Contains(emulated_offline_sw_, service_worker_id))
    return false;

  return true;
}

bool BackgroundSyncManager::CanFireAnyRegistrationUponConnectivity(
    BackgroundSyncType sync_type) {
  for (const auto& sw_reg_id_and_registrations : active_registrations_) {
    int64_t service_worker_registration_id = sw_reg_id_and_registrations.first;
    for (const auto& key_and_registration :
         sw_reg_id_and_registrations.second.registration_map) {
      const BackgroundSyncRegistration& registration =
          key_and_registration.second;
      if (sync_type != registration.sync_type())
        continue;

      if (AllConditionsExceptConnectivitySatisfied(
              registration, service_worker_registration_id)) {
        return true;
      }
    }
  }
  return false;
}

bool& BackgroundSyncManager::delayed_processing_scheduled(
    BackgroundSyncType sync_type) {
  if (sync_type == BackgroundSyncType::ONE_SHOT)
    return delayed_processing_scheduled_one_shot_sync_;
  else
    return delayed_processing_scheduled_periodic_sync_;
}

void BackgroundSyncManager::ScheduleOrCancelDelayedProcessing(
    BackgroundSyncType sync_type) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  bool can_fire_with_connectivity =
      CanFireAnyRegistrationUponConnectivity(sync_type);

  if (delayed_processing_scheduled(sync_type) && !can_fire_with_connectivity &&
      !GetNumFiringRegistrations(sync_type)) {
    CancelDelayedProcessingOfRegistrations(sync_type);
    delayed_processing_scheduled(sync_type) = false;
  } else if (can_fire_with_connectivity ||
             GetNumFiringRegistrations(sync_type)) {
    ScheduleDelayedProcessingOfRegistrations(sync_type);
    delayed_processing_scheduled(sync_type) = true;
  }
}

bool BackgroundSyncManager::IsRegistrationReadyToFire(
    const BackgroundSyncRegistration& registration,
    int64_t service_worker_id) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (clock_->Now() < registration.delay_until())
    return false;

  return AllConditionsExceptConnectivitySatisfied(registration,
                                                  service_worker_id) &&
         AreOptionConditionsMet();
}

int BackgroundSyncManager::GetNumFiringRegistrations(
    BackgroundSyncType sync_type) {
  if (sync_type == BackgroundSyncType::ONE_SHOT)
    return num_firing_registrations_one_shot_;
  return num_firing_registrations_periodic_;
}

void BackgroundSyncManager::UpdateNumFiringRegistrationsBy(
    BackgroundSyncType sync_type,
    int to_add) {
  if (sync_type == BackgroundSyncType::ONE_SHOT)
    num_firing_registrations_one_shot_ += to_add;
  else
    num_firing_registrations_periodic_ += to_add;
}

bool BackgroundSyncManager::AllRegistrationsWaitingToBeResolved() const {
  for (const auto& active_registration : active_registrations_) {
    for (const auto& key_and_registration :
         active_registration.second.registration_map) {
      const BackgroundSyncRegistration& registration =
          key_and_registration.second;
      if (registration.resolved())
        return false;
    }
  }
  return true;
}

base::TimeDelta BackgroundSyncManager::GetSoonestWakeupDelta(
    BackgroundSyncType sync_type,
    base::Time last_browser_wakeup_time) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  base::TimeDelta soonest_wakeup_delta = base::TimeDelta::Max();
  bool need_retries = false;
  for (const auto& sw_reg_id_and_registrations : active_registrations_) {
    for (const auto& key_and_registration :
         sw_reg_id_and_registrations.second.registration_map) {
      const BackgroundSyncRegistration& registration =
          key_and_registration.second;
      if (registration.sync_type() != sync_type)
        continue;
      if (registration.num_attempts() > 0 &&
          registration.num_attempts() < registration.max_attempts()) {
        need_retries = true;
      }
      if (registration.sync_state() ==
          blink::mojom::BackgroundSyncState::PENDING) {
        if (clock_->Now() >= registration.delay_until()) {
          soonest_wakeup_delta = base::TimeDelta();
          break;
        } else {
          base::TimeDelta delay_delta =
              registration.delay_until() - clock_->Now();
          soonest_wakeup_delta = std::min(delay_delta, soonest_wakeup_delta);
        }
      }
    }
  }

  // If the browser is closed while firing events, the browser needs a task to
  // wake it back up and try again.
  if (GetNumFiringRegistrations(sync_type) > 0 &&
      soonest_wakeup_delta > parameters_->min_sync_recovery_time) {
    soonest_wakeup_delta = parameters_->min_sync_recovery_time;
  }

  // If we're still waiting for registrations to be resolved, don't schedule
  // a wake up task eagerly.
  if (sync_type == BackgroundSyncType::ONE_SHOT &&
      AllRegistrationsWaitingToBeResolved() &&
      soonest_wakeup_delta < parameters_->min_sync_recovery_time) {
    soonest_wakeup_delta = parameters_->min_sync_recovery_time;
  }

  // The browser may impose a hard limit on how often it can be woken up to
  // process periodic Background Sync registrations. This excludes retries.
  if (sync_type == BackgroundSyncType::PERIODIC && !need_retries) {
    soonest_wakeup_delta = MaybeApplyBrowserWakeupCountLimit(
        soonest_wakeup_delta, last_browser_wakeup_time);
  }
  return soonest_wakeup_delta;
}

base::TimeDelta BackgroundSyncManager::MaybeApplyBrowserWakeupCountLimit(
    base::TimeDelta soonest_wakeup_delta,
    base::Time last_browser_wakeup_time) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  if (last_browser_wakeup_time.is_null())
    return soonest_wakeup_delta;

  base::TimeDelta time_since_last_browser_wakeup =
      clock_->Now() - last_browser_wakeup_time;
  if (time_since_last_browser_wakeup >=
      parameters_->min_periodic_sync_events_interval) {
    return soonest_wakeup_delta;
  }

  base::TimeDelta time_till_next_allowed_browser_wakeup =
      parameters_->min_periodic_sync_events_interval -
      time_since_last_browser_wakeup;
  return std::max(soonest_wakeup_delta, time_till_next_allowed_browser_wakeup);
}

base::TimeDelta
BackgroundSyncManager::GetSmallestPeriodicSyncEventDelayForOrigin(
    const url::Origin& origin,
    const std::string& tag_to_skip) const {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  base::Time soonest_wakeup_time = base::Time();
  for (const auto& active_registration : active_registrations_) {
    if (active_registration.second.origin != origin)
      continue;

    const auto& tag_and_registrations =
        active_registration.second.registration_map;
    for (const auto& tag_and_registration : tag_and_registrations) {
      if (/* tag= */ tag_and_registration.first.first == tag_to_skip)
        continue;
      if (/* sync_type= */ tag_and_registration.first.second !=
          BackgroundSyncType::PERIODIC) {
        continue;
      }
      if (tag_and_registration.second.delay_until().is_null())
        continue;
      if (soonest_wakeup_time.is_null() ||
          tag_and_registration.second.delay_until() < soonest_wakeup_time) {
        soonest_wakeup_time = tag_and_registration.second.delay_until();
      }
    }
  }

  if (soonest_wakeup_time.is_null())
    return base::TimeDelta::Max();

  if (soonest_wakeup_time < clock_->Now())
    return base::TimeDelta();

  return soonest_wakeup_time - clock_->Now();
}

void BackgroundSyncManager::RevivePeriodicSyncRegistrations(
    url::Origin origin) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_)
    return;

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::ReviveOriginImpl,
                     weak_ptr_factory_.GetWeakPtr(), std::move(origin),
                     MakeEmptyCompletion(id)));
}

void BackgroundSyncManager::ReviveOriginImpl(url::Origin origin,
                                             base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  // Create a list of registrations to revive.
  std::vector<const BackgroundSyncRegistration*> to_revive;
  std::map<const BackgroundSyncRegistration*, int64_t>
      service_worker_registration_ids;

  for (const auto& active_registration : active_registrations_) {
    int64_t service_worker_registration_id = active_registration.first;
    if (active_registration.second.origin != origin)
      continue;

    for (const auto& key_and_registration :
         active_registration.second.registration_map) {
      const BackgroundSyncRegistration* registration =
          &key_and_registration.second;
      if (!registration->is_suspended())
        continue;

      to_revive.push_back(registration);
      service_worker_registration_ids[registration] =
          service_worker_registration_id;
    }
  }

  if (to_revive.empty()) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  base::RepeatingClosure received_new_delays_closure = base::BarrierClosure(
      to_revive.size(),
      base::BindOnce(
          &BackgroundSyncManager::DidReceiveDelaysForSuspendedRegistrations,
          weak_ptr_factory_.GetWeakPtr(), std::move(callback)));

  for (const auto* registration : to_revive) {
    // TODO(crbug.com/824858): Remove the else branch after the feature is
    // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
    // function so the if/else isn't needed.
    if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
      base::TimeDelta delay = GetNextEventDelay(
          service_worker_context_, *registration,
          std::make_unique<BackgroundSyncParameters>(*parameters_),
          GetSmallestPeriodicSyncEventDelayForOrigin(
              origin, registration->options()->tag));
      ReviveDidGetNextEventDelay(service_worker_registration_ids[registration],
                                 *registration, received_new_delays_closure,
                                 delay);
    } else {
      GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
          FROM_HERE,
          base::BindOnce(
              &GetNextEventDelay, service_worker_context_, *registration,
              std::make_unique<BackgroundSyncParameters>(*parameters_),
              GetSmallestPeriodicSyncEventDelayForOrigin(
                  origin, registration->options()->tag)),
          base::BindOnce(&BackgroundSyncManager::ReviveDidGetNextEventDelay,
                         weak_ptr_factory_.GetWeakPtr(),
                         service_worker_registration_ids[registration],
                         *registration, received_new_delays_closure));
    }
  }
}

void BackgroundSyncManager::ReviveDidGetNextEventDelay(
    int64_t service_worker_registration_id,
    BackgroundSyncRegistration registration,
    base::OnceClosure done_closure,
    base::TimeDelta delay) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (delay.is_max()) {
    std::move(done_closure).Run();
    return;
  }

  BackgroundSyncRegistration* active_registration =
      LookupActiveRegistration(blink::mojom::BackgroundSyncRegistrationInfo(
          service_worker_registration_id, registration.options()->tag,
          registration.sync_type()));
  if (!active_registration) {
    std::move(done_closure).Run();
    return;
  }

  active_registration->set_delay_until(clock_->Now() + delay);

  StoreRegistrations(
      service_worker_registration_id,
      base::BindOnce(&BackgroundSyncManager::ReviveDidStoreRegistration,
                     weak_ptr_factory_.GetWeakPtr(),
                     service_worker_registration_id, std::move(done_closure)));
}

void BackgroundSyncManager::ReviveDidStoreRegistration(
    int64_t service_worker_registration_id,
    base::OnceClosure done_closure,
    blink::ServiceWorkerStatusCode status) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status == blink::ServiceWorkerStatusCode::kErrorNotFound) {
    // The service worker registration is gone.
    active_registrations_.erase(service_worker_registration_id);
    std::move(done_closure).Run();
    return;
  }

  if (status != blink::ServiceWorkerStatusCode::kOk) {
    DisableAndClearManager(std::move(done_closure));
    return;
  }

  std::move(done_closure).Run();
}

void BackgroundSyncManager::DidReceiveDelaysForSuspendedRegistrations(
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  ScheduleOrCancelDelayedProcessing(BackgroundSyncType::PERIODIC);
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

void BackgroundSyncManager::ScheduleDelayedProcessingOfRegistrations(
    blink::mojom::BackgroundSyncType sync_type) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  auto fire_events_callback = base::BindOnce(
      &BackgroundSyncManager::FireReadyEvents, weak_ptr_factory_.GetWeakPtr(),
      sync_type, /* reschedule= */ true, base::DoNothing::Once(),
      /* keepalive= */ nullptr);

  proxy_->ScheduleDelayedProcessing(
      sync_type,
      GetSoonestWakeupDelta(sync_type,
                            /* last_browser_wakeup_time= */ base::Time()),
      std::move(fire_events_callback));
}

void BackgroundSyncManager::CancelDelayedProcessingOfRegistrations(
    blink::mojom::BackgroundSyncType sync_type) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  proxy_->CancelDelayedProcessing(sync_type);
}

void BackgroundSyncManager::FireReadyEvents(
    blink::mojom::BackgroundSyncType sync_type,
    bool reschedule,
    base::OnceClosure callback,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (!reschedule) {
    // This invocation has come from scheduled processing of registrations.
    // Since this delayed processing is one-off, update internal state.
    delayed_processing_scheduled(sync_type) = false;
  }

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(&BackgroundSyncManager::FireReadyEventsImpl,
                     weak_ptr_factory_.GetWeakPtr(), sync_type, reschedule, id,
                     std::move(callback), std::move(keepalive)));
}

void BackgroundSyncManager::FireReadyEventsImpl(
    blink::mojom::BackgroundSyncType sync_type,
    bool reschedule,
    int scheduler_id,
    base::OnceClosure callback,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        op_scheduler_.WrapCallbackToRunNext(scheduler_id, std::move(callback)));
    return;
  }

  // Find the registrations that are ready to run.
  std::vector<blink::mojom::BackgroundSyncRegistrationInfoPtr> to_fire;

  for (auto& sw_reg_id_and_registrations : active_registrations_) {
    const int64_t service_worker_registration_id =
        sw_reg_id_and_registrations.first;
    for (auto& key_and_registration :
         sw_reg_id_and_registrations.second.registration_map) {
      BackgroundSyncRegistration* registration = &key_and_registration.second;
      if (sync_type != registration->sync_type())
        continue;

      if (IsRegistrationReadyToFire(*registration,
                                    service_worker_registration_id)) {
        to_fire.emplace_back(blink::mojom::BackgroundSyncRegistrationInfo::New(
            service_worker_registration_id,
            /* tag= */ key_and_registration.first.first,
            /* sync_type= */ key_and_registration.first.second));

        // The state change is not saved to persistent storage because
        // if the sync event is killed mid-sync then it should return to
        // SYNC_STATE_PENDING.
        registration->set_sync_state(blink::mojom::BackgroundSyncState::FIRING);
      }
    }
  }

  if (!reschedule) {
    // This method has been called from a Chrome wakeup task.
    BackgroundSyncMetrics::RecordEventsFiredFromWakeupTask(
        sync_type, /* events_fired= */ !to_fire.empty());
  }

  if (to_fire.empty()) {
    // TODO(crbug.com/996166): Reschedule wakeup after a non-zero delay if
    // called from a wakeup task.
    if (reschedule)
      ScheduleOrCancelDelayedProcessing(sync_type);
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        op_scheduler_.WrapCallbackToRunNext(scheduler_id, std::move(callback)));
    return;
  }

  base::TimeTicks start_time = base::TimeTicks::Now();

  // If we've been called from a wake up task, potentially keep the browser
  // awake till all events have completed. If not, we only do so until all
  // events have been fired.
  // To allow the |op_scheduler_| to process other tasks after sync events
  // have been fired, mark this task complete after firing events.
  base::OnceClosure events_fired_callback, events_completed_callback;
  bool keep_browser_awake_till_events_complete =
      !reschedule && parameters_->keep_browser_awake_till_events_complete;
  if (keep_browser_awake_till_events_complete) {
    events_fired_callback = MakeEmptyCompletion(scheduler_id);
    events_completed_callback = std::move(callback);
  } else {
    events_fired_callback =
        op_scheduler_.WrapCallbackToRunNext(scheduler_id, std::move(callback));
    events_completed_callback = base::DoNothing::Once();
  }

  // Fire the sync event of the ready registrations and run
  // |events_fired_closure| once they're all done.
  base::RepeatingClosure events_fired_barrier_closure = base::BarrierClosure(
      to_fire.size(),
      base::BindOnce(&BackgroundSyncManager::FireReadyEventsAllEventsFiring,
                     weak_ptr_factory_.GetWeakPtr(), sync_type, reschedule,
                     std::move(events_fired_callback)));

  // Record the total time taken after all events have run to completion.
  base::RepeatingClosure events_completed_barrier_closure =
      base::BarrierClosure(
          to_fire.size(),
          base::BindOnce(&BackgroundSyncManager::OnAllSyncEventsCompleted,
                         sync_type, start_time, !reschedule, to_fire.size(),
                         std::move(events_completed_callback)));

  for (auto& registration_info : to_fire) {
    const BackgroundSyncRegistration* registration =
        LookupActiveRegistration(*registration_info);
    DCHECK(registration);

    int64_t service_worker_registration_id =
        registration_info->service_worker_registration_id;
    service_worker_context_->FindReadyRegistrationForId(
        service_worker_registration_id,
        active_registrations_[service_worker_registration_id].origin,
        base::BindOnce(
            &BackgroundSyncManager::FireReadyEventsDidFindRegistration,
            weak_ptr_factory_.GetWeakPtr(), std::move(registration_info),
            std::move(keepalive), events_fired_barrier_closure,
            events_completed_barrier_closure));
  }
}

void BackgroundSyncManager::FireReadyEventsDidFindRegistration(
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive,
    base::OnceClosure event_fired_callback,
    base::OnceClosure event_completed_callback,
    blink::ServiceWorkerStatusCode service_worker_status,
    scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  BackgroundSyncRegistration* registration =
      LookupActiveRegistration(*registration_info);

  if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
    if (registration)
      registration->set_sync_state(blink::mojom::BackgroundSyncState::PENDING);
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, std::move(event_fired_callback));
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, std::move(event_completed_callback));
    return;
  }

  DCHECK_EQ(registration_info->service_worker_registration_id,
            service_worker_registration->id());
  DCHECK(registration);

  // The connectivity was lost before dispatching the sync event, so there is
  // no point in going through with it.
  if (!AreOptionConditionsMet()) {
    registration->set_sync_state(blink::mojom::BackgroundSyncState::PENDING);
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, std::move(event_fired_callback));
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, std::move(event_completed_callback));
    return;
  }

  auto sync_type = registration_info->sync_type;
  UpdateNumFiringRegistrationsBy(sync_type, 1);

  const bool last_chance =
      registration->num_attempts() == registration->max_attempts() - 1;

  HasMainFrameWindowClient(
      url::Origin::Create(service_worker_registration->scope().GetOrigin()),
      base::BindOnce(&BackgroundSyncMetrics::RecordEventStarted, sync_type));

  if (sync_type == BackgroundSyncType::ONE_SHOT) {
    DispatchSyncEvent(
        registration->options()->tag,
        service_worker_registration->active_version(), last_chance,
        base::BindOnce(&BackgroundSyncManager::EventComplete,
                       weak_ptr_factory_.GetWeakPtr(),
                       service_worker_registration,
                       std::move(registration_info), std::move(keepalive),
                       std::move(event_completed_callback)));
  } else {
    DispatchPeriodicSyncEvent(
        registration->options()->tag,
        service_worker_registration->active_version(),
        base::BindOnce(&BackgroundSyncManager::EventComplete,
                       weak_ptr_factory_.GetWeakPtr(),
                       service_worker_registration,
                       std::move(registration_info), std::move(keepalive),
                       std::move(event_completed_callback)));
  }

  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE, std::move(event_fired_callback));
}

void BackgroundSyncManager::FireReadyEventsAllEventsFiring(
    BackgroundSyncType sync_type,
    bool reschedule,
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (reschedule)
    ScheduleOrCancelDelayedProcessing(sync_type);

  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

// |service_worker_registration| is just to keep the registration alive
// while the event is firing.
void BackgroundSyncManager::EventComplete(
    scoped_refptr<ServiceWorkerRegistration> service_worker_registration,
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive,
    base::OnceClosure callback,
    blink::ServiceWorkerStatusCode status_code) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  // The event ran to completion, we should count it, no matter what happens
  // from here.
  url::Origin origin =
      url::Origin::Create(service_worker_registration->scope().GetOrigin());
  HasMainFrameWindowClient(
      origin,
      base::BindOnce(&BackgroundSyncMetrics::RecordEventResult,
                     registration_info->sync_type,
                     status_code == blink::ServiceWorkerStatusCode::kOk));

  auto id = op_scheduler_.CreateId();
  op_scheduler_.ScheduleOperation(
      id, CacheStorageSchedulerMode::kExclusive,
      CacheStorageSchedulerOp::kBackgroundSync,
      CacheStorageSchedulerPriority::kNormal,
      base::BindOnce(
          &BackgroundSyncManager::EventCompleteImpl,
          weak_ptr_factory_.GetWeakPtr(), std::move(registration_info),
          std::move(keepalive), status_code, origin,
          op_scheduler_.WrapCallbackToRunNext(id, std::move(callback))));
}

void BackgroundSyncManager::EventCompleteImpl(
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info,
    std::unique_ptr<BackgroundSyncEventKeepAlive> keepalive,
    blink::ServiceWorkerStatusCode status_code,
    const url::Origin& origin,
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (disabled_) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  BackgroundSyncRegistration* registration =
      LookupActiveRegistration(*registration_info);
  if (!registration) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }
  DCHECK_NE(blink::mojom::BackgroundSyncState::PENDING,
            registration->sync_state());

  // It's important to update |num_attempts| before we update |delay_until|.
  bool succeeded = status_code == blink::ServiceWorkerStatusCode::kOk;
  registration->set_num_attempts(GetNumAttemptsAfterEvent(
      registration->sync_type(), registration->num_attempts(),
      registration->max_attempts(), registration->sync_state(), succeeded));

  // If |delay_until| needs to be updated, get updated delay.
  if (registration->sync_type() == BackgroundSyncType::PERIODIC ||
      (!succeeded &&
       registration->num_attempts() < registration->max_attempts())) {
    // TODO(crbug.com/824858): Remove the else branch after the feature is
    // enabled. Also, try to make a RunOrPostTaskOnThreadAndReplyWithResult()
    // function so the if/else isn't needed.
    if (ServiceWorkerContext::IsServiceWorkerOnUIEnabled()) {
      base::TimeDelta delay = GetNextEventDelay(
          service_worker_context_, *registration,
          std::make_unique<BackgroundSyncParameters>(*parameters_),
          GetSmallestPeriodicSyncEventDelayForOrigin(
              origin, registration->options()->tag));
      EventCompleteDidGetDelay(std::move(registration_info), status_code,
                               origin, std::move(callback), delay);

    } else {
      GetUIThreadTaskRunner({})->PostTaskAndReplyWithResult(
          FROM_HERE,
          base::BindOnce(
              &GetNextEventDelay, service_worker_context_, *registration,
              std::make_unique<BackgroundSyncParameters>(*parameters_),
              GetSmallestPeriodicSyncEventDelayForOrigin(
                  origin, registration->options()->tag)),
          base::BindOnce(&BackgroundSyncManager::EventCompleteDidGetDelay,
                         weak_ptr_factory_.GetWeakPtr(),
                         std::move(registration_info), status_code, origin,
                         std::move(callback)));
    }
    return;
  }

  EventCompleteDidGetDelay(std::move(registration_info), status_code, origin,
                           std::move(callback), base::TimeDelta::Max());
}

void BackgroundSyncManager::EventCompleteDidGetDelay(
    blink::mojom::BackgroundSyncRegistrationInfoPtr registration_info,
    blink::ServiceWorkerStatusCode status_code,
    const url::Origin& origin,
    base::OnceClosure callback,
    base::TimeDelta delay) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  UpdateNumFiringRegistrationsBy(registration_info->sync_type, -1);

  BackgroundSyncRegistration* registration =
      LookupActiveRegistration(*registration_info);
  if (!registration) {
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  bool succeeded = status_code == blink::ServiceWorkerStatusCode::kOk;
  bool can_retry = registration->num_attempts() < registration->max_attempts();

  bool registration_completed = true;
  if (registration->sync_state() ==
      blink::mojom::BackgroundSyncState::REREGISTERED_WHILE_FIRING) {
    registration->set_sync_state(blink::mojom::BackgroundSyncState::PENDING);
    registration->set_num_attempts(0);
    registration_completed = false;
    if (ShouldLogToDevTools(registration->sync_type())) {
      devtools_context_->LogBackgroundServiceEventOnCoreThread(
          registration_info->service_worker_registration_id, origin,
          GetDevToolsBackgroundService(registration->sync_type()),
          /* event_name= */ "Sync event reregistered",
          /* instance_id= */ registration_info->tag,
          /* event_metadata= */ {});
    }
  } else if ((!succeeded && can_retry) ||
             registration->sync_type() == BackgroundSyncType::PERIODIC) {
    registration->set_sync_state(blink::mojom::BackgroundSyncState::PENDING);
    registration_completed = false;
    registration->set_delay_until(clock_->Now() + delay);

    std::string event_name = GetSyncEventName(registration->sync_type()) +
                             (succeeded ? " event completed" : " event failed");
    base::TimeDelta display_delay =
        registration->sync_type() == BackgroundSyncType::ONE_SHOT
            ? delay
            : registration->delay_until() - clock_->Now();
    std::map<std::string, std::string> event_metadata = {
        {"Next Attempt Delay (ms)", GetDelayAsString(display_delay)}};
    if (!succeeded) {
      event_metadata.emplace("Failure Reason",
                             GetEventStatusString(status_code));
    }

    if (ShouldLogToDevTools(registration->sync_type())) {
      devtools_context_->LogBackgroundServiceEventOnCoreThread(
          registration_info->service_worker_registration_id, origin,
          GetDevToolsBackgroundService(registration->sync_type()), event_name,
          /* instance_id= */ registration_info->tag, event_metadata);
    }
  }

  if (registration_completed) {
    BackgroundSyncMetrics::RecordRegistrationComplete(
        succeeded, registration->num_attempts());

    if (ShouldLogToDevTools(registration->sync_type())) {
      devtools_context_->LogBackgroundServiceEventOnCoreThread(
          registration_info->service_worker_registration_id, origin,
          GetDevToolsBackgroundService(registration->sync_type()),
          /* event_name= */ "Sync completed",
          /* instance_id= */ registration_info->tag,
          {{"Status", GetEventStatusString(status_code)}});
    }

    if (registration_info->sync_type ==
        blink::mojom::BackgroundSyncType::ONE_SHOT) {
      RunOrPostTaskOnThread(
          FROM_HERE, BrowserThread::UI,
          base::BindOnce(&NotifyOneShotBackgroundSyncCompletedOnUIThread,
                         service_worker_context_, origin, status_code,
                         registration->num_attempts(),
                         registration->max_attempts()));
    } else {
      RunOrPostTaskOnThread(
          FROM_HERE, BrowserThread::UI,
          base::BindOnce(&NotifyPeriodicBackgroundSyncCompletedOnUIThread,
                         service_worker_context_, origin, status_code,
                         registration->num_attempts(),
                         registration->max_attempts()));
    }

    RemoveActiveRegistration(*registration_info);
  }

  StoreRegistrations(
      registration_info->service_worker_registration_id,
      base::BindOnce(&BackgroundSyncManager::EventCompleteDidStore,
                     weak_ptr_factory_.GetWeakPtr(),
                     registration_info->sync_type,
                     registration_info->service_worker_registration_id,
                     std::move(callback)));
}

void BackgroundSyncManager::EventCompleteDidStore(
    blink::mojom::BackgroundSyncType sync_type,
    int64_t service_worker_id,
    base::OnceClosure callback,
    blink::ServiceWorkerStatusCode status_code) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  if (status_code == blink::ServiceWorkerStatusCode::kErrorNotFound) {
    // The registration is gone.
    active_registrations_.erase(service_worker_id);
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  std::move(callback));
    return;
  }

  if (status_code != blink::ServiceWorkerStatusCode::kOk) {
    DisableAndClearManager(std::move(callback));
    return;
  }

  // Fire any ready events and call RunInBackground if anything is waiting.
  FireReadyEvents(sync_type, /* reschedule= */ true, base::DoNothing::Once());

  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

// static
void BackgroundSyncManager::OnAllSyncEventsCompleted(
    BackgroundSyncType sync_type,
    const base::TimeTicks& start_time,
    bool from_wakeup_task,
    int number_of_batched_sync_events,
    base::OnceClosure callback) {
  // Record the combined time taken by all sync events.
  BackgroundSyncMetrics::RecordBatchSyncEventComplete(
      sync_type, base::TimeTicks::Now() - start_time, from_wakeup_task,
      number_of_batched_sync_events);
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

void BackgroundSyncManager::OnRegistrationDeletedImpl(
    int64_t sw_registration_id,
    base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  // The backend (ServiceWorkerStorage) will delete the data, so just delete the
  // memory representation here.
  active_registrations_.erase(sw_registration_id);
  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
}

void BackgroundSyncManager::OnStorageWipedImpl(base::OnceClosure callback) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

  active_registrations_.clear();
  disabled_ = false;
  InitImpl(std::move(callback));
}

void BackgroundSyncManager::OnNetworkChanged() {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());

#if defined(OS_ANDROID)
  if (parameters_->rely_on_android_network_detection)
    return;
#endif

  if (!AreOptionConditionsMet())
    return;

  FireReadyEvents(BackgroundSyncType::ONE_SHOT, /* reschedule= */ true,
                  base::DoNothing::Once());
  FireReadyEvents(BackgroundSyncType::PERIODIC, /* reschedule= */ true,
                  base::DoNothing::Once());
}

base::OnceClosure BackgroundSyncManager::MakeEmptyCompletion(
    CacheStorageSchedulerId id) {
  DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
  return op_scheduler_.WrapCallbackToRunNext(id, base::DoNothing::Once());
}

blink::ServiceWorkerStatusCode BackgroundSyncManager::CanEmulateSyncEvent(
    scoped_refptr<ServiceWorkerVersion> active_version) {
  if (!active_version)
    return blink::ServiceWorkerStatusCode::kErrorAbort;
  if (!network_observer_->NetworkSufficient())
    return blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected;
  int64_t registration_id = active_version->registration_id();
  if (base::Contains(emulated_offline_sw_, registration_id))
    return blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected;
  return blink::ServiceWorkerStatusCode::kOk;
}

bool BackgroundSyncManager::ShouldLogToDevTools(BackgroundSyncType sync_type) {
  return devtools_context_->IsRecording(
      GetDevToolsBackgroundService(sync_type));
}

}  // namespace content
