910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/background_fetch/background_fetch_scheduler.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/background_fetch/background_fetch_data_manager.h"
#include "content/browser/background_fetch/background_fetch_delegate_proxy.h"
#include "content/browser/background_fetch/background_fetch_job_controller.h"
#include "content/browser/background_fetch/background_fetch_registration_notifier.h"
#include "content/browser/background_fetch/background_fetch_registration_service_impl.h"
#include "content/browser/devtools/devtools_background_services_context_impl.h"
#include "content/browser/service_worker/service_worker_context_core_observer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom.h"

namespace content {

namespace {

// The maximum number of active registrations that can be processed
// concurrently. The active registrations are from distinct origins.
constexpr char kMaxActiveRegistrations[] = "max_active_registrations";
constexpr int kMaxActiveRegistrationsDefaultValue = 2;

// The maximum number of downloads the Download Service can process at the same
// time.
// TODO(crbug.com/40608586): Figure out how to keep this in sync with the
// Download Service value.
constexpr char kMaxRunningDownloads[] = "max_running_downloads";
constexpr int kMaxRunningDownloadsDefaultValue = 2;

}  // namespace

using blink::mojom::BackgroundFetchError;
using blink::mojom::BackgroundFetchFailureReason;

// The major stages/events a Background Fetch instance goes through via the
// BackgroundFetchScheduler.
enum class BackgroundFetchScheduler::Event {
  // The Background Fetch was successfully registered.
  kFetchRegistered,
  // The Background Fetch registration was loaded on start-up.
  kFetchResumedOnStartup,
  // The scheduler marked the registration as active.
  kFetchScheduled,
  // A request within the registration is being fetched.
  kRequestStarted,
  // A request within the registration had been fetched.
  kRequestCompleted,
};

BackgroundFetchScheduler::BackgroundFetchScheduler(
    BackgroundFetchContext* background_fetch_context,
    BackgroundFetchDataManager* data_manager,
    BackgroundFetchRegistrationNotifier* registration_notifier,
    BackgroundFetchDelegateProxy* delegate_proxy,
    DevToolsBackgroundServicesContextImpl& devtools_context,
    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
    : data_manager_(data_manager),
      registration_notifier_(registration_notifier),
      delegate_proxy_(delegate_proxy),
      devtools_context_(&devtools_context),
      event_dispatcher_(background_fetch_context,
                        std::move(service_worker_context),
                        devtools_context) {
  DCHECK(delegate_proxy_);
  delegate_proxy_->SetClickEventDispatcher(
      base::BindRepeating(&BackgroundFetchScheduler::DispatchClickEvent,
                          weak_ptr_factory_.GetWeakPtr()));

  max_active_registrations_ = base::GetFieldTrialParamByFeatureAsInt(
      features::kBackgroundFetch, kMaxActiveRegistrations,
      kMaxActiveRegistrationsDefaultValue);
  max_running_downloads_ = base::GetFieldTrialParamByFeatureAsInt(
      features::kBackgroundFetch, kMaxRunningDownloads,
      kMaxRunningDownloadsDefaultValue);
}

BackgroundFetchScheduler::~BackgroundFetchScheduler() = default;

BackgroundFetchScheduler::RegistrationData::RegistrationData(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationDataPtr registration)
    : registration_id(registration_id), registration(std::move(registration)) {}

BackgroundFetchScheduler::RegistrationData::~RegistrationData() = default;

bool BackgroundFetchScheduler::ScheduleDownload() {
  DCHECK_LT(num_running_downloads_, max_running_downloads_);

  // 1. Try to activate a registration from a different storage key.
  if (num_active_registrations_ < max_active_registrations_ &&
      !controller_ids_.empty()) {
    // Try to find a pending registration with a different storage key.
    for (const auto& controller_id : controller_ids_) {
      // Make sure the storage key is not already active.
      bool is_new_storage_key = true;
      for (auto* controller : active_controllers_) {
        if (controller->registration_id().storage_key() ==
            controller_id.storage_key()) {
          is_new_storage_key = false;
          break;
        }
      }

      if (is_new_storage_key) {
        // Start new registration, and move to the front of the queue.
        auto* controller = job_controllers_[controller_id.unique_id()].get();
        active_controllers_.push_front(controller);
        ++num_active_registrations_;

        LogBackgroundFetchEventForDevTools(Event::kFetchScheduled,
                                           controller_id,
                                           /* request_info= */ nullptr);

        base::Erase(controller_ids_, controller_id);
        break;
      }
    }
  }

  // 2. Try to start a request within the LRU registration.
  for (auto it = active_controllers_.begin(); it != active_controllers_.end();
       ++it) {
    auto* controller = *it;
    if (!controller->HasMoreRequests())
      continue;

    // Activate a request within |controller| and move it to the end of the
    // queue.
    ++num_running_downloads_;
    controller->PopNextRequest(
        base::BindOnce(&BackgroundFetchScheduler::DidStartRequest,
                       weak_ptr_factory_.GetWeakPtr()),
        base::BindOnce(&BackgroundFetchScheduler::DidCompleteRequest,
                       weak_ptr_factory_.GetWeakPtr()));

    active_controllers_.erase(it);
    active_controllers_.push_back(controller);
    return true;
  }
  return false;
}

void BackgroundFetchScheduler::Abort(
    const BackgroundFetchRegistrationId& registration_id,
    BackgroundFetchFailureReason failure_reason,
    blink::mojom::BackgroundFetchRegistrationService::AbortCallback callback) {
  DCHECK_EQ(failure_reason,
            BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER);

  base::Erase(controller_ids_, registration_id);

  auto it = job_controllers_.find(registration_id.unique_id());
  if (it == job_controllers_.end()) {
    std::move(callback).Run(BackgroundFetchError::INVALID_ID);
    return;
  }

  it->second->Abort(failure_reason, std::move(callback));
}

void BackgroundFetchScheduler::DidStartRequest(
    const BackgroundFetchRegistrationId& registration_id,
    const BackgroundFetchRequestInfo* request_info) {
  LogBackgroundFetchEventForDevTools(Event::kRequestStarted, registration_id,
                                     request_info);
}

void BackgroundFetchScheduler::DidCompleteRequest(
    const BackgroundFetchRegistrationId& registration_id,
    scoped_refptr<BackgroundFetchRequestInfo> request_info) {
  LogBackgroundFetchEventForDevTools(Event::kRequestCompleted, registration_id,
                                     request_info.get());

  auto* controller = GetActiveController(registration_id);
  if (controller)
    controller->MarkRequestAsComplete(std::move(request_info));

  --num_running_downloads_;
  if (num_running_downloads_ < max_running_downloads_)
    ScheduleDownload();
}

void BackgroundFetchScheduler::FinishJob(
    const BackgroundFetchRegistrationId& registration_id,
    BackgroundFetchFailureReason failure_reason,
    base::OnceCallback<void(BackgroundFetchError)> callback) {
  auto* active_controller = GetActiveController(registration_id);
  if (active_controller) {
    base::EraseIf(active_controllers_, [&registration_id](auto* controller) {
      return controller->registration_id() == registration_id;
    });
  }

  data_manager_->MarkRegistrationForDeletion(
      registration_id,
      /* check_for_failure= */ failure_reason ==
          BackgroundFetchFailureReason::NONE,
      base::BindOnce(&BackgroundFetchScheduler::DidMarkForDeletion,
                     weak_ptr_factory_.GetWeakPtr(), registration_id,
                     /* job_started= */ active_controller != nullptr,
                     std::move(callback)));

  auto it = job_controllers_.find(registration_id.unique_id());
  if (it != job_controllers_.end()) {
    completed_fetches_[it->first] = std::make_unique<RegistrationData>(
        registration_id, it->second->NewRegistrationData());

    // Reset scheduler params.
    num_running_downloads_ -= it->second->pending_downloads();
    --num_active_registrations_;

    // Destroying the controller will stop all in progress tasks.
    job_controllers_.erase(it);
  }

  if (num_running_downloads_ < max_running_downloads_)
    ScheduleDownload();
}

void BackgroundFetchScheduler::DidMarkForDeletion(
    const BackgroundFetchRegistrationId& registration_id,
    bool job_started,
    base::OnceCallback<void(BackgroundFetchError)> callback,
    BackgroundFetchError error,
    BackgroundFetchFailureReason failure_reason) {
  DCHECK(callback);
  std::move(callback).Run(error);

  // It's normal to get INVALID_ID errors here - it means the registration was
  // already inactive (marked for deletion). This happens when an abort (from
  // developer or from user) races with the download completing/failing, or even
  // when two aborts race.
  if (error != BackgroundFetchError::NONE)
    return;

  auto it = completed_fetches_.find(registration_id.unique_id());
  CHECK(it != completed_fetches_.end());

  blink::mojom::BackgroundFetchRegistrationDataPtr& registration_data =
      it->second->registration;
  // Include any other failure reasons the marking for deletion may have found.
  if (registration_data->failure_reason == BackgroundFetchFailureReason::NONE)
    registration_data->failure_reason = failure_reason;

  registration_data->result =
      registration_data->failure_reason == BackgroundFetchFailureReason::NONE
          ? blink::mojom::BackgroundFetchResult::SUCCESS
          : blink::mojom::BackgroundFetchResult::FAILURE;

  registration_notifier_->Notify(registration_id.unique_id(),
                                 *registration_data);

  event_dispatcher_.DispatchBackgroundFetchCompletionEvent(
      registration_id, registration_data.Clone(),
      base::BindOnce(&BackgroundFetchScheduler::CleanupRegistration,
                     weak_ptr_factory_.GetWeakPtr(), registration_id));

  if (!job_started ||
      registration_data->failure_reason ==
          BackgroundFetchFailureReason::CANCELLED_FROM_UI ||
      registration_data->failure_reason ==
          BackgroundFetchFailureReason::CANCELLED_BY_DEVELOPER) {
    // No need to keep the controller around since there won't be dispatch
    // events.
    completed_fetches_.erase(it);
  } else {
    // The registration is now safe to delete.
    it->second->processing_completed = true;
  }
}

void BackgroundFetchScheduler::CleanupRegistration(
    const BackgroundFetchRegistrationId& registration_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  // Indicate to the renderer that the records for this fetch are no longer
  // available.
  registration_notifier_->NotifyRecordsUnavailable(registration_id.unique_id());

  // Delete the data associated with this fetch. Cache storage will keep the
  // downloaded data around so long as there are references to it, and delete
  // it once there is none. We don't need to do that accounting.
  data_manager_->DeleteRegistration(registration_id, base::DoNothing());

  // Notify other systems that this registration is complete.
  delegate_proxy_->MarkJobComplete(registration_id.unique_id());
}

void BackgroundFetchScheduler::DispatchClickEvent(
    const std::string& unique_id) {
  // Case 1: The active fetch received a click event.
  auto* active_controller = GetActiveController(unique_id);

  if (active_controller) {
    event_dispatcher_.DispatchBackgroundFetchClickEvent(
        active_controller->registration_id(),
        active_controller->NewRegistrationData(), base::DoNothing());
    return;
  }

  // Case 2: A completed fetch received a click event.
  auto it = completed_fetches_.find(unique_id);
  if (it == completed_fetches_.end())
    return;

  event_dispatcher_.DispatchBackgroundFetchClickEvent(
      it->second->registration_id, it->second->registration.Clone(),
      base::DoNothing());

  if (it->second->processing_completed)
    completed_fetches_.erase(unique_id);
}

std::unique_ptr<BackgroundFetchJobController>
BackgroundFetchScheduler::CreateInitializedController(
    const BackgroundFetchRegistrationId& registration_id,
    const blink::mojom::BackgroundFetchRegistrationData& registration_data,
    blink::mojom::BackgroundFetchOptionsPtr options,
    const SkBitmap& icon,
    int num_completed_requests,
    int num_requests,
    std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
        active_fetch_requests,
    bool start_paused,
    std::optional<net::IsolationInfo> isolation_info) {
  // TODO(rayankans): Only create a controller when the fetch starts.
  auto controller = std::make_unique<BackgroundFetchJobController>(
      data_manager_, delegate_proxy_, registration_id, std::move(options), icon,
      registration_data.downloaded, registration_data.uploaded,
      registration_data.upload_total,
      // Safe because JobControllers are destroyed before RegistrationNotifier.
      base::BindRepeating(&BackgroundFetchRegistrationNotifier::Notify,
                          base::Unretained(registration_notifier_)),
      base::BindOnce(&BackgroundFetchScheduler::FinishJob,
                     weak_ptr_factory_.GetWeakPtr()));

  controller->InitializeRequestStatus(num_completed_requests, num_requests,
                                      std::move(active_fetch_requests),
                                      start_paused, std::move(isolation_info));

  return controller;
}

void BackgroundFetchScheduler::OnRegistrationCreated(
    const BackgroundFetchRegistrationId& registration_id,
    const blink::mojom::BackgroundFetchRegistrationData& registration_data,
    blink::mojom::BackgroundFetchOptionsPtr options,
    const SkBitmap& icon,
    int num_requests,
    bool start_paused,
    net::IsolationInfo isolation_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  LogBackgroundFetchEventForDevTools(
      Event::kFetchRegistered, registration_id,
      /* request_info= */ nullptr,
      {{"Total Requests", base::NumberToString(num_requests)},
       {"Start Paused", start_paused ? "Yes" : "No"}});

  auto controller = CreateInitializedController(
      registration_id, registration_data, std::move(options), icon,
      /* completed_requests= */ 0, num_requests,
      /* active_fetch_requests= */ {}, start_paused, std::move(isolation_info));

  DCHECK_EQ(job_controllers_.count(registration_id.unique_id()), 0u);
  job_controllers_[registration_id.unique_id()] = std::move(controller);
  controller_ids_.push_back(registration_id);

  // Schedule as much as possible.
  while (num_running_downloads_ < max_running_downloads_) {
    if (!ScheduleDownload())
      return;
  }
}

void BackgroundFetchScheduler::OnRegistrationLoadedAtStartup(
    const BackgroundFetchRegistrationId& registration_id,
    const blink::mojom::BackgroundFetchRegistrationData& registration_data,
    blink::mojom::BackgroundFetchOptionsPtr options,
    const SkBitmap& icon,
    int num_completed_requests,
    int num_requests,
    std::vector<scoped_refptr<BackgroundFetchRequestInfo>>
        active_fetch_requests,
    std::optional<net::IsolationInfo> isolation_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  LogBackgroundFetchEventForDevTools(
      Event::kFetchResumedOnStartup, registration_id,
      /* request_info= */ nullptr,
      {{"Completed Requests", base::NumberToString(num_completed_requests)},
       {"Active Requests",
        base::NumberToString(active_fetch_requests.size())}});

  auto controller = CreateInitializedController(
      registration_id, registration_data, std::move(options), icon,
      num_completed_requests, num_requests, active_fetch_requests,
      /* start_paused= */ false, std::move(isolation_info));

  auto* controller_ptr = controller.get();
  active_controllers_.push_back(controller_ptr);
  job_controllers_[registration_id.unique_id()] = std::move(controller);

  ++num_active_registrations_;
  num_running_downloads_ += active_fetch_requests.size();

  if (active_fetch_requests.empty()) {
    DCHECK_LT(num_completed_requests, num_requests);
    // Start processing the next request.
    ++num_running_downloads_;
    controller_ptr->PopNextRequest(
        base::BindOnce(&BackgroundFetchScheduler::DidStartRequest,
                       weak_ptr_factory_.GetWeakPtr()),
        base::BindOnce(&BackgroundFetchScheduler::DidCompleteRequest,
                       weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  for (auto& request_info : active_fetch_requests) {
    controller_ptr->StartRequest(
        std::move(request_info),
        base::BindOnce(&BackgroundFetchScheduler::DidCompleteRequest,
                       weak_ptr_factory_.GetWeakPtr()));
  }
}

void BackgroundFetchScheduler::OnRequestCompleted(
    const std::string& unique_id,
    blink::mojom::FetchAPIRequestPtr request,
    blink::mojom::FetchAPIResponsePtr response) {
  registration_notifier_->NotifyRequestCompleted(unique_id, std::move(request),
                                                 std::move(response));
}

void BackgroundFetchScheduler::AbortFetches(
    int64_t service_worker_registration_id) {
  // Abandon all active associated with this service worker.
  // BackgroundFetchJobController::Abort() will eventually lead to deletion of
  // the controller from job_controllers, so the IDs need to be copied over.
  std::vector<BackgroundFetchJobController*> to_abort;
  for (const auto& controller : job_controllers_) {
    if (service_worker_registration_id !=
            blink::mojom::kInvalidServiceWorkerRegistrationId &&
        service_worker_registration_id !=
            controller.second->registration_id()
                .service_worker_registration_id()) {
      continue;
    }
    to_abort.push_back(controller.second.get());
  }

  for (auto* controller : to_abort) {
    // Erase it from |controller_ids_| first to avoid rescheduling.
    base::Erase(controller_ids_, controller->registration_id());
    controller->Abort(BackgroundFetchFailureReason::SERVICE_WORKER_UNAVAILABLE,
                      base::DoNothing());
  }
}

void BackgroundFetchScheduler::OnRegistrationQueried(
    const BackgroundFetchRegistrationId& registration_id,
    blink::mojom::BackgroundFetchRegistrationData* registration_data) {
  DCHECK(registration_data);

  auto* controller = GetActiveController(registration_id.unique_id());
  if (!controller)
    return;

  // The data manager only has the number of bytes from completed downloads, so
  // augment this with the number of downloaded/uploaded bytes from in-progress
  // jobs.
  registration_data->downloaded += controller->GetInProgressDownloadedBytes();
  registration_data->uploaded += controller->GetInProgressUploadedBytes();
}

void BackgroundFetchScheduler::OnServiceWorkerDatabaseCorrupted(
    int64_t service_worker_registration_id) {
  AbortFetches(service_worker_registration_id);
}

void BackgroundFetchScheduler::OnRegistrationDeleted(
    int64_t registration_id,
    const GURL& pattern,
    const blink::StorageKey& key) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  AbortFetches(registration_id);
}

void BackgroundFetchScheduler::OnStorageWiped() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  AbortFetches(blink::mojom::kInvalidServiceWorkerRegistrationId);
}

BackgroundFetchJobController* BackgroundFetchScheduler::GetActiveController(
    const BackgroundFetchRegistrationId& registration_id) {
  for (auto* controller : active_controllers_) {
    if (controller->registration_id() == registration_id)
      return controller;
  }
  return nullptr;
}

BackgroundFetchJobController* BackgroundFetchScheduler::GetActiveController(
    const std::string& unique_id) {
  // |unique_id| is used for all BackgroundFetchRegistrationId comparisons, so
  // this creates a |unique_id| wrapper with default values for other
  // parameters.
  BackgroundFetchRegistrationId registration_id(
      /* service_worker_registration_id= */ 0,
      /* storage_key= */ blink::StorageKey(),
      /* developer_id= */ "", unique_id);
  return GetActiveController(registration_id);
}

void BackgroundFetchScheduler::LogBackgroundFetchEventForDevTools(
    Event event,
    const BackgroundFetchRegistrationId& registration_id,
    const BackgroundFetchRequestInfo* request_info,
    std::map<std::string, std::string> metadata) {
  CHECK(devtools_context_);
  if (!devtools_context_->IsRecording(
          DevToolsBackgroundService::kBackgroundFetch)) {
    return;
  }

  std::string event_name;

  // Fill with the appropriate event description in |event_name|, and append
  // any additional data to |metadata|.
  switch (event) {
    case Event::kFetchRegistered:
      event_name = "Background Fetch registered";
      break;
    case Event::kFetchResumedOnStartup:
      event_name = "Background Fetch resuming after browser restart";
      break;
    case Event::kFetchScheduled:
      event_name = "Background Fetch started";
      break;
    case Event::kRequestStarted:
      event_name = "Request processing started";
      DCHECK(request_info);
      break;
    case Event::kRequestCompleted:
      event_name = "Request processing completed";
      DCHECK(request_info);
      metadata["Response Status"] =
          base::NumberToString(request_info->GetResponseCode());
      metadata["Response Size (bytes)"] =
          base::NumberToString(request_info->GetResponseSize());
      break;
  }

  DCHECK(!event_name.empty());

  // Include common request metadata.
  if (request_info) {
    metadata["URL"] = request_info->fetch_request()->url.spec();
    metadata["Request Index"] =
        base::NumberToString(request_info->request_index());
    if (request_info->request_body_size())
      metadata["Upload Size (bytes)"] =
          base::NumberToString(request_info->request_body_size());
  }

  devtools_context_->LogBackgroundServiceEvent(
      registration_id.service_worker_registration_id(),
      registration_id.storage_key(),
      DevToolsBackgroundService::kBackgroundFetch, std::move(event_name),
      /* instance_id= */ registration_id.developer_id(), metadata);
}

void BackgroundFetchScheduler::Shutdown() {
  event_dispatcher_.Shutdown();
  devtools_context_ = nullptr;
}

}  // namespace content