910e62b5创建于 1月15日历史提交
// Copyright 2023 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/devtools/protocol/preload_handler.h"

#include <memory>
#include <utility>

#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_preload_storage.h"
#include "content/browser/devtools/protocol/preload.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/preloading/prefetch/prefetch_service.h"
#include "content/browser/preloading/preloading.h"
#include "content/browser/preloading/preloading_config.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/prefetch_service_delegate.h"

namespace content::protocol {

namespace {

Preload::PrerenderFinalStatus PrerenderFinalStatusToProtocol(
    PrerenderFinalStatus feature) {
  switch (feature) {
    case PrerenderFinalStatus::kActivated:
      return Preload::PrerenderFinalStatusEnum::Activated;
    case PrerenderFinalStatus::kBlockedByClient:
      return Preload::PrerenderFinalStatusEnum::BlockedByClient;
    case PrerenderFinalStatus::kCancelAllHostsForTesting:
      return Preload::PrerenderFinalStatusEnum::CancelAllHostsForTesting;
    case PrerenderFinalStatus::kClientCertRequested:
      return Preload::PrerenderFinalStatusEnum::ClientCertRequested;
    case PrerenderFinalStatus::kDataSaverEnabled:
      return Preload::PrerenderFinalStatusEnum::DataSaverEnabled;
    case PrerenderFinalStatus::kDestroyed:
      return Preload::PrerenderFinalStatusEnum::Destroyed;
    case PrerenderFinalStatus::kDidFailLoad:
      return Preload::PrerenderFinalStatusEnum::DidFailLoad;
    case PrerenderFinalStatus::kDownload:
      return Preload::PrerenderFinalStatusEnum::Download;
    case PrerenderFinalStatus::kInvalidSchemeNavigation:
      return Preload::PrerenderFinalStatusEnum::InvalidSchemeNavigation;
    case PrerenderFinalStatus::kInvalidSchemeRedirect:
      return Preload::PrerenderFinalStatusEnum::InvalidSchemeRedirect;
    case PrerenderFinalStatus::kLoginAuthRequested:
      return Preload::PrerenderFinalStatusEnum::LoginAuthRequested;
    case PrerenderFinalStatus::kLowEndDevice:
      return Preload::PrerenderFinalStatusEnum::LowEndDevice;
    case PrerenderFinalStatus::kMemoryLimitExceeded:
      return Preload::PrerenderFinalStatusEnum::MemoryLimitExceeded;
    case PrerenderFinalStatus::kMixedContent:
      return Preload::PrerenderFinalStatusEnum::MixedContent;
    case PrerenderFinalStatus::kMojoBinderPolicy:
      return Preload::PrerenderFinalStatusEnum::MojoBinderPolicy;
    case PrerenderFinalStatus::kNavigationBadHttpStatus:
      return Preload::PrerenderFinalStatusEnum::NavigationBadHttpStatus;
    case PrerenderFinalStatus::kNavigationNotCommitted:
      return Preload::PrerenderFinalStatusEnum::NavigationNotCommitted;
    case PrerenderFinalStatus::kNavigationRequestBlockedByCsp:
      return Preload::PrerenderFinalStatusEnum::NavigationRequestBlockedByCsp;
    case PrerenderFinalStatus::kNavigationRequestNetworkError:
      return Preload::PrerenderFinalStatusEnum::NavigationRequestNetworkError;
    case PrerenderFinalStatus::kRendererProcessCrashed:
      return Preload::PrerenderFinalStatusEnum::RendererProcessCrashed;
    case PrerenderFinalStatus::kRendererProcessKilled:
      return Preload::PrerenderFinalStatusEnum::RendererProcessKilled;
    case PrerenderFinalStatus::kSslCertificateError:
      return Preload::PrerenderFinalStatusEnum::SslCertificateError;
    case PrerenderFinalStatus::kStop:
      return Preload::PrerenderFinalStatusEnum::Stop;
    case PrerenderFinalStatus::kTriggerBackgrounded:
      return Preload::PrerenderFinalStatusEnum::TriggerBackgrounded;
    case PrerenderFinalStatus::kTriggerDestroyed:
      return Preload::PrerenderFinalStatusEnum::TriggerDestroyed;
    case PrerenderFinalStatus::kUaChangeRequiresReload:
      return Preload::PrerenderFinalStatusEnum::UaChangeRequiresReload;
    case PrerenderFinalStatus::kTriggerUrlHasEffectiveUrl:
      return Preload::PrerenderFinalStatusEnum::TriggerUrlHasEffectiveUrl;
    case PrerenderFinalStatus::kActivatedBeforeStarted:
      return Preload::PrerenderFinalStatusEnum::ActivatedBeforeStarted;
    case PrerenderFinalStatus::kInactivePageRestriction:
      return Preload::PrerenderFinalStatusEnum::InactivePageRestriction;
    case PrerenderFinalStatus::kStartFailed:
      return Preload::PrerenderFinalStatusEnum::StartFailed;
    case PrerenderFinalStatus::kTimeoutBackgrounded:
      return Preload::PrerenderFinalStatusEnum::TimeoutBackgrounded;
    case PrerenderFinalStatus::kCrossSiteRedirectInInitialNavigation:
      return Preload::PrerenderFinalStatusEnum::
          CrossSiteRedirectInInitialNavigation;
    case PrerenderFinalStatus::kCrossSiteNavigationInInitialNavigation:
      return Preload::PrerenderFinalStatusEnum::
          CrossSiteNavigationInInitialNavigation;
    case PrerenderFinalStatus::
        kSameSiteCrossOriginRedirectNotOptInInInitialNavigation:
      return Preload::PrerenderFinalStatusEnum::
          SameSiteCrossOriginRedirectNotOptInInInitialNavigation;
    case PrerenderFinalStatus::
        kSameSiteCrossOriginNavigationNotOptInInInitialNavigation:
      return Preload::PrerenderFinalStatusEnum::
          SameSiteCrossOriginNavigationNotOptInInInitialNavigation;
    case PrerenderFinalStatus::kActivationNavigationParameterMismatch:
      return Preload::PrerenderFinalStatusEnum::
          ActivationNavigationParameterMismatch;
    case PrerenderFinalStatus::kActivatedInBackground:
      return Preload::PrerenderFinalStatusEnum::ActivatedInBackground;
    case PrerenderFinalStatus::kActivationNavigationDestroyedBeforeSuccess:
      return Preload::PrerenderFinalStatusEnum::
          ActivationNavigationDestroyedBeforeSuccess;
    case PrerenderFinalStatus::kTabClosedByUserGesture:
      return Preload::PrerenderFinalStatusEnum::TabClosedByUserGesture;
    case PrerenderFinalStatus::kTabClosedWithoutUserGesture:
      return Preload::PrerenderFinalStatusEnum::TabClosedWithoutUserGesture;
    case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessCrashed:
      return Preload::PrerenderFinalStatusEnum::
          PrimaryMainFrameRendererProcessCrashed;
    case PrerenderFinalStatus::kPrimaryMainFrameRendererProcessKilled:
      return Preload::PrerenderFinalStatusEnum::
          PrimaryMainFrameRendererProcessKilled;
    case PrerenderFinalStatus::kActivationFramePolicyNotCompatible:
      return Preload::PrerenderFinalStatusEnum::
          ActivationFramePolicyNotCompatible;
    case PrerenderFinalStatus::kPreloadingDisabled:
      return Preload::PrerenderFinalStatusEnum::PreloadingDisabled;
    case PrerenderFinalStatus::kBatterySaverEnabled:
      return Preload::PrerenderFinalStatusEnum::BatterySaverEnabled;
    case PrerenderFinalStatus::kActivatedDuringMainFrameNavigation:
      return Preload::PrerenderFinalStatusEnum::
          ActivatedDuringMainFrameNavigation;
    case PrerenderFinalStatus::kPreloadingUnsupportedByWebContents:
      return Preload::PrerenderFinalStatusEnum::
          PreloadingUnsupportedByWebContents;
    case PrerenderFinalStatus::kCrossSiteRedirectInMainFrameNavigation:
      return Preload::PrerenderFinalStatusEnum::
          CrossSiteRedirectInMainFrameNavigation;
    case PrerenderFinalStatus::kCrossSiteNavigationInMainFrameNavigation:
      return Preload::PrerenderFinalStatusEnum::
          CrossSiteNavigationInMainFrameNavigation;
    case PrerenderFinalStatus::
        kSameSiteCrossOriginRedirectNotOptInInMainFrameNavigation:
      return Preload::PrerenderFinalStatusEnum::
          SameSiteCrossOriginRedirectNotOptInInMainFrameNavigation;
    case PrerenderFinalStatus::
        kSameSiteCrossOriginNavigationNotOptInInMainFrameNavigation:
      return Preload::PrerenderFinalStatusEnum::
          SameSiteCrossOriginNavigationNotOptInInMainFrameNavigation;
    case PrerenderFinalStatus::kMemoryPressureOnTrigger:
      return Preload::PrerenderFinalStatusEnum::MemoryPressureOnTrigger;
    case PrerenderFinalStatus::kMemoryPressureAfterTriggered:
      return Preload::PrerenderFinalStatusEnum::MemoryPressureAfterTriggered;
    case PrerenderFinalStatus::kPrerenderingDisabledByDevTools:
      return Preload::PrerenderFinalStatusEnum::PrerenderingDisabledByDevTools;
    case PrerenderFinalStatus::kSpeculationRuleRemoved:
      return Preload::PrerenderFinalStatusEnum::SpeculationRuleRemoved;
    case PrerenderFinalStatus::kActivatedWithAuxiliaryBrowsingContexts:
      return Preload::PrerenderFinalStatusEnum::
          ActivatedWithAuxiliaryBrowsingContexts;
    case PrerenderFinalStatus::kMaxNumOfRunningImmediatePrerendersExceeded:
      return Preload::PrerenderFinalStatusEnum::
          MaxNumOfRunningEagerPrerendersExceeded;
    case PrerenderFinalStatus::kMaxNumOfRunningNonImmediatePrerendersExceeded:
      return Preload::PrerenderFinalStatusEnum::
          MaxNumOfRunningNonEagerPrerendersExceeded;
    case PrerenderFinalStatus::kMaxNumOfRunningEmbedderPrerendersExceeded:
      return Preload::PrerenderFinalStatusEnum::
          MaxNumOfRunningEmbedderPrerendersExceeded;
    case PrerenderFinalStatus::kPrerenderingUrlHasEffectiveUrl:
      return Preload::PrerenderFinalStatusEnum::PrerenderingUrlHasEffectiveUrl;
    case PrerenderFinalStatus::kRedirectedPrerenderingUrlHasEffectiveUrl:
      return Preload::PrerenderFinalStatusEnum::
          RedirectedPrerenderingUrlHasEffectiveUrl;
    case PrerenderFinalStatus::kActivationUrlHasEffectiveUrl:
      return Preload::PrerenderFinalStatusEnum::ActivationUrlHasEffectiveUrl;
    case PrerenderFinalStatus::kJavaScriptInterfaceAdded:
      return Preload::PrerenderFinalStatusEnum::JavaScriptInterfaceAdded;
    case PrerenderFinalStatus::kJavaScriptInterfaceRemoved:
      return Preload::PrerenderFinalStatusEnum::JavaScriptInterfaceRemoved;
    case PrerenderFinalStatus::kAllPrerenderingCanceled:
      return Preload::PrerenderFinalStatusEnum::AllPrerenderingCanceled;
    case PrerenderFinalStatus::kWindowClosed:
      return Preload::PrerenderFinalStatusEnum::WindowClosed;
    case PrerenderFinalStatus::kSlowNetwork:
      return Preload::PrerenderFinalStatusEnum::SlowNetwork;
    case PrerenderFinalStatus::kOtherPrerenderedPageActivated:
      return Preload::PrerenderFinalStatusEnum::OtherPrerenderedPageActivated;
    case PrerenderFinalStatus::kPrerenderFailedDuringPrefetch:
      return Preload::PrerenderFinalStatusEnum::PrerenderFailedDuringPrefetch;
    case PrerenderFinalStatus::kBrowsingDataRemoved:
      return Preload::PrerenderFinalStatusEnum::BrowsingDataRemoved;
    case PrerenderFinalStatus::kPrerenderHostReused:
      return Preload::PrerenderFinalStatusEnum::PrerenderHostReused;
  }
}

Preload::PreloadingStatus PreloadingTriggeringOutcomeToProtocol(
    PreloadingTriggeringOutcome feature) {
  switch (feature) {
    case PreloadingTriggeringOutcome::kRunning:
      return Preload::PreloadingStatusEnum::Running;
    case PreloadingTriggeringOutcome::kReady:
      return Preload::PreloadingStatusEnum::Ready;
    case PreloadingTriggeringOutcome::kSuccess:
      return Preload::PreloadingStatusEnum::Success;
    case PreloadingTriggeringOutcome::kFailure:
      return Preload::PreloadingStatusEnum::Failure;
    case PreloadingTriggeringOutcome::kTriggeredButPending:
      return Preload::PreloadingStatusEnum::Pending;
    case PreloadingTriggeringOutcome::kUnspecified:
    case PreloadingTriggeringOutcome::kDuplicate:
    case PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown:
    case PreloadingTriggeringOutcome::kTriggeredButUpgradedToPrerender:
    case PreloadingTriggeringOutcome::kNoOp:
      return Preload::PreloadingStatusEnum::NotSupported;
  }
}

Preload::PrefetchStatus PrefetchStatusToProtocol(PrefetchStatus status) {
  switch (status) {
    case PrefetchStatus::kPrefetchNotUsedProbeFailed:
      return Preload::PrefetchStatusEnum::PrefetchNotUsedProbeFailed;
    case PrefetchStatus::kPrefetchNotStarted:
      return Preload::PrefetchStatusEnum::PrefetchNotStarted;
    case PrefetchStatus::kPrefetchIneligibleUserHasCookies:
      return Preload::PrefetchStatusEnum::PrefetchNotEligibleUserHasCookies;
    case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorker:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleUserHasServiceWorker;
    case PrefetchStatus::kPrefetchIneligibleSchemeIsNotHttps:
      return Preload::PrefetchStatusEnum::PrefetchNotEligibleSchemeIsNotHttps;
    case PrefetchStatus::kPrefetchIneligibleNonDefaultStoragePartition:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleNonDefaultStoragePartition;
    case PrefetchStatus::kPrefetchNotFinishedInTime:
      return Preload::PrefetchStatusEnum::PrefetchNotFinishedInTime;
    case PrefetchStatus::kPrefetchFailedNetError:
      return Preload::PrefetchStatusEnum::PrefetchFailedNetError;
    case PrefetchStatus::kPrefetchFailedNon2XX:
      return Preload::PrefetchStatusEnum::PrefetchFailedNon2XX;
    case PrefetchStatus::kPrefetchFailedMIMENotSupported:
      return Preload::PrefetchStatusEnum::PrefetchFailedMIMENotSupported;
    case PrefetchStatus::kPrefetchSuccessful:
      return Preload::PrefetchStatusEnum::PrefetchSuccessfulButNotUsed;
    case PrefetchStatus::kPrefetchIneligibleRetryAfter:
      return Preload::PrefetchStatusEnum::PrefetchIneligibleRetryAfter;
    case PrefetchStatus::kPrefetchIneligiblePrefetchProxyNotAvailable:
      return Preload::PrefetchStatusEnum::PrefetchProxyNotAvailable;
    case PrefetchStatus::kPrefetchIsPrivacyDecoy:
      return Preload::PrefetchStatusEnum::PrefetchIsPrivacyDecoy;
    case PrefetchStatus::kPrefetchIsStale:
      return Preload::PrefetchStatusEnum::PrefetchIsStale;
    case PrefetchStatus::kPrefetchNotUsedCookiesChanged:
      return Preload::PrefetchStatusEnum::PrefetchNotUsedCookiesChanged;
    case PrefetchStatus::kPrefetchIneligibleHostIsNonUnique:
      return Preload::PrefetchStatusEnum::PrefetchNotEligibleHostIsNonUnique;
    case PrefetchStatus::kPrefetchIneligibleDataSaverEnabled:
      return Preload::PrefetchStatusEnum::PrefetchNotEligibleDataSaverEnabled;
    case PrefetchStatus::kPrefetchIneligibleExistingProxy:
      return Preload::PrefetchStatusEnum::PrefetchNotEligibleExistingProxy;
    case PrefetchStatus::kPrefetchIneligiblePreloadingDisabled:
      return Preload::PrefetchStatusEnum::PrefetchNotEligiblePreloadingDisabled;
    case PrefetchStatus::kPrefetchIneligibleBatterySaverEnabled:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleBatterySaverEnabled;
    case PrefetchStatus::kPrefetchHeldback:
      return Preload::PrefetchStatusEnum::PrefetchHeldback;
    case PrefetchStatus::kPrefetchResponseUsed:
      return Preload::PrefetchStatusEnum::PrefetchResponseUsed;
    case PrefetchStatus::kPrefetchFailedInvalidRedirect:
      return Preload::PrefetchStatusEnum::PrefetchFailedInvalidRedirect;
    case PrefetchStatus::kPrefetchFailedIneligibleRedirect:
      return Preload::PrefetchStatusEnum::PrefetchFailedIneligibleRedirect;
    case PrefetchStatus::
        kPrefetchIneligibleSameSiteCrossOriginPrefetchRequiredProxy:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleSameSiteCrossOriginPrefetchRequiredProxy;
    case PrefetchStatus::kPrefetchEvictedAfterCandidateRemoved:
      return Preload::PrefetchStatusEnum::PrefetchEvictedAfterCandidateRemoved;
    case PrefetchStatus::kPrefetchEvictedForNewerPrefetch:
      return Preload::PrefetchStatusEnum::PrefetchEvictedForNewerPrefetch;
    case PrefetchStatus::kPrefetchIneligibleRedirectFromServiceWorker:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleRedirectFromServiceWorker;
    case PrefetchStatus::kPrefetchIneligibleRedirectToServiceWorker:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleRedirectToServiceWorker;
    case PrefetchStatus::kPrefetchIneligibleUserHasServiceWorkerNoFetchHandler:
      return Preload::PrefetchStatusEnum::
          PrefetchNotEligibleUserHasServiceWorkerNoFetchHandler;
    case PrefetchStatus::kPrefetchEvictedAfterBrowsingDataRemoved:
      return Preload::PrefetchStatusEnum::
          PrefetchEvictedAfterBrowsingDataRemoved;
  }
}

bool PreloadingTriggeringOutcomeSupportedByPrefetch(
    PreloadingTriggeringOutcome feature) {
  // TODO(crbug.com/40246462): revisit the unsupported cases call sites to make
  // sure that either they are covered by other CDPs or they are included by the
  // current CDPs in the future.
  switch (feature) {
    case PreloadingTriggeringOutcome::kRunning:
    case PreloadingTriggeringOutcome::kReady:
    case PreloadingTriggeringOutcome::kSuccess:
    case PreloadingTriggeringOutcome::kFailure:
      return true;
    case PreloadingTriggeringOutcome::kTriggeredButPending:
    case PreloadingTriggeringOutcome::kUnspecified:
    case PreloadingTriggeringOutcome::kDuplicate:
    case PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown:
    case PreloadingTriggeringOutcome::kTriggeredButUpgradedToPrerender:
    case PreloadingTriggeringOutcome::kNoOp:
      return false;
  }
}

bool PreloadingTriggeringOutcomeSupportedByPrerender(
    PreloadingTriggeringOutcome feature) {
  // TODO(crbug.com/40246462): revisit the unsupported cases call sites to make
  // sure that either they are covered by other CDPs or they are included by the
  // current CDPs in the future.
  switch (feature) {
    case PreloadingTriggeringOutcome::kRunning:
    case PreloadingTriggeringOutcome::kReady:
    case PreloadingTriggeringOutcome::kSuccess:
    case PreloadingTriggeringOutcome::kFailure:
    case PreloadingTriggeringOutcome::kTriggeredButPending:
      return true;
    case PreloadingTriggeringOutcome::kUnspecified:
    case PreloadingTriggeringOutcome::kDuplicate:
    case PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown:
    case PreloadingTriggeringOutcome::kTriggeredButUpgradedToPrerender:
    case PreloadingTriggeringOutcome::kNoOp:
      return false;
  }
}

std::optional<protocol::Preload::SpeculationTargetHint>
GetProtocolSpeculationTargetHint(
    std::optional<blink::mojom::SpeculationTargetHint> target_hint) {
  if (!target_hint.has_value()) {
    return std::nullopt;
  }
  switch (target_hint.value()) {
    case blink::mojom::SpeculationTargetHint::kNoHint:
      return std::nullopt;
    case blink::mojom::SpeculationTargetHint::kBlank:
      return protocol::Preload::SpeculationTargetHintEnum::Blank;
    case blink::mojom::SpeculationTargetHint::kSelf:
      return protocol::Preload::SpeculationTargetHintEnum::Self;
  }
}

Preload::SpeculationAction SpeculationActionToProtocol(
    blink::mojom::SpeculationAction action) {
  switch (action) {
    case blink::mojom::SpeculationAction::kPrerender:
      return Preload::SpeculationActionEnum::Prerender;
    case blink::mojom::SpeculationAction::kPrerenderUntilScript:
      return Preload::SpeculationActionEnum::PrerenderUntilScript;
    case blink::mojom::SpeculationAction::kPrefetch:
      return Preload::SpeculationActionEnum::Prefetch;
    case blink::mojom::SpeculationAction::kPrefetchWithSubresources:
      // `kPrefetchWithSubresources` will be deprecated soon.
      NOTREACHED();
  }
}

}  // namespace

PreloadHandler::PreloadHandler()
    : DevToolsDomainHandler(Preload::Metainfo::domainName) {}

PreloadHandler::~PreloadHandler() = default;

// static
std::vector<PreloadHandler*> PreloadHandler::ForAgentHost(
    DevToolsAgentHostImpl* host) {
  return host->HandlersByName<PreloadHandler>(Preload::Metainfo::domainName);
}

void PreloadHandler::DidUpdatePrefetchStatus(
    const base::UnguessableToken& initiator_devtools_navigation_token,
    const std::string& initiating_frame_id,
    const GURL& prefetch_url,
    const base::UnguessableToken& preload_pipeline_id,
    PreloadingTriggeringOutcome status,
    PrefetchStatus prefetch_status,
    const std::string& request_id) {
  if (!enabled_) {
    return;
  }

  auto preloading_attempt_key =
      protocol::Preload::PreloadingAttemptKey::Create()
          .SetLoaderId(initiator_devtools_navigation_token.ToString())
          .SetAction(Preload::SpeculationActionEnum::Prefetch)
          .SetUrl(prefetch_url.spec())
          .Build();
  if (PreloadingTriggeringOutcomeSupportedByPrefetch(status)) {
    frontend_->PrefetchStatusUpdated(
        std::move(preloading_attempt_key), preload_pipeline_id.ToString(),
        initiating_frame_id, prefetch_url.spec(),
        PreloadingTriggeringOutcomeToProtocol(status),
        PrefetchStatusToProtocol(prefetch_status), request_id);
  }
}

void PreloadHandler::DidUpdatePrerenderStatus(
    const base::UnguessableToken& initiator_devtools_navigation_token,
    blink::mojom::SpeculationAction action,
    const GURL& prerender_url,
    std::optional<blink::mojom::SpeculationTargetHint> target_hint,
    const base::UnguessableToken& preload_pipeline_id,
    PreloadingTriggeringOutcome status,
    std::optional<PrerenderFinalStatus> prerender_status,
    std::optional<std::string> disallowed_mojo_interface,
    const std::vector<PrerenderMismatchedHeaders>* mismatched_headers) {
  if (!enabled_) {
    return;
  }

  auto preloading_attempt_key =
      protocol::Preload::PreloadingAttemptKey::Create()
          .SetLoaderId(initiator_devtools_navigation_token.ToString())
          .SetAction(SpeculationActionToProtocol(action))
          .SetUrl(prerender_url.spec())
          .Build();
  std::optional<protocol::Preload::SpeculationTargetHint> protocol_target_hint =
      GetProtocolSpeculationTargetHint(target_hint);
  if (protocol_target_hint.has_value()) {
    preloading_attempt_key->SetTargetHint(protocol_target_hint.value());
  }
  std::optional<Preload::PrerenderFinalStatus> protocol_prerender_status =
      prerender_status.has_value()
          ? PrerenderFinalStatusToProtocol(prerender_status.value())
          : std::optional<Preload::PrerenderFinalStatus>();
  std::optional<std::string> protocol_disallowed_mojo_interface =
      disallowed_mojo_interface.has_value()
          ? std::optional<std::string>(disallowed_mojo_interface.value())
          : std::nullopt;
  std::unique_ptr<
      protocol::Array<protocol::Preload::PrerenderMismatchedHeaders>>
      maybe_mismatched_headers;
  if (mismatched_headers) {
    auto mismatched_headers_internal = std::make_unique<
        protocol::Array<protocol::Preload::PrerenderMismatchedHeaders>>();

    for (const auto& mismatched_headers_it : *mismatched_headers) {
      auto protocol_mismatched_headers =
          protocol::Preload::PrerenderMismatchedHeaders::Create()
              .SetHeaderName(mismatched_headers_it.header_name)
              .Build();
      if (mismatched_headers_it.initial_value) {
        protocol_mismatched_headers->SetInitialValue(
            mismatched_headers_it.initial_value.value());
      }
      if (mismatched_headers_it.activation_value) {
        protocol_mismatched_headers->SetActivationValue(
            mismatched_headers_it.activation_value.value());
      }
      mismatched_headers_internal->push_back(
          std::move(protocol_mismatched_headers));
    }
    maybe_mismatched_headers = std::move(mismatched_headers_internal);
  }

  if (PreloadingTriggeringOutcomeSupportedByPrerender(status)) {
    frontend_->PrerenderStatusUpdated(
        std::move(preloading_attempt_key), preload_pipeline_id.ToString(),
        PreloadingTriggeringOutcomeToProtocol(status),
        std::move(protocol_prerender_status),
        std::move(protocol_disallowed_mojo_interface),
        std::move(maybe_mismatched_headers));
  }
}

Response PreloadHandler::Enable() {
  enabled_ = true;
  SendInitialPreloadEnabledState();
  SendCurrentPreloadStatus();
  return Response::FallThrough();
}

Response PreloadHandler::Disable() {
  enabled_ = false;
  return Response::FallThrough();
}

void PreloadHandler::Wire(UberDispatcher* dispatcher) {
  frontend_ = std::make_unique<Preload::Frontend>(dispatcher->channel());
  Preload::Dispatcher::wire(dispatcher, this);
}

void PreloadHandler::SetRenderer(int process_host_id,
                                 RenderFrameHostImpl* frame_host) {
  host_ = frame_host;
}

void PreloadHandler::SendInitialPreloadEnabledState() {
  if (!host_) {
    return;
  }

  WebContentsImpl* web_contents =
      WebContentsImpl::FromRenderFrameHostImpl(host_);
  PrefetchService* prefetch_service = PrefetchService::GetFromFrameTreeNodeId(
      web_contents->GetPrimaryMainFrame()->GetFrameTreeNodeId());

  if (!prefetch_service || !prefetch_service->GetPrefetchServiceDelegate()) {
    return;
  }

  auto* delegate = prefetch_service->GetPrefetchServiceDelegate();
  auto& config = PreloadingConfig::GetInstance();

  // TODO(crbug.com/40246462): Add more grainularity to
  // PreloadingEligibility to distinguish PreloadHoldback and
  // DisabledByPreference for PreloadingEligibility::kPreloadingDisabled.
  // Use more general method to check status of Preloading instead of
  // relying on PrefetchService.
  frontend_->PreloadEnabledStateUpdated(
      !delegate->IsPreloadingPrefEnabled(), delegate->IsDataSaverEnabled(),
      delegate->IsBatterySaverEnabled(),
      // Keep them in alphabetical order.
      config.ShouldHoldback(
          PreloadingType::kPrefetch,
          content::content_preloading_predictor::kSpeculationRules),
      config.ShouldHoldback(
          PreloadingType::kPrerender,
          content::content_preloading_predictor::kSpeculationRules));
  // TODO(https://crbug.com/428500219): Set holdback status for
  // prerender-until-script.
}

void PreloadHandler::SendCurrentPreloadStatus() {
  if (!host_) {
    return;
  }

  std::vector<RenderFrameHostImpl*> documents_in_local_subtree;
  RenderFrameHostImpl* root = host_;
  host_->ForEachRenderFrameHostImplWithAction(
      [&documents_in_local_subtree, root](
          RenderFrameHostImpl* rfh) -> RenderFrameHost::FrameIterationAction {
        if (rfh != root &&
            RenderFrameDevToolsAgentHost::ShouldCreateDevToolsForHost(rfh)) {
          return RenderFrameHost::FrameIterationAction::kSkipChildren;
        }
        documents_in_local_subtree.push_back(rfh);
        return RenderFrameHost::FrameIterationAction::kContinue;
      });

  for (RenderFrameHostImpl* document : documents_in_local_subtree) {
    auto* preload_storage =
        DevToolsPreloadStorage::GetForCurrentDocument(document);
    if (!preload_storage) {
      continue;
    }

    std::optional<base::UnguessableToken> maybe_navigation_token =
        document->GetDevToolsNavigationToken();
    if (!maybe_navigation_token.has_value()) {
      continue;
    }
    const base::UnguessableToken initiator_devtools_navigation_token =
        maybe_navigation_token.value();
    const std::string initiating_frame_id =
        document->GetDevToolsFrameToken().ToString();
    for (const auto& [key, data] : preload_storage->prefetch_data_map()) {
      DidUpdatePrefetchStatus(initiator_devtools_navigation_token,
                              initiating_frame_id,
                              /*prefetch_url=*/key, data.preload_pipeline_id,
                              data.outcome, data.status, data.request_id);
    }
    for (const auto& [key, data] : preload_storage->prerender_data_map()) {
      DidUpdatePrerenderStatus(
          initiator_devtools_navigation_token,
          blink::mojom::SpeculationAction::kPrerender,
          /*prerender_url=*/key.first,
          /*target_hint=*/key.second, data.preload_pipeline_id, data.outcome,
          data.status, data.disallowed_mojo_interface,
          data.mismatched_headers.empty() ? nullptr : &data.mismatched_headers);
    }
    for (const auto& [key, data] :
         preload_storage->prerender_until_script_data_map()) {
      DidUpdatePrerenderStatus(
          initiator_devtools_navigation_token,
          blink::mojom::SpeculationAction::kPrerenderUntilScript,
          /*prerender_url=*/key.first,
          /*target_hint=*/key.second, data.preload_pipeline_id, data.outcome,
          data.status, data.disallowed_mojo_interface,
          data.mismatched_headers.empty() ? nullptr : &data.mismatched_headers);
    }
  }
}

}  // namespace content::protocol