// Copyright 2018 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/devtools_instrumentation.h"

#include "base/containers/adapters.h"
#include "base/feature_list.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/traced_value.h"
#include "components/download/public/common/download_create_info.h"
#include "components/download/public/common/download_item.h"
#include "components/download/public/common/download_url_parameters.h"
#include "content/browser/devtools/browser_devtools_agent_host.h"
#include "content/browser/devtools/dedicated_worker_devtools_agent_host.h"
#include "content/browser/devtools/devtools_issue_storage.h"
#include "content/browser/devtools/devtools_preload_storage.h"
#include "content/browser/devtools/protocol/audits.h"
#include "content/browser/devtools/protocol/audits_handler.h"
#include "content/browser/devtools/protocol/browser_handler.h"
#include "content/browser/devtools/protocol/device_access_handler.h"
#include "content/browser/devtools/protocol/emulation_handler.h"
#include "content/browser/devtools/protocol/fedcm_handler.h"
#include "content/browser/devtools/protocol/fetch_handler.h"
#include "content/browser/devtools/protocol/input_handler.h"
#include "content/browser/devtools/protocol/log_handler.h"
#include "content/browser/devtools/protocol/network.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/devtools/protocol/page_handler.h"
#include "content/browser/devtools/protocol/preload_handler.h"
#include "content/browser/devtools/protocol/security_handler.h"
#include "content/browser/devtools/protocol/storage_handler.h"
#include "content/browser/devtools/protocol/target_handler.h"
#include "content/browser/devtools/protocol/tracing_handler.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/devtools/service_worker_devtools_agent_host.h"
#include "content/browser/devtools/shared_worker_devtools_agent_host.h"
#include "content/browser/devtools/web_contents_devtools_agent_host.h"
#include "content/browser/devtools/worker_devtools_manager.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_package/signed_exchange_envelope.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/cookie_insight_list_data.h"
#include "content/public/browser/cookie_insight_list_handler.h"
#include "devtools_agent_host_impl.h"
#include "devtools_instrumentation.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/features.h"
#include "net/base/load_flags.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/filter/source_stream_type.h"
#include "net/http/http_request_headers.h"
#include "net/quic/web_transport_error.h"
#include "net/ssl/ssl_info.h"
#include "services/network/public/cpp/devtools_observer_util.h"
#include "services/network/public/cpp/url_loader_factory_builder.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"

namespace content {
namespace devtools_instrumentation {

namespace {

namespace AttributionReportingIssueTypeEnum =
    protocol::Audits::AttributionReportingIssueTypeEnum;

const char kExampleBrowserProcessDeprecation[] =
    "ExampleBrowserProcessDeprecation";
const char kRelatedWebsiteSets[] = "RelatedWebsiteSets";

template <typename Handler, typename... MethodArgs, typename... Args>
void DispatchToAgents(DevToolsAgentHostImpl* host,
                      void (Handler::*method)(MethodArgs...),
                      Args&&... args) {
  if (!host) {
    return;
  }
  for (auto* h : Handler::ForAgentHost(host)) {
    (h->*method)(std::forward<Args>(args)...);
  }
}

template <typename Handler, typename... MethodArgs, typename... Args>
void DispatchToAgents(FrameTreeNode* frame_tree_node,
                      void (Handler::*method)(MethodArgs...),
                      Args&&... args) {
  DevToolsAgentHostImpl* agent_host =
      RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
  DispatchToAgents(agent_host, method, std::forward<Args>(args)...);
}

template <typename Handler, typename... MethodArgs, typename... Args>
void DispatchToAgents(FrameTreeNodeId frame_tree_node_id,
                      void (Handler::*method)(MethodArgs...),
                      Args&&... args) {
  FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (ftn) {
    DispatchToAgents(ftn, method, std::forward<Args>(args)...);
  }
}

template <typename Handler, typename... MethodArgs, typename... Args>
void DispatchToAgents(WebContents* web_contents,
                      void (Handler::*method)(MethodArgs...),
                      Args&&... args) {
  auto agent_host = DevToolsAgentHost::GetForTab(web_contents);
  if (agent_host) {
    DispatchToAgents(static_cast<DevToolsAgentHostImpl*>(agent_host.get()),
                     method, std::forward<Args>(args)...);
  }
  if (DevToolsAgentHost::HasFor(web_contents)) {
    DispatchToAgents(
        static_cast<DevToolsAgentHostImpl*>(
            DevToolsAgentHost::GetOrCreateFor(web_contents).get()),
        method, std::forward<Args>(args)...);
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue> BuildHeavyAdIssue(
    const blink::mojom::HeavyAdIssueDetailsPtr& issue_details) {
  protocol::String status =
      (issue_details->resolution ==
       blink::mojom::HeavyAdResolutionStatus::kHeavyAdBlocked)
          ? protocol::Audits::HeavyAdResolutionStatusEnum::HeavyAdBlocked
          : protocol::Audits::HeavyAdResolutionStatusEnum::HeavyAdWarning;
  protocol::String reason_string;
  switch (issue_details->reason) {
    case blink::mojom::HeavyAdReason::kNetworkTotalLimit:
      reason_string = protocol::Audits::HeavyAdReasonEnum::NetworkTotalLimit;
      break;
    case blink::mojom::HeavyAdReason::kCpuTotalLimit:
      reason_string = protocol::Audits::HeavyAdReasonEnum::CpuTotalLimit;
      break;
    case blink::mojom::HeavyAdReason::kCpuPeakLimit:
      reason_string = protocol::Audits::HeavyAdReasonEnum::CpuPeakLimit;
      break;
  }
  auto heavy_ad_details =
      protocol::Audits::HeavyAdIssueDetails::Create()
          .SetReason(reason_string)
          .SetResolution(status)
          .SetFrame(protocol::Audits::AffectedFrame::Create()
                        .SetFrameId(issue_details->frame->frame_id)
                        .Build())
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetHeavyAdIssueDetails(std::move(heavy_ad_details))
          .Build();
  auto issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(protocol::Audits::InspectorIssueCodeEnum::HeavyAdIssue)
          .SetDetails(std::move(protocol_issue_details))
          .Build();
  return issue;
}

protocol::Audits::AttributionReportingIssueType
BuildAttributionReportingIssueViolationType(
    blink::mojom::AttributionReportingIssueType type) {
  switch (type) {
    case blink::mojom::AttributionReportingIssueType::kPermissionPolicyDisabled:
      return AttributionReportingIssueTypeEnum::PermissionPolicyDisabled;
    case blink::mojom::AttributionReportingIssueType::
        kUntrustworthyReportingOrigin:
      return AttributionReportingIssueTypeEnum::UntrustworthyReportingOrigin;
    case blink::mojom::AttributionReportingIssueType::kInsecureContext:
      return AttributionReportingIssueTypeEnum::InsecureContext;
    case blink::mojom::AttributionReportingIssueType::
        kInvalidRegisterSourceHeader:
      return AttributionReportingIssueTypeEnum::InvalidHeader;
    case blink::mojom::AttributionReportingIssueType::
        kInvalidRegisterTriggerHeader:
      return AttributionReportingIssueTypeEnum::InvalidRegisterTriggerHeader;
    case blink::mojom::AttributionReportingIssueType::kSourceAndTriggerHeaders:
      return AttributionReportingIssueTypeEnum::SourceAndTriggerHeaders;
    case blink::mojom::AttributionReportingIssueType::kSourceIgnored:
      return AttributionReportingIssueTypeEnum::SourceIgnored;
    case blink::mojom::AttributionReportingIssueType::kTriggerIgnored:
      return AttributionReportingIssueTypeEnum::TriggerIgnored;
    case blink::mojom::AttributionReportingIssueType::kOsSourceIgnored:
      return AttributionReportingIssueTypeEnum::OsSourceIgnored;
    case blink::mojom::AttributionReportingIssueType::kOsTriggerIgnored:
      return AttributionReportingIssueTypeEnum::OsTriggerIgnored;
    case blink::mojom::AttributionReportingIssueType::
        kInvalidRegisterOsSourceHeader:
      return AttributionReportingIssueTypeEnum::InvalidRegisterOsSourceHeader;
    case blink::mojom::AttributionReportingIssueType::
        kInvalidRegisterOsTriggerHeader:
      return AttributionReportingIssueTypeEnum::InvalidRegisterOsTriggerHeader;
    case blink::mojom::AttributionReportingIssueType::kWebAndOsHeaders:
      return AttributionReportingIssueTypeEnum::WebAndOsHeaders;
    case blink::mojom::AttributionReportingIssueType::kNoWebOrOsSupport:
      return AttributionReportingIssueTypeEnum::NoWebOrOsSupport;
    case blink::mojom::AttributionReportingIssueType::
        kNavigationRegistrationWithoutTransientUserActivation:
      // This issue is not reported from the browser.
      NOTREACHED();
    case blink::mojom::AttributionReportingIssueType::kInvalidInfoHeader:
      return AttributionReportingIssueTypeEnum::InvalidInfoHeader;
    case blink::mojom::AttributionReportingIssueType::kNoRegisterSourceHeader:
      return AttributionReportingIssueTypeEnum::NoRegisterSourceHeader;
    case blink::mojom::AttributionReportingIssueType::kNoRegisterTriggerHeader:
      return AttributionReportingIssueTypeEnum::NoRegisterTriggerHeader;
    case blink::mojom::AttributionReportingIssueType::kNoRegisterOsSourceHeader:
      return AttributionReportingIssueTypeEnum::NoRegisterOsSourceHeader;
    case blink::mojom::AttributionReportingIssueType::
        kNoRegisterOsTriggerHeader:
      return AttributionReportingIssueTypeEnum::NoRegisterOsTriggerHeader;
    case blink::mojom::AttributionReportingIssueType::
        kNavigationRegistrationUniqueScopeAlreadySet:
      return AttributionReportingIssueTypeEnum::
          NavigationRegistrationUniqueScopeAlreadySet;
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue>
BuildAttributionReportingIssue(
    const blink::mojom::AttributionReportingIssueDetailsPtr& issue_details) {
  protocol::String violation_type = BuildAttributionReportingIssueViolationType(
      issue_details->violation_type);

  auto request = protocol::Audits::AffectedRequest::Create()
                     .SetUrl(issue_details->request->url)
                     .Build();
  if (issue_details->request->request_id.has_value()) {
    request->SetRequestId(issue_details->request->request_id.value());
  }
  auto attribution_reporting_issue_details =
      protocol::Audits::AttributionReportingIssueDetails::Create()
          .SetViolationType(violation_type)
          .SetRequest(std::move(request))
          .Build();
  if (issue_details->invalid_parameter.has_value()) {
    attribution_reporting_issue_details->SetInvalidParameter(
        issue_details->invalid_parameter.value());
  }

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetAttributionReportingIssueDetails(
              std::move(attribution_reporting_issue_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                AttributionReportingIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();
  return issue;
}

protocol::Audits::FederatedAuthRequestIssueReason
FederatedAuthRequestResultToProtocol(
    blink::mojom::FederatedAuthRequestResult result) {
  using blink::mojom::FederatedAuthRequestResult;
  namespace FederatedAuthRequestIssueReasonEnum =
      protocol::Audits::FederatedAuthRequestIssueReasonEnum;
  switch (result) {
    case FederatedAuthRequestResult::kShouldEmbargo: {
      return FederatedAuthRequestIssueReasonEnum::ShouldEmbargo;
    }
    case FederatedAuthRequestResult::kDisabledInSettings: {
      return FederatedAuthRequestIssueReasonEnum::DisabledInSettings;
    }
    case FederatedAuthRequestResult::kDisabledInFlags: {
      return FederatedAuthRequestIssueReasonEnum::DisabledInFlags;
    }
    case FederatedAuthRequestResult::kIdpNotPotentiallyTrustworthy: {
      return FederatedAuthRequestIssueReasonEnum::IdpNotPotentiallyTrustworthy;
    }
    case FederatedAuthRequestResult::kTooManyRequests: {
      return FederatedAuthRequestIssueReasonEnum::TooManyRequests;
    }
    case FederatedAuthRequestResult::kWellKnownHttpNotFound: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownHttpNotFound;
    }
    case FederatedAuthRequestResult::kWellKnownNoResponse: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownNoResponse;
    }
    case FederatedAuthRequestResult::kWellKnownInvalidResponse: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownInvalidResponse;
    }
    case FederatedAuthRequestResult::kWellKnownListEmpty: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownListEmpty;
    }
    case FederatedAuthRequestResult::kWellKnownInvalidContentType: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownInvalidContentType;
    }
    case FederatedAuthRequestResult::kConfigNotInWellKnown: {
      return FederatedAuthRequestIssueReasonEnum::ConfigNotInWellKnown;
    }
    case FederatedAuthRequestResult::kWellKnownTooBig: {
      return FederatedAuthRequestIssueReasonEnum::WellKnownTooBig;
    }
    case FederatedAuthRequestResult::kConfigHttpNotFound: {
      return FederatedAuthRequestIssueReasonEnum::ConfigHttpNotFound;
    }
    case FederatedAuthRequestResult::kConfigNoResponse: {
      return FederatedAuthRequestIssueReasonEnum::ConfigNoResponse;
    }
    case FederatedAuthRequestResult::kConfigInvalidResponse: {
      return FederatedAuthRequestIssueReasonEnum::ConfigInvalidResponse;
    }
    case FederatedAuthRequestResult::kConfigInvalidContentType: {
      return FederatedAuthRequestIssueReasonEnum::ConfigInvalidContentType;
    }
    case FederatedAuthRequestResult::kClientMetadataHttpNotFound: {
      return FederatedAuthRequestIssueReasonEnum::ClientMetadataHttpNotFound;
    }
    case FederatedAuthRequestResult::kClientMetadataNoResponse: {
      return FederatedAuthRequestIssueReasonEnum::ClientMetadataNoResponse;
    }
    case FederatedAuthRequestResult::kClientMetadataInvalidResponse: {
      return FederatedAuthRequestIssueReasonEnum::ClientMetadataInvalidResponse;
    }
    case FederatedAuthRequestResult::kClientMetadataInvalidContentType: {
      return FederatedAuthRequestIssueReasonEnum::
          ClientMetadataInvalidContentType;
    }
    case FederatedAuthRequestResult::kAccountsHttpNotFound: {
      return FederatedAuthRequestIssueReasonEnum::AccountsHttpNotFound;
    }
    case FederatedAuthRequestResult::kAccountsNoResponse: {
      return FederatedAuthRequestIssueReasonEnum::AccountsNoResponse;
    }
    case FederatedAuthRequestResult::kAccountsInvalidResponse: {
      return FederatedAuthRequestIssueReasonEnum::AccountsInvalidResponse;
    }
    case FederatedAuthRequestResult::kAccountsListEmpty: {
      return FederatedAuthRequestIssueReasonEnum::AccountsListEmpty;
    }
    case FederatedAuthRequestResult::kAccountsInvalidContentType: {
      return FederatedAuthRequestIssueReasonEnum::AccountsInvalidContentType;
    }
    case FederatedAuthRequestResult::kIdTokenHttpNotFound: {
      return FederatedAuthRequestIssueReasonEnum::IdTokenHttpNotFound;
    }
    case FederatedAuthRequestResult::kIdTokenNoResponse: {
      return FederatedAuthRequestIssueReasonEnum::IdTokenNoResponse;
    }
    case FederatedAuthRequestResult::kIdTokenInvalidResponse: {
      return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidResponse;
    }
    case FederatedAuthRequestResult::kIdTokenIdpErrorResponse: {
      return FederatedAuthRequestIssueReasonEnum::IdTokenIdpErrorResponse;
    }
    case FederatedAuthRequestResult::kIdTokenCrossSiteIdpErrorResponse: {
      return FederatedAuthRequestIssueReasonEnum::
          IdTokenCrossSiteIdpErrorResponse;
    }
    case FederatedAuthRequestResult::kIdTokenInvalidContentType: {
      return FederatedAuthRequestIssueReasonEnum::IdTokenInvalidContentType;
    }
    case FederatedAuthRequestResult::kCanceled: {
      return FederatedAuthRequestIssueReasonEnum::Canceled;
    }
    case FederatedAuthRequestResult::kRpPageNotVisible:
      return FederatedAuthRequestIssueReasonEnum::RpPageNotVisible;
    case FederatedAuthRequestResult::kError: {
      return FederatedAuthRequestIssueReasonEnum::ErrorIdToken;
    }
    case FederatedAuthRequestResult::kSilentMediationFailure: {
      return FederatedAuthRequestIssueReasonEnum::SilentMediationFailure;
    }
    case FederatedAuthRequestResult::kThirdPartyCookiesBlocked: {
      return FederatedAuthRequestIssueReasonEnum::ThirdPartyCookiesBlocked;
    }
    case FederatedAuthRequestResult::kNotSignedInWithIdp: {
      return FederatedAuthRequestIssueReasonEnum::NotSignedInWithIdp;
    }
    case FederatedAuthRequestResult::kMissingTransientUserActivation: {
      return FederatedAuthRequestIssueReasonEnum::
          MissingTransientUserActivation;
    }
    case FederatedAuthRequestResult::kReplacedByActiveMode: {
      return FederatedAuthRequestIssueReasonEnum::ReplacedByActiveMode;
    }
    case FederatedAuthRequestResult::kInvalidFieldsSpecified: {
      return FederatedAuthRequestIssueReasonEnum::InvalidFieldsSpecified;
    }
    case FederatedAuthRequestResult::kRelyingPartyOriginIsOpaque: {
      return FederatedAuthRequestIssueReasonEnum::RelyingPartyOriginIsOpaque;
    }
    case FederatedAuthRequestResult::kTypeNotMatching: {
      return FederatedAuthRequestIssueReasonEnum::TypeNotMatching;
    }
    case FederatedAuthRequestResult::kUiDismissedNoEmbargo: {
      return FederatedAuthRequestIssueReasonEnum::UiDismissedNoEmbargo;
    }
    case FederatedAuthRequestResult::kCorsError: {
      return FederatedAuthRequestIssueReasonEnum::CorsError;
    }
    case FederatedAuthRequestResult::kSuppressedBySegmentationPlatform: {
      return FederatedAuthRequestIssueReasonEnum::
          SuppressedBySegmentationPlatform;
    }
    case FederatedAuthRequestResult::kSuccess: {
      NOTREACHED();
    }
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue>
BuildFederatedAuthRequestIssue(
    const blink::mojom::FederatedAuthRequestIssueDetailsPtr& issue_details) {
  auto federated_auth_request_details =
      protocol::Audits::FederatedAuthRequestIssueDetails::Create()
          .SetFederatedAuthRequestIssueReason(
              FederatedAuthRequestResultToProtocol(issue_details->status))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetFederatedAuthRequestIssueDetails(
              std::move(federated_auth_request_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                FederatedAuthRequestIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();
  return issue;
}

protocol::Audits::FederatedAuthUserInfoRequestIssueReason
FederatedAuthUserInfoRequestResultToProtocol(
    blink::mojom::FederatedAuthUserInfoRequestResult result) {
  using blink::mojom::FederatedAuthUserInfoRequestResult;
  namespace FederatedAuthUserInfoRequestIssueReasonEnum =
      protocol::Audits::FederatedAuthUserInfoRequestIssueReasonEnum;
  switch (result) {
    case FederatedAuthUserInfoRequestResult::kNotSameOrigin: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::NotSameOrigin;
    }
    case FederatedAuthUserInfoRequestResult::kNotIframe: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::NotIframe;
    }
    case FederatedAuthUserInfoRequestResult::kNotPotentiallyTrustworthy: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::
          NotPotentiallyTrustworthy;
    }
    case FederatedAuthUserInfoRequestResult::kNoApiPermission: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::NoApiPermission;
    }
    case FederatedAuthUserInfoRequestResult::kNotSignedInWithIdp: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::NotSignedInWithIdp;
    }
    case FederatedAuthUserInfoRequestResult::kNoAccountSharingPermission: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::
          NoAccountSharingPermission;
    }
    case FederatedAuthUserInfoRequestResult::kInvalidConfigOrWellKnown: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::
          InvalidConfigOrWellKnown;
    }
    case FederatedAuthUserInfoRequestResult::kInvalidAccountsResponse: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::
          InvalidAccountsResponse;
    }
    case FederatedAuthUserInfoRequestResult::
        kNoReturningUserFromFetchedAccounts: {
      return FederatedAuthUserInfoRequestIssueReasonEnum::
          NoReturningUserFromFetchedAccounts;
    }
    case FederatedAuthUserInfoRequestResult::kSuccess:
    case FederatedAuthUserInfoRequestResult::kUnhandledRequest: {
      NOTREACHED();
    }
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue>
BuildFederatedAuthUserInfoRequestIssue(
    const blink::mojom::FederatedAuthUserInfoRequestIssueDetailsPtr&
        issue_details) {
  auto federated_auth_user_info_request_details =
      protocol::Audits::FederatedAuthUserInfoRequestIssueDetails::Create()
          .SetFederatedAuthUserInfoRequestIssueReason(
              FederatedAuthUserInfoRequestResultToProtocol(
                  issue_details->status))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetFederatedAuthUserInfoRequestIssueDetails(
              std::move(federated_auth_user_info_request_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                FederatedAuthUserInfoRequestIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();
  return issue;
}

const char* DeprecationIssueTypeToProtocol(
    blink::mojom::DeprecationIssueType error_type) {
  switch (error_type) {
    case blink::mojom::DeprecationIssueType::kExampleBrowserProcessDeprecation:
      return kExampleBrowserProcessDeprecation;
    case blink::mojom::DeprecationIssueType::kRelatedWebsiteSets:
      return kRelatedWebsiteSets;
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue> BuildDeprecationIssue(
    const blink::mojom::DeprecationIssueDetailsPtr& issue_details) {
  std::unique_ptr<protocol::Audits::SourceCodeLocation> source_code_location =
      protocol::Audits::SourceCodeLocation::Create()
          .SetUrl(issue_details->affected_location->url.value())
          .SetLineNumber(issue_details->affected_location->line)
          .SetColumnNumber(issue_details->affected_location->column)
          .Build();

  if (issue_details->affected_location->script_id.has_value()) {
    source_code_location->SetScriptId(
        issue_details->affected_location->script_id.value());
  }

  auto deprecation_issue_details =
      protocol::Audits::DeprecationIssueDetails::Create()
          .SetSourceCodeLocation(std::move(source_code_location))
          .SetType(DeprecationIssueTypeToProtocol(issue_details->type))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetDeprecationIssueDetails(std::move(deprecation_issue_details))
          .Build();

  auto deprecation_issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(protocol::Audits::InspectorIssueCodeEnum::DeprecationIssue)
          .SetDetails(std::move(protocol_issue_details))
          .Build();

  return deprecation_issue;
}

std::unique_ptr<protocol::Audits::InspectorIssue> BuildBounceTrackingIssue(
    const blink::mojom::BounceTrackingIssueDetailsPtr& issue_details) {
  auto bounce_tracking_issue_details =
      protocol::Audits::BounceTrackingIssueDetails::Create()
          .SetTrackingSites(std::make_unique<protocol::Array<protocol::String>>(
              issue_details->tracking_sites))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetBounceTrackingIssueDetails(
              std::move(bounce_tracking_issue_details))
          .Build();

  auto issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(
              protocol::Audits::InspectorIssueCodeEnum::BounceTrackingIssue)
          .SetDetails(std::move(protocol_issue_details))
          .Build();

  return issue;
}

std::unique_ptr<protocol::Audits::InspectorIssue> BuildPartitioningBlobURLIssue(
    const blink::mojom::PartitioningBlobURLIssueDetailsPtr& issue_details) {
  protocol::String partitioning_blob_url_info_string;
  switch (issue_details->partitioning_blob_url_info) {
    case blink::mojom::PartitioningBlobURLInfo::kBlockedCrossPartitionFetching:
      partitioning_blob_url_info_string = protocol::Audits::
          PartitioningBlobURLInfoEnum::BlockedCrossPartitionFetching;
      break;
    case blink::mojom::PartitioningBlobURLInfo::kEnforceNoopenerForNavigation:
      partitioning_blob_url_info_string = protocol::Audits::
          PartitioningBlobURLInfoEnum::EnforceNoopenerForNavigation;
      break;
    default:
      partitioning_blob_url_info_string = "Unknown";
      break;
  }

  auto partitioning_blob_url_issue_details =
      protocol::Audits::PartitioningBlobURLIssueDetails::Create()
          .SetUrl(issue_details->url.spec())
          .SetPartitioningBlobURLInfo(partitioning_blob_url_info_string)
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetPartitioningBlobURLIssueDetails(
              std::move(partitioning_blob_url_issue_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                PartitioningBlobURLIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();

  return issue;
}

void UpdateChildFrameTrees(FrameTreeNode* ftn, bool update_target_info) {
  if (auto* agent_host = WebContentsDevToolsAgentHost::GetFor(
          WebContentsImpl::FromFrameTreeNode(ftn))) {
    agent_host->UpdateChildFrameTrees(update_target_info);
  }
}

}  // namespace

void OnResetNavigationRequest(NavigationRequest* navigation_request) {
  // Traverse frame chain all the way to the top and report to all
  // page handlers that the navigation completed.
  for (FrameTreeNode* node = navigation_request->frame_tree_node(); node;
       node = FrameTreeNode::From(node->parent())) {
    DispatchToAgents(node, &protocol::PageHandler::NavigationReset,
                     navigation_request);
  }
}

void OnNavigationResponseReceived(const NavigationRequest& nav_request,
                                  const network::mojom::URLResponseHead& head) {
  // This response is artificial (see CachedNavigationURLLoader), so we don't
  // want to report it.
  if (nav_request.IsPageActivation()) {
    return;
  }

  FrameTreeNode* ftn = nav_request.frame_tree_node();
  std::string id = nav_request.devtools_navigation_token().ToString();
  std::string frame_id =
      ftn->current_frame_host()->devtools_frame_token().ToString();
  GURL url = nav_request.common_params().url;

  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(head);
  DispatchToAgents(ftn, &protocol::NetworkHandler::ResponseReceived, id, id,
                   url, protocol::Network::ResourceTypeEnum::Document,
                   *head_info, frame_id);
}

void OnFetchKeepAliveRequestWillBeSent(
    FrameTreeNode* frame_tree_node,
    const std::string& request_id,
    const network::ResourceRequest& request,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info) {
  CHECK(frame_tree_node);

  auto timestamp = base::TimeTicks::Now();
  std::string frame_token =
      frame_tree_node->current_frame_host()->devtools_frame_token().ToString();
  GURL initiator_url;
  if (request.request_initiator.has_value()) {
    initiator_url = request.request_initiator->GetURL();
  }
  DispatchToAgents(frame_tree_node,
                   &protocol::NetworkHandler::FetchKeepAliveRequestWillBeSent,
                   request_id, request, initiator_url, frame_token, timestamp,
                   redirect_info);
}

void OnFetchKeepAliveResponseReceived(
    FrameTreeNode* frame_tree_node,
    const std::string& request_id,
    const GURL& url,
    const network::mojom::URLResponseHead& head) {
  CHECK(frame_tree_node);

  std::string frame_token =
      frame_tree_node->current_frame_host()->devtools_frame_token().ToString();
  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(head);
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::ResponseReceived,
                   request_id, request_id, url,
                   protocol::Network::ResourceTypeEnum::Fetch, *head_info,
                   frame_token);
}

void OnFetchKeepAliveRequestComplete(
    FrameTreeNode* frame_tree_node,
    const std::string& request_id,
    const network::URLLoaderCompletionStatus& status) {
  CHECK(frame_tree_node);

  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::LoadingComplete,
                   request_id, protocol::Network::ResourceTypeEnum::Fetch,
                   status);
}

void BackForwardCacheNotUsed(
    const NavigationRequest* nav_request,
    const BackForwardCacheCanStoreDocumentResult* result,
    const BackForwardCacheCanStoreTreeResult* tree_result) {
  DCHECK(nav_request);
  FrameTreeNode* ftn = nav_request->frame_tree_node();
  DispatchToAgents(ftn, &protocol::PageHandler::BackForwardCacheNotUsed,
                   nav_request, result, tree_result);
}

void WillSwapFrameTreeNode(FrameTreeNode& old_node, FrameTreeNode& new_node) {
  auto* host = static_cast<RenderFrameDevToolsAgentHost*>(
      RenderFrameDevToolsAgentHost::GetFor(&old_node));
  if (!host || host->HasSessionsWithoutTabTargetSupport()) {
    return;
  }
  // The new node may have a previous host associated, disconnect it first.
  scoped_refptr<RenderFrameDevToolsAgentHost> previous_host =
      static_cast<RenderFrameDevToolsAgentHost*>(
          RenderFrameDevToolsAgentHost::GetFor(&new_node));
  // Disconnect old host entirely, so it detaches from renderer and does not
  // cause problem if renderer comes back from the BFCache.
  previous_host->DisconnectWebContents();
  host->SetFrameTreeNode(&new_node);
}

void OnFrameTreeNodeDestroyed(FrameTreeNode& frame_tree_node) {
  // If the child frame is an OOPIF, we emit Page.frameDetached event which
  // otherwise might be lost because the OOPIF target is being destroyed.
  RenderFrameHostImpl* parent = frame_tree_node.parent();
  if (!parent) {
    return;
  }
  if (RenderFrameDevToolsAgentHost::GetFor(&frame_tree_node) !=
      RenderFrameDevToolsAgentHost::GetFor(parent)) {
    DispatchToAgents(
        RenderFrameDevToolsAgentHost::GetFor(parent),
        &protocol::PageHandler::OnFrameDetached,
        frame_tree_node.current_frame_host()->devtools_frame_token());
  }
}

void DidUpdatePolicyContainerHost(FrameTreeNode* ftn) {
  if (!ftn) {
    return;
  }

  DispatchToAgents(ftn,
                   &protocol::NetworkHandler::OnPolicyContainerHostUpdated);
}

void DidUpdateSpeculationCandidates(
    RenderFrameHost& rfh,
    const std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) {
  if (auto* storage = DevToolsPreloadStorage::GetForCurrentDocument(&rfh)) {
    storage->SpeculationCandidatesUpdated(candidates);
  }
}

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

  // See a comment in `PreloadingDecider::ctor()`.
  auto* devtools_preload_storage =
      DevToolsPreloadStorage::GetForCurrentDocument(ftn->current_frame_host());
  if (!devtools_preload_storage) {
    return;
  }

  // We update DevToolsPreloadStorage, even if there are no active DevTools
  // sessions, to persist the latest status update.
  devtools_preload_storage->UpdatePrefetchStatus(
      prefetch_url, preload_pipeline_id, status, prefetch_status, request_id);

  std::string initiating_frame_id =
      ftn->current_frame_host()->devtools_frame_token().ToString();
  DispatchToAgents(ftn, &protocol::PreloadHandler::DidUpdatePrefetchStatus,
                   initiator_devtools_navigation_token, initiating_frame_id,
                   prefetch_url, preload_pipeline_id, status, prefetch_status,
                   request_id);
}

void OnPrefetchRequestWillBeSent(
    FrameTreeNode& ftn,
    const std::string& request_id,
    const GURL& initiator,
    const network::ResourceRequest& request,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info) {
  auto timestamp = base::TimeTicks::Now();
  std::string frame_token =
      ftn.current_frame_host()->devtools_frame_token().ToString();
  DispatchToAgents(&ftn, &protocol::NetworkHandler::PrefetchRequestWillBeSent,
                   request_id, request, initiator, frame_token, timestamp,
                   redirect_info);
}

void OnPrefetchResponseReceived(FrameTreeNode* frame_tree_node,
                                const std::string& request_id,
                                const GURL& url,
                                const network::mojom::URLResponseHead& head) {
  std::string frame_token =
      frame_tree_node->current_frame_host()->devtools_frame_token().ToString();

  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(head);
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::ResponseReceived,
                   request_id, request_id, url,
                   protocol::Network::ResourceTypeEnum::Prefetch, *head_info,
                   frame_token);
}

void OnPrefetchRequestComplete(
    FrameTreeNode* frame_tree_node,
    const std::string& request_id,
    const network::URLLoaderCompletionStatus& status) {
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::LoadingComplete,
                   request_id, protocol::Network::ResourceTypeEnum::Prefetch,
                   status);
}

void OnPrefetchBodyDataReceived(FrameTreeNode* frame_tree_node,
                                const std::string& request_id,
                                const std::string& body,
                                bool is_base64_encoded) {
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::BodyDataReceived,
                   request_id, body, is_base64_encoded);
}

bool IsPrerenderAllowed(FrameTree& frame_tree) {
  FrameTreeNode* ftn = frame_tree.root();

  auto* render_frame_agent_host = static_cast<RenderFrameDevToolsAgentHost*>(
      RenderFrameDevToolsAgentHost::GetFor(ftn));
  if (render_frame_agent_host &&
      render_frame_agent_host->HasSessionsWithoutTabTargetSupport()) {
    return false;
  }

  bool is_allowed = true;
  DispatchToAgents(ftn, &protocol::PageHandler::IsPrerenderingAllowed,
                   is_allowed);
  return is_allowed;
}

void WillInitiatePrerender(FrameTree& frame_tree) {
  DCHECK(frame_tree.is_prerendering());
  auto* wc = WebContentsImpl::FromFrameTreeNode(frame_tree.root());
  if (auto* host = WebContentsDevToolsAgentHost::GetFor(wc)) {
    host->WillInitiatePrerender(frame_tree.root());
  }
}

void DidActivatePrerender(const NavigationRequest& nav_request,
                          const std::optional<base::UnguessableToken>&
                              initiator_devtools_navigation_token) {
  FrameTreeNode* ftn = nav_request.frame_tree_node();
  UpdateChildFrameTrees(ftn, /* update_target_info= */ true);
}

void DidUpdatePrerenderStatus(
    FrameTreeNodeId initiator_frame_tree_node_id,
    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) {
  auto* ftn = FrameTreeNode::GloballyFindByID(initiator_frame_tree_node_id);
  // ftn will be null if this is browser-initiated, which has no initiator.
  if (!ftn) {
    return;
  }

  // See a comment in `PreloadingDecider::ctor()`.
  auto* devtools_preload_storage =
      DevToolsPreloadStorage::GetForCurrentDocument(ftn->current_frame_host());
  if (!devtools_preload_storage) {
    return;
  }

  // We update DevToolsPreloadStorage, even if there are no active DevTools
  // sessions, to persist the latest status update.
  devtools_preload_storage->UpdatePrerenderStatus(
      action, prerender_url, target_hint, preload_pipeline_id, status,
      prerender_status, disallowed_mojo_interface, mismatched_headers);

  DispatchToAgents(ftn, &protocol::PreloadHandler::DidUpdatePrerenderStatus,
                   initiator_devtools_navigation_token, action, prerender_url,
                   target_hint, preload_pipeline_id, status, prerender_status,
                   disallowed_mojo_interface, mismatched_headers);
}

namespace {

protocol::String BuildBlockedByResponseReason(
    network::mojom::BlockedByResponseReason reason) {
  // TODO(crbug.com/336752983):
  // Add specific error messages when a subresource load was blocked due to
  // Document-Isolation-Policy (Dip).
  switch (reason) {
    case network::mojom::BlockedByResponseReason::
        kCoepFrameResourceNeedsCoepHeader:
      return protocol::Audits::BlockedByResponseReasonEnum::
          CoepFrameResourceNeedsCoepHeader;
    case network::mojom::BlockedByResponseReason::
        kCoopSandboxedIFrameCannotNavigateToCoopPage:
      return protocol::Audits::BlockedByResponseReasonEnum::
          CoopSandboxedIFrameCannotNavigateToCoopPage;
    case network::mojom::BlockedByResponseReason::kCorpNotSameOrigin:
      return protocol::Audits::BlockedByResponseReasonEnum::CorpNotSameOrigin;
    case network::mojom::BlockedByResponseReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByCoep:
    case network::mojom::BlockedByResponseReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByDip:
    case network::mojom::BlockedByResponseReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByCoepAndDip:
      return protocol::Audits::BlockedByResponseReasonEnum::
          CorpNotSameOriginAfterDefaultedToSameOriginByCoep;
    case network::mojom::BlockedByResponseReason::kCorpNotSameSite:
      return protocol::Audits::BlockedByResponseReasonEnum::CorpNotSameSite;
    case network::mojom::BlockedByResponseReason::kSRIMessageSignatureMismatch:
      return protocol::Audits::BlockedByResponseReasonEnum::
          SRIMessageSignatureMismatch;
  }
}

void ReportBlockedByResponseIssue(
    const GURL& url,
    std::string& requestId,
    FrameTreeNode* ftn,
    RenderFrameHostImpl* parent_frame,
    const network::URLLoaderCompletionStatus& status) {
  DCHECK(status.blocked_by_response_reason);

  auto issueDetails = protocol::Audits::InspectorIssueDetails::Create();
  auto request = protocol::Audits::AffectedRequest::Create()
                     .SetRequestId(requestId)
                     .SetUrl(url.spec())
                     .Build();
  auto blockedByResponseDetails =
      protocol::Audits::BlockedByResponseIssueDetails::Create()
          .SetRequest(std::move(request))
          .SetReason(
              BuildBlockedByResponseReason(*status.blocked_by_response_reason))
          .Build();

  blockedByResponseDetails->SetBlockedFrame(
      protocol::Audits::AffectedFrame::Create()
          .SetFrameId(
              ftn->current_frame_host()->devtools_frame_token().ToString())
          .Build());
  if (parent_frame) {
    blockedByResponseDetails->SetParentFrame(
        protocol::Audits::AffectedFrame::Create()
            .SetFrameId(parent_frame->devtools_frame_token().ToString())
            .Build());
  }

  issueDetails.SetBlockedByResponseIssueDetails(
      std::move(blockedByResponseDetails));

  auto inspector_issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(
              protocol::Audits::InspectorIssueCodeEnum::BlockedByResponseIssue)
          .SetDetails(issueDetails.Build())
          .Build();

  ReportBrowserInitiatedIssue(ftn->current_frame_host(),
                              std::move(inspector_issue));
}

}  // namespace

void OnNavigationRequestFailed(
    const NavigationRequest& nav_request,
    const network::URLLoaderCompletionStatus& status) {
  FrameTreeNode* ftn = nav_request.frame_tree_node();
  std::string id = nav_request.devtools_navigation_token().ToString();

  if (status.blocked_by_response_reason) {
    ReportBlockedByResponseIssue(
        const_cast<NavigationRequest&>(nav_request).GetURL(), id, ftn,
        ftn->parent(), status);
  }

  // If a BFCache navigation fails, it will be restarted as a regular
  // navigation, so we don't want to report this failure.
  if (nav_request.IsServedFromBackForwardCache()) {
    return;
  }

  // Activation of a prerender page is synchronous with its own activation flow
  // (crrev.com/c/2992411); if the prerender is cancelled (e.g. speculation rule
  // removed), the flow will fallback to a normal navigation, which is no longer
  // considered as a page activation.
  DCHECK(!nav_request.IsPageActivation());

  DispatchToAgents(ftn, &protocol::NetworkHandler::LoadingComplete, id,
                   protocol::Network::ResourceTypeEnum::Document, status);
}

void OnNavigationEntryMarkedSkippable(const GURL& url,
                                      RenderFrameHostImpl* rfh) {
  DCHECK(rfh);

  auto request =
      protocol::Audits::AffectedRequest::Create().SetUrl(url.spec()).Build();

  auto generic_details =
      protocol::Audits::GenericIssueDetails::Create()
          .SetErrorType(protocol::Audits::GenericIssueErrorTypeEnum::
                            NavigationEntryMarkedSkippable)
          .SetRequest(std::move(request))
          .Build();

  auto details = protocol::Audits::InspectorIssueDetails::Create()
                     .SetGenericIssueDetails(std::move(generic_details))
                     .Build();

  auto issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(protocol::Audits::InspectorIssueCodeEnum::GenericIssue)
          .SetDetails(std::move(details))
          .Build();

  ReportBrowserInitiatedIssue(rfh, std::move(issue));
}

bool ShouldBypassCSP(const NavigationRequest& nav_request) {
  DevToolsAgentHostImpl* agent_host =
      RenderFrameDevToolsAgentHost::GetFor(nav_request.frame_tree_node());
  if (!agent_host) {
    return false;
  }

  for (auto* page : protocol::PageHandler::ForAgentHost(agent_host)) {
    if (page->ShouldBypassCSP()) {
      return true;
    }
  }
  return false;
}

bool ShouldBypassCertificateErrors(DevToolsAgentHost* agent_host) {
  if (!agent_host) {
    return false;
  }

  DevToolsAgentHostImpl* host_impl =
      static_cast<DevToolsAgentHostImpl*>(agent_host);
  for (auto* security_handler :
       protocol::SecurityHandler::ForAgentHost(host_impl)) {
    if (security_handler->IsIgnoreCertificateErrorsSet()) {
      return true;
    }
  }
  return false;
}

bool ShouldBypassCertificateErrors() {
  for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances()) {
    if (ShouldBypassCertificateErrors(browser_agent_host)) {
      return true;
    }
  }
  return false;
}

void ApplyNetworkOverridesForDownload(
    RenderFrameHostImpl* rfh,
    download::DownloadUrlParameters* parameters) {
  FrameTreeNode* ftn =
      FrameTreeNode::GloballyFindByID(rfh->GetFrameTreeNodeId());
  if (ftn) {
    DispatchToAgents(
        ftn, &protocol::EmulationHandler::ApplyNetworkOverridesForDownload,
        parameters);
  }
}

void WillBeginDownload(download::DownloadCreateInfo* info,
                       download::DownloadItem* item) {
  if (!item) {
    return;
  }
  auto* rfh = static_cast<RenderFrameHostImpl*>(
      RenderFrameHost::FromID(info->render_process_id, info->render_frame_id));
  FrameTreeNode* ftn =
      rfh ? FrameTreeNode::GloballyFindByID(rfh->GetFrameTreeNodeId())
          : nullptr;
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn, &protocol::BrowserHandler::DownloadWillBegin, ftn,
                   item);
  DispatchToAgents(ftn, &protocol::PageHandler::DownloadWillBegin, ftn, item);

  for (auto* agent_host : BrowserDevToolsAgentHost::Instances()) {
    for (auto* browser_handler :
         protocol::BrowserHandler::ForAgentHost(agent_host)) {
      browser_handler->DownloadWillBegin(ftn, item);
    }
  }
}

void OnSignedExchangeReceived(
    FrameTreeNode* frame_tree_node,
    std::optional<const base::UnguessableToken> devtools_navigation_token,
    const GURL& outer_request_url,
    const network::mojom::URLResponseHead& outer_response,
    const std::optional<SignedExchangeEnvelope>& envelope,
    const scoped_refptr<net::X509Certificate>& certificate,
    const std::optional<net::SSLInfo>& ssl_info,
    const std::vector<SignedExchangeError>& errors) {
  DispatchToAgents(frame_tree_node,
                   &protocol::NetworkHandler::OnSignedExchangeReceived,
                   devtools_navigation_token, outer_request_url, outer_response,
                   envelope, certificate, ssl_info, errors);
}

namespace inspector_will_send_navigation_request_event {
std::unique_ptr<base::trace_event::TracedValue> Data(
    const base::UnguessableToken& request_id) {
  auto value = std::make_unique<base::trace_event::TracedValue>();
  value->SetString("requestId", request_id.ToString());
  return value;
}
}  // namespace inspector_will_send_navigation_request_event

void OnSignedExchangeCertificateRequestSent(
    FrameTreeNode* frame_tree_node,
    const base::UnguessableToken& request_id,
    const base::UnguessableToken& loader_id,
    const network::ResourceRequest& request,
    const GURL& signed_exchange_url) {
  // Make sure both back-ends yield the same timestamp.
  auto timestamp = base::TimeTicks::Now();
  network::mojom::URLRequestDevToolsInfoPtr request_info =
      network::ExtractDevToolsInfo(request);
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::RequestSent,
                   request_id.ToString(), loader_id.ToString(), request.headers,
                   *request_info,
                   protocol::Network::Initiator::TypeEnum::SignedExchange,
                   signed_exchange_url, /*initiator_devtools_request_id=*/"",
                   /*frame_token=*/std::nullopt, timestamp);

  auto value = std::make_unique<base::trace_event::TracedValue>();
  value->SetString("requestId", request_id.ToString());
  TRACE_EVENT_INSTANT_WITH_TIMESTAMP1(
      "devtools.timeline", "ResourceWillSendRequest", TRACE_EVENT_SCOPE_PROCESS,
      timestamp, "data",
      inspector_will_send_navigation_request_event::Data(request_id));
}

void OnSignedExchangeCertificateResponseReceived(
    FrameTreeNode* frame_tree_node,
    const base::UnguessableToken& request_id,
    const base::UnguessableToken& loader_id,
    const GURL& url,
    const network::mojom::URLResponseHead& head) {
  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(head);
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::ResponseReceived,
                   request_id.ToString(), loader_id.ToString(), url,
                   protocol::Network::ResourceTypeEnum::Other, *head_info,
                   std::nullopt);
}

void OnSignedExchangeCertificateRequestCompleted(
    FrameTreeNode* frame_tree_node,
    const base::UnguessableToken& request_id,
    const network::URLLoaderCompletionStatus& status) {
  DispatchToAgents(frame_tree_node, &protocol::NetworkHandler::LoadingComplete,
                   request_id.ToString(),
                   protocol::Network::ResourceTypeEnum::Other, status);
}

void ThrottleForServiceWorkerAgentHost(
    ServiceWorkerDevToolsAgentHost* agent_host,
    DevToolsAgentHostImpl* requesting_agent_host,
    scoped_refptr<DevToolsThrottleHandle> throttle_handle) {
  for (auto* target_handler :
       protocol::TargetHandler::ForAgentHost(requesting_agent_host)) {
    target_handler->AddWorkerThrottle(agent_host, throttle_handle);
  }
}

void CreateAndAddNavigationThrottles(NavigationThrottleRegistry& registry) {
  auto* navigation_handle = &registry.GetNavigationHandle();
  FrameTreeNode* frame_tree_node =
      NavigationRequest::From(navigation_handle)->frame_tree_node();
  FrameTreeNode* parent = FrameTreeNode::From(frame_tree_node->parent());

  if (!parent) {
    FrameTreeNode* outer_delegate_node =
        frame_tree_node->render_manager()->GetOuterDelegateNode();
    if (outer_delegate_node && frame_tree_node->IsFencedFrameRoot()) {
      parent = outer_delegate_node->parent()->frame_tree_node();
    } else if (frame_tree_node->GetFrameType() ==
                   FrameType::kPrerenderMainFrame &&
               !frame_tree_node->current_frame_host()
                    ->has_committed_any_navigation()) {
      if (auto* agent_host = WebContentsDevToolsAgentHost::GetFor(
              WebContentsImpl::FromFrameTreeNode(frame_tree_node))) {
        // For prerender, perform auto-attach to tab target at the point of
        // initial navigation.
        agent_host->auto_attacher()->CreateAndAddNavigationThrottles(registry);
        return;
      }
    }
  }

  if (parent) {
    if (auto* agent_host = RenderFrameDevToolsAgentHost::GetFor(parent)) {
      agent_host->auto_attacher()->CreateAndAddNavigationThrottles(registry);
    }
  } else {
    for (DevToolsAgentHostImpl* host : BrowserDevToolsAgentHost::Instances()) {
      host->auto_attacher()->CreateAndAddNavigationThrottles(registry);
    }
  }
}

void ThrottleServiceWorkerMainScriptFetch(
    ServiceWorkerContextWrapper* wrapper,
    int64_t version_id,
    const GlobalRenderFrameHostId& requesting_frame_id,
    scoped_refptr<DevToolsThrottleHandle> throttle_handle) {
  ServiceWorkerDevToolsAgentHost* agent_host =
      ServiceWorkerDevToolsManager::GetInstance()
          ->GetDevToolsAgentHostForNewInstallingWorker(wrapper, version_id);
  DCHECK(agent_host);

  // TODO(crbug.com/40276949): We should probably also add the
  // possibility for Browser wide agents to throttle the request.

  // If we have a requesting_frame_id, we should have a frame and a frame tree
  // node. However since the lifetime of these objects can be complex, we check
  // at each step that we indeed can go reach all the way to the FrameTreeNode.
  if (!requesting_frame_id) {
    return;
  }

  RenderFrameHostImpl* requesting_frame =
      RenderFrameHostImpl::FromID(requesting_frame_id);
  if (!requesting_frame) {
    return;
  }

  FrameTreeNode* ftn = requesting_frame->frame_tree_node();
  DCHECK(ftn);

  DevToolsAgentHostImpl* requesting_agent_host =
      RenderFrameDevToolsAgentHost::GetFor(ftn);
  if (!requesting_agent_host) {
    return;
  }

  ThrottleForServiceWorkerAgentHost(agent_host, requesting_agent_host,
                                    throttle_handle);
}

void ThrottleWorkerMainScriptFetch(
    const base::UnguessableToken& devtools_worker_token,
    const GlobalRenderFrameHostId& ancestor_render_frame_host_id,
    scoped_refptr<DevToolsThrottleHandle> throttle_handle) {
  DedicatedWorkerDevToolsAgentHost* agent_host =
      WorkerDevToolsManager::GetInstance().GetDevToolsHostFromToken(
          devtools_worker_token);
  if (!agent_host) {
    return;
  }

  RenderFrameHostImpl* rfh =
      RenderFrameHostImpl::FromID(ancestor_render_frame_host_id);
  if (!rfh) {
    return;
  }

  FrameTreeNode* ftn = rfh->frame_tree_node();
  DispatchToAgents(ftn, &protocol::TargetHandler::AddWorkerThrottle, agent_host,
                   std::move(throttle_handle));
}

bool ShouldWaitForDebuggerInWindowOpen() {
  for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances()) {
    for (auto* target_handler :
         protocol::TargetHandler::ForAgentHost(browser_agent_host)) {
      if (target_handler->ShouldThrottlePopups()) {
        return true;
      }
    }
  }
  return false;
}

DevtoolsOverriddenOutputParams ApplyEmulationOverrides(
    DevToolsAgentHostImpl* agent_host,
    net::HttpRequestHeaders* headers) {
  DevtoolsOverriddenOutputParams output_params;
  for (auto* emulation : protocol::EmulationHandler::ForAgentHost(agent_host)) {
    bool ua_overridden = false;
    bool accept_language_overridden = false;
    emulation->ApplyOverrides(headers, &ua_overridden,
                              &accept_language_overridden);

    output_params.user_agent_overridden |= ua_overridden;
    output_params.accept_language_overridden |= accept_language_overridden;
  }
  return output_params;
}

namespace {
// This is a helper function used in ApplyNetworkRequestOverrides and
// ApplyUserAgentMetadataOverrides to help correctly set network request header
// overrides. It behaves the same as RenderFrameDevToolsAgentHost::GetFor for
// all FrameTreeNodes except those that are prerendering. For prerendering
// FrameTreeNodes, it returns the DevToolsAgentHost of the primary main frame,
// even if it has a DTAH of its own. The network header overrides are applied
// too early, before the correct values sent by the client are propagated to the
// prerender's DTAH's handlers. As a result, we use the values that were
// previously set for the primary main frame.
DevToolsAgentHostImpl* GetDevToolsAgentHostForNetworkOverrides(
    FrameTreeNode* frame_tree_node) {
  if (frame_tree_node->frame_tree().is_prerendering()) {
    return RenderFrameDevToolsAgentHost::GetFor(
        WebContentsImpl::FromFrameTreeNode(frame_tree_node)
            ->GetPrimaryMainFrame()
            ->frame_tree_node());
  }
  return RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
}

void ApplyNetworkRequestOverrides(
    DevToolsAgentHostImpl* agent_host,
    net::HttpRequestHeaders* headers,
    bool* disable_cache,
    bool* network_instrumentation_enabled,
    bool* skip_service_worker,
    std::optional<std::vector<net::SourceStreamType>>*
        devtools_accepted_stream_types,
    bool* devtools_user_agent_overridden,
    bool* devtools_accept_language_overridden,
    GURL* referrer_override) {
  for (auto* network : protocol::NetworkHandler::ForAgentHost(agent_host)) {
    if (!network->enabled()) {
      continue;
    }
    if (network_instrumentation_enabled) {
      *network_instrumentation_enabled = true;
    }
    network->ApplyOverrides(headers, skip_service_worker, disable_cache,
                            devtools_accepted_stream_types, referrer_override);
  }

  DevtoolsOverriddenOutputParams output_params =
      ApplyEmulationOverrides(agent_host, headers);
  if (devtools_user_agent_overridden) {
    *devtools_user_agent_overridden = output_params.user_agent_overridden;
  }
  if (devtools_accept_language_overridden) {
    *devtools_accept_language_overridden =
        output_params.accept_language_overridden;
  }
}

}  // namespace

void ApplyAuctionNetworkRequestOverrides(
    FrameTreeNode* frame_tree_node,
    network::ResourceRequest* request,
    bool* network_instrumentation_enabled) {
  bool disable_cache = false;
  DevToolsAgentHostImpl* agent_host =
      GetDevToolsAgentHostForNetworkOverrides(frame_tree_node);
  if (!agent_host) {
    return;
  }
  ApplyNetworkRequestOverrides(
      agent_host, &request->headers, &disable_cache,
      network_instrumentation_enabled, &request->skip_service_worker,
      &request->devtools_accepted_stream_types, nullptr, nullptr, nullptr);
  if (disable_cache) {
    request->load_flags = net::LOAD_BYPASS_CACHE;
  }
}

void ApplyNetworkRequestOverrides(
    FrameTreeNode* frame_tree_node,
    blink::mojom::BeginNavigationParams* begin_params,
    bool* report_raw_headers,
    std::optional<std::vector<net::SourceStreamType>>*
        devtools_accepted_stream_types,
    bool* devtools_user_agent_overridden,
    bool* devtools_accept_language_overridden,
    GURL* referrer_override) {
  *devtools_user_agent_overridden = false;
  *devtools_accept_language_overridden = false;
  bool disable_cache = false;
  DevToolsAgentHostImpl* agent_host =
      GetDevToolsAgentHostForNetworkOverrides(frame_tree_node);
  if (!agent_host) {
    return;
  }
  net::HttpRequestHeaders headers;
  headers.AddHeadersFromString(begin_params->headers);
  ApplyNetworkRequestOverrides(
      agent_host, &headers, &disable_cache, report_raw_headers,
      &begin_params->skip_service_worker, devtools_accepted_stream_types,
      devtools_user_agent_overridden, devtools_accept_language_overridden,
      referrer_override);
  if (disable_cache) {
    begin_params->load_flags &=
        ~(net::LOAD_VALIDATE_CACHE | net::LOAD_SKIP_CACHE_VALIDATION |
          net::LOAD_ONLY_FROM_CACHE | net::LOAD_DISABLE_CACHE);
    begin_params->load_flags |= net::LOAD_BYPASS_CACHE;
  }

  begin_params->headers = headers.ToString();
}

bool ApplyUserAgentMetadataOverrides(
    FrameTreeNode* frame_tree_node,
    std::optional<blink::UserAgentMetadata>* override_out) {
  DevToolsAgentHostImpl* agent_host =
      GetDevToolsAgentHostForNetworkOverrides(frame_tree_node);
  if (!agent_host) {
    return false;
  }

  bool result = false;
  for (auto* emulation : protocol::EmulationHandler::ForAgentHost(agent_host)) {
    result = emulation->ApplyUserAgentMetadataOverrides(override_out) || result;
  }

  return result;
}

bool ApplyNetworkCookieControlsOverrides(
    RenderFrameHostImpl& rfh,
    net::CookieSettingOverrides& overrides) {
  FrameTreeNode* ftn = rfh.frame_tree_node();
  if (!ftn) {
    return false;
  }
  DevToolsAgentHostImpl* agent_host =
      GetDevToolsAgentHostForNetworkOverrides(ftn);

  return ApplyNetworkCookieControlsOverrides(agent_host, overrides);
}

bool ApplyNetworkCookieControlsOverrides(
    DevToolsAgentHostImpl* agent_host,
    net::CookieSettingOverrides& overrides) {
  if (!agent_host) {
    return false;
  }
  for (auto* network : protocol::NetworkHandler::ForAgentHost(agent_host)) {
    if (network->enabled()) {
      network->ApplyCookieControlsOverrides(overrides);
    }
  }
  return !overrides.empty();
}

namespace {
template <typename HandlerType>
bool MaybeCreateProxyForInterception(
    DevToolsAgentHostImpl* agent_host,
    int process_id,
    StoragePartition* storage_partition,
    const base::UnguessableToken& frame_token,
    bool is_navigation,
    bool is_download,
    network::mojom::URLLoaderFactoryOverride* agent_override) {
  if (!agent_host) {
    return false;
  }
  bool had_interceptors = false;
  const auto& handlers = HandlerType::ForAgentHost(agent_host);
  for (const auto& handler : base::Reversed(handlers)) {
    had_interceptors |= handler->MaybeCreateProxyForInterception(
        process_id, storage_partition, frame_token, is_navigation, is_download,
        agent_override);
  }
  return had_interceptors;
}

}  // namespace

bool WillCreateURLLoaderFactoryParams::Run(
    bool is_navigation,
    bool is_download,
    network::URLLoaderFactoryBuilder& factory_builder,
    network::mojom::URLLoaderFactoryOverridePtr* factory_override) {
  CHECK(!is_download || is_navigation);

  network::mojom::URLLoaderFactoryOverride devtools_override;
  // If caller passed some existing overrides, use those.
  // Otherwise, use our local var, then if handlers actually
  // decide to intercept, move it to |factory_override|.
  network::mojom::URLLoaderFactoryOverride* handler_override =
      factory_override && *factory_override ? factory_override->get()
                                            : &devtools_override;

  // Order of targets and sessions matters -- the latter proxy is created,
  // the closer it is to the network. So start with frame's NetworkHandler,
  // then process frame's FetchHandler and then browser's FetchHandler.
  // Within the target, the agents added earlier are closer to network.
  bool had_interceptors =
      MaybeCreateProxyForInterception<protocol::NetworkHandler>(
          agent_host_, process_id_, storage_partition_, devtools_token_,
          is_navigation, is_download, handler_override);

  had_interceptors |= MaybeCreateProxyForInterception<protocol::FetchHandler>(
      agent_host_, process_id_, storage_partition_, devtools_token_,
      is_navigation, is_download, handler_override);

  // TODO(caseq): assure deterministic order of browser agents (or sessions).
  for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances()) {
    had_interceptors |= MaybeCreateProxyForInterception<protocol::FetchHandler>(
        browser_agent_host, process_id_, storage_partition_, devtools_token_,
        is_navigation, is_download, handler_override);
  }
  if (!had_interceptors) {
    return false;
  }
  CHECK(handler_override->overriding_factory);
  CHECK(handler_override->overridden_factory_receiver);
  if (!factory_override) {
    // Not a subresource navigation, so just override the target receiver.
    auto [receiver, remote] = factory_builder.Append();
    mojo::FusePipes(std::move(receiver),
                    std::move(devtools_override.overriding_factory));
    mojo::FusePipes(std::move(devtools_override.overridden_factory_receiver),
                    std::move(remote));
  } else if (!*factory_override) {
    // No other overrides, so just returns ours as is.
    *factory_override = network::mojom::URLLoaderFactoryOverride::New(
        std::move(devtools_override.overriding_factory),
        std::move(devtools_override.overridden_factory_receiver),
        /*skip_cors_enabled_scheme_check=*/false);
  }
  // ... else things are already taken care of, as handler_override was pointing
  // to factory override and we've done all magic in-place.
  CHECK(!devtools_override.overriding_factory);
  CHECK(!devtools_override.overridden_factory_receiver);

  return true;
}

WillCreateURLLoaderFactoryParams::WillCreateURLLoaderFactoryParams(
    DevToolsAgentHostImpl* agent_host,
    const base::UnguessableToken& devtools_token,
    int process_id,
    StoragePartition* storage_partition)
    : agent_host_(agent_host),
      devtools_token_(devtools_token),
      process_id_(process_id),
      storage_partition_(storage_partition) {}

WillCreateURLLoaderFactoryParams WillCreateURLLoaderFactoryParams::ForFrame(
    RenderFrameHostImpl* rfh) {
  return WillCreateURLLoaderFactoryParams(
      RenderFrameDevToolsAgentHost::GetFor(rfh), rfh->GetDevToolsFrameToken(),
      rfh->GetProcess()->GetDeprecatedID(),
      rfh->GetProcess()->GetStoragePartition());
}

WillCreateURLLoaderFactoryParams
WillCreateURLLoaderFactoryParams::ForServiceWorker(RenderProcessHost& rph,
                                                   int routing_id) {
  ServiceWorkerDevToolsAgentHost* agent_host =
      ServiceWorkerDevToolsManager::GetInstance()
          ->GetDevToolsAgentHostForWorker(rph.GetDeprecatedID(), routing_id);
  CHECK(agent_host);
  return WillCreateURLLoaderFactoryParams(
      agent_host, agent_host->devtools_worker_token(), rph.GetDeprecatedID(),
      rph.GetStoragePartition());
}

std::optional<WillCreateURLLoaderFactoryParams>
WillCreateURLLoaderFactoryParams::ForServiceWorkerMainScript(
    const ServiceWorkerContextWrapper* context_wrapper,
    std::optional<int64_t> version_id) {
  if (!version_id.has_value()) {
    return std::nullopt;
  }

  // If we have a version_id, we are fetching a worker main script. We have a
  // DevtoolsAgentHost ready for the worker and we can add the devtools override
  // before instantiating the URLFactoryLoader.
  ServiceWorkerDevToolsAgentHost* agent_host =
      ServiceWorkerDevToolsManager::GetInstance()
          ->GetDevToolsAgentHostForNewInstallingWorker(context_wrapper,
                                                       *version_id);
  CHECK(agent_host);
  return WillCreateURLLoaderFactoryParams(
      agent_host, agent_host->devtools_worker_token(),
      ChildProcessHost::kInvalidUniqueID, context_wrapper->storage_partition());
}

std::optional<WillCreateURLLoaderFactoryParams>
WillCreateURLLoaderFactoryParams::ForSharedWorker(SharedWorkerHost* host) {
  auto* agent_host = SharedWorkerDevToolsAgentHost::GetFor(host);
  if (!agent_host) {
    return std::nullopt;
  }
  RenderProcessHost* rph = agent_host->GetProcessHost();
  CHECK(rph);
  return WillCreateURLLoaderFactoryParams(
      agent_host, agent_host->devtools_worker_token(), rph->GetDeprecatedID(),
      rph->GetStoragePartition());
}

WillCreateURLLoaderFactoryParams
WillCreateURLLoaderFactoryParams::ForWorkerMainScript(
    DevToolsAgentHostImpl* agent_host,
    const base::UnguessableToken& worker_token,
    RenderFrameHostImpl& ancestor_render_frame_host) {
  // Use the ancestor frame's interceptor to align with the interception
  // behavior in the renderer that reuses the same url loader factory from
  // the ancestor frame for the worker.
  return WillCreateURLLoaderFactoryParams::ForFrame(
      &ancestor_render_frame_host);
}

void OnAuctionWorkletNetworkRequestWillBeSent(
    FrameTreeNodeId frame_tree_node_id,
    const network::ResourceRequest& request,
    base::TimeTicks timestamp) {
  if (request.devtools_request_id->empty()) {
    return;
  }

  network::mojom::URLRequestDevToolsInfoPtr request_info =
      network::ExtractDevToolsInfo(request);

  GURL initiator_url;
  if (request.request_initiator.has_value()) {
    initiator_url = request.request_initiator->GetURL();
  }
  // if we cannot get the loader_id from the parent, use an empty string.
  std::string loader_id = "";
  if (frame_tree_node_id) {
    FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);

    if (ftn == nullptr) {
      return;
    }
    const std::optional<base::UnguessableToken>& devtools_navigation_token =
        ftn->current_frame_host()->GetDevToolsNavigationToken();

    if (devtools_navigation_token.has_value()) {
      loader_id = devtools_navigation_token->ToString();
    }

    DispatchToAgents(
        frame_tree_node_id, &protocol::NetworkHandler::RequestSent,
        /*request_id=*/request.devtools_request_id.value(),
        /*loader_id=*/loader_id, request.headers, *request_info,
        /*initiator_type=*/protocol::Network::Initiator::TypeEnum::Other,
        initiator_url,
        /*initiator_devtools_request_id=*/"", /*frame_token=*/std::nullopt,
        timestamp);
  }
}

void OnAuctionWorkletNetworkResponseReceived(
    FrameTreeNodeId frame_tree_node_id,
    const std::string& request_id,
    const std::string& loader_id,
    const GURL& request_url,
    const network::mojom::URLResponseHead& headers) {
  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(headers);
  DispatchToAgents(frame_tree_node_id,
                   &protocol::NetworkHandler::ResponseReceived, request_id,
                   loader_id, request_url,
                   /*resource_type=*/protocol::Network::ResourceTypeEnum::Other,
                   *head_info, base::ToString(frame_tree_node_id));
}

void OnAuctionWorkletNetworkRequestComplete(
    FrameTreeNodeId frame_tree_node_id,
    const std::string& request_id,
    const network::URLLoaderCompletionStatus& status) {
  DispatchToAgents(frame_tree_node_id,
                   &protocol::NetworkHandler::LoadingComplete, request_id,
                   /*resource_type=*/protocol::Network::ResourceTypeEnum::Other,
                   status);
}

bool NeedInterestGroupAuctionEvents(FrameTreeNodeId frame_tree_node_id) {
  FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (!ftn) {
    return false;
  }
  DevToolsAgentHostImpl* agent_host = RenderFrameDevToolsAgentHost::GetFor(ftn);
  if (!agent_host) {
    return false;
  }
  for (auto* storage : protocol::StorageHandler::ForAgentHost(agent_host)) {
    if (storage->interest_group_auction_tracking_enabled()) {
      return true;
    }
  }
  return false;
}

void OnInterestGroupAuctionEventOccurred(
    FrameTreeNodeId frame_tree_node_id,
    base::Time event_time,
    InterestGroupAuctionEventType type,
    const std::string& unique_auction_id,
    base::optional_ref<const std::string> parent_auction_id,
    const base::Value::Dict& auction_config) {
  DispatchToAgents(
      frame_tree_node_id,
      &protocol::StorageHandler::NotifyInterestGroupAuctionEventOccurred,
      event_time, type, unique_auction_id, parent_auction_id, auction_config);
}

void OnInterestGroupAuctionNetworkRequestCreated(
    FrameTreeNodeId frame_tree_node_id,
    InterestGroupAuctionFetchType type,
    const std::string& request_id,
    const std::vector<std::string>& devtools_auction_ids) {
  DispatchToAgents(frame_tree_node_id,
                   &protocol::StorageHandler::
                       NotifyInterestGroupAuctionNetworkRequestCreated,
                   type, request_id, devtools_auction_ids);
}

void OnNavigationRequestWillBeSent(
    const NavigationRequest& navigation_request) {
  // Note this intentionally deviates from the usual instrumentation signal
  // logic and dispatches to all agents upwards from the frame, to make sure
  // the security checks are properly applied even if no DevTools session is
  // established for the navigated frame itself. This is because the page
  // agent may navigate all of its subframes currently.
  for (RenderFrameHostImpl* rfh =
           navigation_request.frame_tree_node()->current_frame_host();
       rfh; rfh = rfh->GetParentOrOuterDocument()) {
    // Only check frames that qualify as DevTools targets, i.e. (local)? roots.
    if (!RenderFrameDevToolsAgentHost::ShouldCreateDevToolsForHost(rfh)) {
      continue;
    }
    auto* agent_host = static_cast<RenderFrameDevToolsAgentHost*>(
        RenderFrameDevToolsAgentHost::GetFor(rfh));
    if (!agent_host) {
      continue;
    }
    agent_host->OnNavigationRequestWillBeSent(navigation_request);
  }

  // We use CachedNavigationURLLoader for page activation (BFCache navigations
  // and Prerender activations) and don't actually send a network request, so we
  // don't report this request to DevTools.
  if (navigation_request.IsPageActivation()) {
    return;
  }

  // Make sure both back-ends yield the same timestamp.
  auto timestamp = base::TimeTicks::Now();
  DispatchToAgents(navigation_request.frame_tree_node(),
                   &protocol::NetworkHandler::NavigationRequestWillBeSent,
                   navigation_request, timestamp);
  TRACE_EVENT_INSTANT_WITH_TIMESTAMP1(
      "devtools.timeline", "ResourceWillSendRequest", TRACE_EVENT_SCOPE_PROCESS,
      timestamp, "data",
      inspector_will_send_navigation_request_event::Data(
          navigation_request.devtools_navigation_token()));
}

// Notify the provided agent host of a certificate error. Returns true if one of
// the host's handlers will handle the certificate error.
bool NotifyCertificateError(DevToolsAgentHost* host,
                            int cert_error,
                            const GURL& request_url,
                            const CertErrorCallback& callback) {
  DevToolsAgentHostImpl* host_impl = static_cast<DevToolsAgentHostImpl*>(host);
  for (auto* security_handler :
       protocol::SecurityHandler::ForAgentHost(host_impl)) {
    if (security_handler->NotifyCertificateError(cert_error, request_url,
                                                 callback)) {
      return true;
    }
  }
  return false;
}

bool HandleCertificateError(WebContents* web_contents,
                            int cert_error,
                            const GURL& request_url,
                            CertErrorCallback callback) {
  if (!DevToolsAgentHost::HasFor(web_contents)) {
    return false;
  }
  scoped_refptr<DevToolsAgentHost> agent_host =
      DevToolsAgentHost::GetOrCreateFor(web_contents).get();
  if (NotifyCertificateError(agent_host.get(), cert_error, request_url,
                             callback)) {
    // Only allow a single agent host to handle the error.
    callback.Reset();
  }

  for (auto* browser_agent_host : BrowserDevToolsAgentHost::Instances()) {
    if (NotifyCertificateError(browser_agent_host, cert_error, request_url,
                               callback)) {
      // Only allow a single agent host to handle the error.
      callback.Reset();
    }
  }
  return !callback;
}

void FencedFrameCreated(
    base::SafeRef<RenderFrameHostImpl> owner_render_frame_host,
    FencedFrame* fenced_frame) {
  auto* agent_host = static_cast<RenderFrameDevToolsAgentHost*>(
      RenderFrameDevToolsAgentHost::GetFor(
          owner_render_frame_host->frame_tree_node()));
  if (!agent_host) {
    return;
  }
  agent_host->DidCreateFencedFrame(fenced_frame);
}

void WillStartDragging(FrameTreeNode* main_frame_tree_node,
                       const DropData& drop_data,
                       const blink::mojom::DragDataPtr drag_data,
                       blink::DragOperationsMask drag_operations_mask,
                       bool* intercepted) {
  DCHECK(main_frame_tree_node->frame_tree().root() == main_frame_tree_node);
  DispatchToAgents(main_frame_tree_node, &protocol::InputHandler::StartDragging,
                   drop_data, *drag_data, drag_operations_mask, intercepted);
}

void DragEnded(FrameTreeNode& node) {
  DCHECK(node.frame_tree().root() == &node);
  DispatchToAgents(&node, &protocol::InputHandler::DragEnded);
}

namespace {
std::unique_ptr<protocol::Array<protocol::String>> BuildExclusionReasons(
    net::CookieInclusionStatus status) {
  auto exclusion_reasons =
      std::make_unique<protocol::Array<protocol::String>>();
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX)) {
    exclusion_reasons->push_back(protocol::Audits::CookieExclusionReasonEnum::
                                     ExcludeSameSiteUnspecifiedTreatedAsLax);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_NONE_INSECURE)) {
    exclusion_reasons->push_back(protocol::Audits::CookieExclusionReasonEnum::
                                     ExcludeSameSiteNoneInsecure);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SAMESITE_LAX)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludeSameSiteLax);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_STRICT)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludeSameSiteStrict);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_DOMAIN_NON_ASCII)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludeDomainNonASCII);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::
            ExcludeThirdPartyCookieBlockedInFirstPartySet);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_THIRD_PARTY_PHASEOUT)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludeThirdPartyPhaseout);
  }

  if (base::FeatureList::IsEnabled(net::features::kEnablePortBoundCookies) &&
      status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_PORT_MISMATCH)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludePortMismatch);
  }

  if (base::FeatureList::IsEnabled(net::features::kEnableSchemeBoundCookies) &&
      status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SCHEME_MISMATCH)) {
    exclusion_reasons->push_back(
        protocol::Audits::CookieExclusionReasonEnum::ExcludeSchemeMismatch);
  }

  return exclusion_reasons;
}

std::unique_ptr<protocol::Array<protocol::String>> BuildWarningReasons(
    net::CookieInclusionStatus status) {
  auto warning_reasons = std::make_unique<protocol::Array<protocol::String>>();
  if (status.HasWarningReason(net::CookieInclusionStatus::WarningReason::
                                  WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnAttributeValueExceedsMaxSize);
  }
  if (status.HasWarningReason(
          net::CookieInclusionStatus::WarningReason::
              WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteUnspecifiedCrossSiteContext);
  }
  if (status.HasWarningReason(net::CookieInclusionStatus::WarningReason::
                                  WARN_SAMESITE_NONE_INSECURE)) {
    warning_reasons->push_back(
        protocol::Audits::CookieWarningReasonEnum::WarnSameSiteNoneInsecure);
  }
  if (status.HasWarningReason(net::CookieInclusionStatus::WarningReason::
                                  WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteUnspecifiedLaxAllowUnsafe);
  }

  // There can only be one of the following warnings.
  if (status.HasWarningReason(net::CookieInclusionStatus::WarningReason::
                                  WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteStrictLaxDowngradeStrict);
  } else if (status.HasWarningReason(
                 net::CookieInclusionStatus::WarningReason::
                     WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteStrictCrossDowngradeStrict);
  } else if (status.HasWarningReason(
                 net::CookieInclusionStatus::WarningReason::
                     WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteStrictCrossDowngradeLax);
  } else if (status.HasWarningReason(
                 net::CookieInclusionStatus::WarningReason::
                     WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteLaxCrossDowngradeStrict);
  } else if (status.HasWarningReason(
                 net::CookieInclusionStatus::WarningReason::
                     WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE)) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnSameSiteLaxCrossDowngradeLax);
  }

  if (status.HasWarningReason(
          net::CookieInclusionStatus::WarningReason::WARN_DOMAIN_NON_ASCII)) {
    warning_reasons->push_back(
        protocol::Audits::CookieWarningReasonEnum::WarnDomainNonASCII);
  }

  if (status.HasWarningReason(net::CookieInclusionStatus::WarningReason::
                                  WARN_THIRD_PARTY_PHASEOUT)) {
    warning_reasons->push_back(
        protocol::Audits::CookieWarningReasonEnum::WarnThirdPartyPhaseout);
  }

  if (status.exemption_reason() ==
      net::CookieInclusionStatus::ExemptionReason::k3PCDMetadata) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnDeprecationTrialMetadata);
  } else if (status.exemption_reason() ==
             net::CookieInclusionStatus::ExemptionReason::k3PCDHeuristics) {
    warning_reasons->push_back(protocol::Audits::CookieWarningReasonEnum::
                                   WarnThirdPartyCookieHeuristic);
  }

  // This warning only affects cookies when the corresponding feature is
  // enabled, therefore we should only create an issue for it then.
  if (base::FeatureList::IsEnabled(
          net::features::kCookieSameSiteConsidersRedirectChain) &&
      status.HasWarningReason(
          net::CookieInclusionStatus::WarningReason::
              WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
    warning_reasons->push_back(
        protocol::Audits::CookieWarningReasonEnum::
            WarnCrossSiteRedirectDowngradeChangesInclusion);
  }

  return warning_reasons;
}

protocol::String BuildCookieOperation(blink::mojom::CookieOperation operation) {
  switch (operation) {
    case blink::mojom::CookieOperation::kReadCookie:
      return protocol::Audits::CookieOperationEnum::ReadCookie;
    case blink::mojom::CookieOperation::kSetCookie:
      return protocol::Audits::CookieOperationEnum::SetCookie;
  }
}

std::unique_ptr<protocol::Audits::InspectorIssue>
BuildCookieDeprecationMetadataIssue(
    const blink::mojom::CookieDeprecationMetadataIssueDetailsPtr&
        issue_details) {
  auto metadata_issue_details =
      protocol::Audits::CookieDeprecationMetadataIssueDetails::Create()
          .SetAllowedSites(std::make_unique<protocol::Array<protocol::String>>(
              issue_details->allowed_sites))
          .SetOptOutPercentage(issue_details->opt_out_percentage)
          .SetIsOptOutTopLevel(issue_details->is_opt_out_top_level)
          .SetOperation(BuildCookieOperation(issue_details->operation))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetCookieDeprecationMetadataIssueDetails(
              std::move(metadata_issue_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                CookieDeprecationMetadataIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();

  return issue;
}

std::unique_ptr<protocol::Audits::CookieIssueInsight> BuildCookieIssueInsight(
    std::string_view cookie_domain,
    const net::CookieInclusionStatus& status) {
  std::optional<CookieIssueInsight> insight =
      CookieInsightListHandler::GetInstance().GetInsight(cookie_domain, status);
  if (!insight.has_value()) {
    return nullptr;
  }

  switch (insight->type) {
    case InsightType::kGitHubResource:
      return protocol::Audits::CookieIssueInsight::Create()
          .SetType(protocol::Audits::InsightTypeEnum::GitHubResource)
          .SetTableEntryUrl(insight->domain_info.entry_url)
          .Build();
    case InsightType::kGracePeriod:
      return protocol::Audits::CookieIssueInsight::Create()
          .SetType(protocol::Audits::InsightTypeEnum::GracePeriod)
          .Build();
    case InsightType::kHeuristics:
      return protocol::Audits::CookieIssueInsight::Create()
          .SetType(protocol::Audits::InsightTypeEnum::Heuristics)
          .Build();
    default:
      NOTREACHED();
  }
}

}  // namespace

void ReportCookieIssue(
    RenderFrameHostImpl* render_frame_host_impl,
    const network::mojom::CookieOrLineWithAccessResultPtr& excluded_cookie,
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    blink::mojom::CookieOperation operation,
    const std::optional<std::string>& devtools_request_id,
    const std::optional<std::string>& devtools_issue_id) {
  auto exclusion_reasons =
      BuildExclusionReasons(excluded_cookie->access_result.status);
  auto warning_reasons =
      BuildWarningReasons(excluded_cookie->access_result.status);
  if (exclusion_reasons->empty() && warning_reasons->empty()) {
    // If we don't report any reason, there is no point in informing DevTools.
    return;
  }

  std::unique_ptr<protocol::Audits::AffectedRequest> affected_request;
  if (devtools_request_id) {
    // We can report the url here, because if devtools_request_id is set, the
    // url is the url of the request.
    affected_request = protocol::Audits::AffectedRequest::Create()
                           .SetRequestId(*devtools_request_id)
                           .SetUrl(url.spec())
                           .Build();
  }

  auto cookie_issue_details =
      protocol::Audits::CookieIssueDetails::Create()
          .SetCookieExclusionReasons(std::move(exclusion_reasons))
          .SetCookieWarningReasons(std::move(warning_reasons))
          .SetOperation(BuildCookieOperation(operation))
          .SetCookieUrl(url.spec())
          .SetRequest(std::move(affected_request))
          .Build();

  if (excluded_cookie->cookie_or_line->is_cookie()) {
    const auto& cookie = excluded_cookie->cookie_or_line->get_cookie();
    auto affected_cookie = protocol::Audits::AffectedCookie::Create()
                               .SetName(cookie.Name())
                               .SetPath(cookie.Path())
                               .SetDomain(cookie.Domain())
                               .Build();
    cookie_issue_details->SetCookie(std::move(affected_cookie));

    cookie_issue_details->SetInsight(BuildCookieIssueInsight(
        cookie.DomainWithoutDot(), excluded_cookie->access_result.status));
  } else {
    CHECK(excluded_cookie->cookie_or_line->is_cookie_string());
    cookie_issue_details->SetRawCookieLine(
        excluded_cookie->cookie_or_line->get_cookie_string());
  }

  if (!site_for_cookies.IsNull()) {
    cookie_issue_details->SetSiteForCookies(
        site_for_cookies.RepresentativeUrl().spec());
  }

  auto details = protocol::Audits::InspectorIssueDetails::Create()
                     .SetCookieIssueDetails(std::move(cookie_issue_details))
                     .Build();

  auto issue =
      protocol::Audits::InspectorIssue::Create()
          .SetCode(protocol::Audits::InspectorIssueCodeEnum::CookieIssue)
          .SetDetails(std::move(details))
          .Build();
  if (devtools_issue_id.has_value()) {
    issue->SetIssueId(devtools_issue_id.value());
  }

  ReportBrowserInitiatedIssue(render_frame_host_impl, std::move(issue));
}

namespace {

const protocol::Audits::InspectorIssue& AddIssueToIssueStorage(
    RenderFrameHost* rfh,
    std::unique_ptr<protocol::Audits::InspectorIssue> issue) {
  // We only utilize a central storage on the page. Each issue is still
  // associated with the originating |RenderFrameHost| though.
  DevToolsIssueStorage* issue_storage =
      DevToolsIssueStorage::GetOrCreateForPage(
          rfh->GetOutermostMainFrame()->GetPage());

  return issue_storage->AddInspectorIssue(rfh, std::move(issue));
}

}  // namespace

namespace {

std::unique_ptr<protocol::Audits::InspectorIssue>
BuildUserReidentificationIssue(
    const blink::mojom::UserReidentificationIssueDetailsPtr& issue_details) {
  auto affected_request = issue_details->request.is_null()
                              ? nullptr
                              : protocol::Audits::AffectedRequest::Create()
                                    .SetUrl(issue_details->request->url)
                                    .Build();
  auto source_code_location =
      issue_details->sourceCodeLocation.is_null()
          ? nullptr
          : protocol::Audits::SourceCodeLocation::Create()
                .SetUrl(issue_details->sourceCodeLocation->url.value())
                .SetLineNumber(issue_details->sourceCodeLocation->line)
                .SetColumnNumber(issue_details->sourceCodeLocation->column)
                .Build();
  std::string issue_type;
  switch (issue_details->type) {
    case blink::mojom::UserReidentificationIssueType::kBlockedFrameNavigation:
      issue_type = protocol::Audits::UserReidentificationIssueTypeEnum::
          BlockedFrameNavigation;
      break;
    case blink::mojom::UserReidentificationIssueType::kBlockedSubresource:
      issue_type = protocol::Audits::UserReidentificationIssueTypeEnum::
          BlockedSubresource;
      break;
    case blink::mojom::UserReidentificationIssueType::kNoisedCanvasReadback:
      issue_type = protocol::Audits::UserReidentificationIssueTypeEnum::
          NoisedCanvasReadback;
      break;
    default:
      NOTREACHED();
  }
  auto reidentification_issue_details =
      protocol::Audits::UserReidentificationIssueDetails::Create()
          .SetType(issue_type)
          .SetRequest(std::move(affected_request))
          .Build();

  auto protocol_issue_details =
      protocol::Audits::InspectorIssueDetails::Create()
          .SetUserReidentificationIssueDetails(
              std::move(reidentification_issue_details))
          .Build();

  auto issue = protocol::Audits::InspectorIssue::Create()
                   .SetCode(protocol::Audits::InspectorIssueCodeEnum::
                                UserReidentificationIssue)
                   .SetDetails(std::move(protocol_issue_details))
                   .Build();

  return issue;
}

}  // namespace

void ReportBrowserInitiatedIssue(
    RenderFrameHostImpl* frame,
    std::unique_ptr<protocol::Audits::InspectorIssue> issue) {
  FrameTreeNode* ftn = frame->frame_tree_node();
  if (!ftn) {
    return;
  }

  const auto& issue_ptr = AddIssueToIssueStorage(frame, std::move(issue));
  DispatchToAgents(ftn, &protocol::AuditsHandler::OnIssueAdded, issue_ptr);
}

void BuildAndReportBrowserInitiatedIssue(
    RenderFrameHostImpl* frame,
    blink::mojom::InspectorIssueInfoPtr info) {
  std::unique_ptr<protocol::Audits::InspectorIssue> issue;
  if (info->code == blink::mojom::InspectorIssueCode::kHeavyAdIssue) {
    issue = BuildHeavyAdIssue(info->details->heavy_ad_issue_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kFederatedAuthRequestIssue) {
    issue = BuildFederatedAuthRequestIssue(
        info->details->federated_auth_request_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kDeprecationIssue) {
    issue = BuildDeprecationIssue(info->details->deprecation_issue_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kBounceTrackingIssue) {
    issue =
        BuildBounceTrackingIssue(info->details->bounce_tracking_issue_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kPartitioningBlobURLIssue) {
    issue = BuildPartitioningBlobURLIssue(
        info->details->partitioning_blob_url_issue_details);
  } else if (info->code == blink::mojom::InspectorIssueCode::
                               kCookieDeprecationMetadataIssue) {
    issue = BuildCookieDeprecationMetadataIssue(
        info->details->cookie_deprecation_metadata_issue_details);
  } else if (info->code == blink::mojom::InspectorIssueCode::
                               kFederatedAuthUserInfoRequestIssue) {
    issue = BuildFederatedAuthUserInfoRequestIssue(
        info->details->federated_auth_user_info_request_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kAttributionReportingIssue) {
    issue = BuildAttributionReportingIssue(
        info->details->attribution_reporting_issue_details);
  } else if (info->code ==
             blink::mojom::InspectorIssueCode::kUserReidentificationIssue) {
    issue = BuildUserReidentificationIssue(
        info->details->user_reidentification_issue_details);
  } else {
    NOTREACHED() << "Unsupported type of browser-initiated issue";
  }
  ReportBrowserInitiatedIssue(frame, std::move(issue));
}

void OnWebTransportHandshakeFailed(
    RenderFrameHostImpl* frame,
    const GURL& url,
    const std::optional<net::WebTransportError>& error) {
  FrameTreeNode* ftn = frame->frame_tree_node();
  if (!ftn) {
    return;
  }
  std::string text = base::StringPrintf(
      "Failed to establish a connection to %s", url.spec().c_str());
  if (error) {
    text += ": ";
    text += net::WebTransportErrorToString(*error);
  }
  text += ".";
  auto entry =
      protocol::Log::LogEntry::Create()
          .SetSource(protocol::Log::LogEntry::SourceEnum::Network)
          .SetLevel(protocol::Log::LogEntry::LevelEnum::Error)
          .SetText(text)
          .SetTimestamp(base::Time::Now().InMillisecondsFSinceUnixEpoch())
          .Build();
  DispatchToAgents(ftn, &protocol::LogHandler::EntryAdded, entry.get());
}

void OnServiceWorkerMainScriptFetchingFailed(
    const GlobalRenderFrameHostId& requesting_frame_id,
    const ServiceWorkerContextWrapper* context_wrapper,
    int64_t version_id,
    const std::string& error,
    const network::URLLoaderCompletionStatus& status,
    const network::mojom::URLResponseHead* response_head,
    const GURL& url) {
  DCHECK(!error.empty());
  DCHECK_NE(net::OK, status.error_code);

  // If we have a requesting_frame_id, we should have a frame and a frame tree
  // node. However since the lifetime of these objects can be complex, we check
  // at each step that we indeed can go reach all the way to the FrameTreeNode.
  if (!requesting_frame_id) {
    return;
  }

  RenderFrameHostImpl* requesting_frame =
      RenderFrameHostImpl::FromID(requesting_frame_id);
  if (!requesting_frame) {
    return;
  }

  FrameTreeNode* ftn = requesting_frame->frame_tree_node();
  if (!ftn) {
    return;
  }

  auto entry =
      protocol::Log::LogEntry::Create()
          .SetSource(protocol::Log::LogEntry::SourceEnum::Network)
          .SetLevel(protocol::Log::LogEntry::LevelEnum::Error)
          .SetText(error)
          .SetTimestamp(base::Time::Now().InMillisecondsFSinceUnixEpoch())
          .Build();
  DispatchToAgents(ftn, &protocol::LogHandler::EntryAdded, entry.get());

  ServiceWorkerDevToolsAgentHost* agent_host =
      ServiceWorkerDevToolsManager::GetInstance()
          ->GetDevToolsAgentHostForNewInstallingWorker(context_wrapper,
                                                       version_id);

  if (response_head) {
    DCHECK(agent_host);
    network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
        network::ExtractDevToolsInfo(*response_head);
    auto worker_token = agent_host->devtools_worker_token().ToString();
    for (auto* network_handler :
         protocol::NetworkHandler::ForAgentHost(agent_host)) {
      network_handler->ResponseReceived(
          worker_token, worker_token, url,
          protocol::Network::ResourceTypeEnum::Other, *head_info,
          requesting_frame->devtools_frame_token().ToString());
      network_handler->frontend()->LoadingFinished(
          worker_token,
          status.completion_time.ToInternalValue() /
              static_cast<double>(base::Time::kMicrosecondsPerSecond),
          status.encoded_data_length);
    }
  } else if (agent_host) {
    for (auto* network_handler :
         protocol::NetworkHandler::ForAgentHost(agent_host)) {
      network_handler->LoadingComplete(
          agent_host->devtools_worker_token().ToString(),
          protocol::Network::ResourceTypeEnum::Other, status);
    }
  }
}

namespace {

// Only assign request id if there's an enabled agent host.
void MaybeAssignResourceRequestId(DevToolsAgentHostImpl* host,
                                  const std::string& id,
                                  network::ResourceRequest& request) {
  DCHECK(!request.devtools_request_id.has_value());
  for (auto* network_handler : protocol::NetworkHandler::ForAgentHost(host)) {
    if (network_handler->enabled()) {
      request.devtools_request_id = id;
      return;
    }
  }
}

}  // namespace

void MaybeAssignResourceRequestId(FrameTreeNode* ftn,
                                  const std::string& id,
                                  network::ResourceRequest& request) {
  if (auto* host = RenderFrameDevToolsAgentHost::GetFor(ftn)) {
    MaybeAssignResourceRequestId(host, id, request);
  }
}

void MaybeAssignResourceRequestId(FrameTreeNodeId frame_tree_node_id,
                                  const std::string& id,
                                  network::ResourceRequest& request) {
  auto* frame_tree_node = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (frame_tree_node) {
    MaybeAssignResourceRequestId(frame_tree_node, id, request);
  }
}

void OnServiceWorkerMainScriptRequestWillBeSent(
    const GlobalRenderFrameHostId& requesting_frame_id,
    const ServiceWorkerContextWrapper* context_wrapper,
    int64_t version_id,
    network::ResourceRequest& request) {
  // Currently, `requesting_frame_id` is invalid when payment apps and
  // extensions register a service worker. See the callers of
  // ServiceWorkerContextWrapper::RegisterServiceWorker().
  if (!requesting_frame_id) {
    return;
  }

  RenderFrameHostImpl* requesting_frame =
      RenderFrameHostImpl::FromID(requesting_frame_id);
  if (!requesting_frame) {
    return;
  }

  auto timestamp = base::TimeTicks::Now();
  network::mojom::URLRequestDevToolsInfoPtr request_info =
      network::ExtractDevToolsInfo(request);

  ServiceWorkerDevToolsAgentHost* agent_host =
      ServiceWorkerDevToolsManager::GetInstance()
          ->GetDevToolsAgentHostForNewInstallingWorker(context_wrapper,
                                                       version_id);
  DCHECK(agent_host);
  const std::string request_id = agent_host->devtools_worker_token().ToString();
  MaybeAssignResourceRequestId(agent_host, request_id, request);
  for (auto* network_handler :
       protocol::NetworkHandler::ForAgentHost(agent_host)) {
    network_handler->RequestSent(request_id,
                                 /*loader_id=*/"", request.headers,
                                 *request_info,
                                 protocol::Network::Initiator::TypeEnum::Other,
                                 requesting_frame->GetLastCommittedURL(),
                                 /*initiator_devtools_request_id=*/"",
                                 /*frame_token=*/std::nullopt, timestamp);
  }
}

void OnWorkerMainScriptLoadingFailed(
    const GURL& url,
    const base::UnguessableToken& worker_token,
    FrameTreeNode* ftn,
    RenderFrameHostImpl* ancestor_rfh,
    const network::URLLoaderCompletionStatus& status) {
  DCHECK(ftn);

  std::string id = worker_token.ToString();

  if (status.blocked_by_response_reason) {
    ReportBlockedByResponseIssue(url, id, ftn, ancestor_rfh, status);
  }

  DispatchToAgents(ftn, &protocol::NetworkHandler::LoadingComplete, id,
                   protocol::Network::ResourceTypeEnum::Other, status);
}

void OnWorkerMainScriptRequestWillBeSent(
    RenderFrameHostImpl& ancestor_frame_host,
    const base::UnguessableToken& worker_token,
    network::ResourceRequest& request) {
  FrameTreeNode* ftn = ancestor_frame_host.frame_tree_node();

  auto timestamp = base::TimeTicks::Now();
  network::mojom::URLRequestDevToolsInfoPtr request_info =
      network::ExtractDevToolsInfo(request);

  auto* owner_host = RenderFrameDevToolsAgentHost::GetFor(ftn);
  if (!owner_host) {
    return;
  }
  MaybeAssignResourceRequestId(owner_host, worker_token.ToString(), request);

  // Note: we apply overrides from the owner frame to match the behavior in the
  // renderer.
  bool disable_cache = false;
  ApplyNetworkRequestOverrides(owner_host, &request.headers, &disable_cache,
                               nullptr, &request.skip_service_worker,
                               &request.devtools_accepted_stream_types, nullptr,
                               nullptr, nullptr);
  if (disable_cache) {
    request.load_flags &=
        ~(net::LOAD_VALIDATE_CACHE | net::LOAD_SKIP_CACHE_VALIDATION |
          net::LOAD_ONLY_FROM_CACHE | net::LOAD_DISABLE_CACHE);
    request.load_flags |= net::LOAD_BYPASS_CACHE;
  }

  DispatchToAgents(
      ftn, &protocol::NetworkHandler::RequestSent, worker_token.ToString(),
      /*loader_id=*/"", request.headers, *request_info,
      protocol::Network::Initiator::TypeEnum::Other, ftn->current_url(),
      /*initiator_devtools_request_id*/ "",
      ancestor_frame_host.devtools_frame_token(), timestamp);
}

void LogWorkletMessage(RenderFrameHostImpl& frame_host,
                       blink::mojom::ConsoleMessageLevel log_level,
                       const std::string& message) {
  FrameTreeNode* ftn = frame_host.frame_tree_node();
  if (!ftn) {
    return;
  }

  std::string log_level_string;
  switch (log_level) {
    case blink::mojom::ConsoleMessageLevel::kVerbose:
      log_level_string = protocol::Log::LogEntry::LevelEnum::Verbose;
      break;
    case blink::mojom::ConsoleMessageLevel::kInfo:
      log_level_string = protocol::Log::LogEntry::LevelEnum::Info;
      break;
    case blink::mojom::ConsoleMessageLevel::kWarning:
      log_level_string = protocol::Log::LogEntry::LevelEnum::Warning;
      break;
    case blink::mojom::ConsoleMessageLevel::kError:
      log_level_string = protocol::Log::LogEntry::LevelEnum::Error;
      break;
  }

  DCHECK(!log_level_string.empty());

  auto entry =
      protocol::Log::LogEntry::Create()
          .SetSource(protocol::Log::LogEntry::SourceEnum::Other)
          .SetLevel(log_level_string)
          .SetText(message)
          .SetTimestamp(base::Time::Now().InMillisecondsFSinceUnixEpoch())
          .Build();
  DispatchToAgents(ftn, &protocol::LogHandler::EntryAdded, entry.get());

  // Manually trigger RenderFrameHostImpl::DidAddMessageToConsole, so that the
  // observer behavior aligns more with the observer behavior for the regular
  // devtools logging path from the renderer.
#if BUILDFLAG(ARKWEB_CONSOLE_LOGGING)
  frame_host.DidAddMessageToConsoleV2(log_level, blink::mojom::ConsoleMessageSource::kOther, base::UTF8ToUTF16(message),
#else
  frame_host.DidAddMessageToConsole(log_level, base::UTF8ToUTF16(message),
#endif
                                    /*line_no=*/0, /*source_id=*/{},
                                    /*untrusted_stack_trace=*/{});
}

void ApplyNetworkContextParamsOverrides(
    BrowserContext* browser_context,
    network::mojom::NetworkContextParams* context_params) {
  for (auto* agent_host : BrowserDevToolsAgentHost::Instances()) {
    for (auto* target_handler :
         protocol::TargetHandler::ForAgentHost(agent_host)) {
      target_handler->ApplyNetworkContextParamsOverrides(browser_context,
                                                         context_params);
    }
  }
}

protocol::Audits::GenericIssueErrorType GenericIssueErrorTypeToProtocol(
    blink::mojom::GenericIssueErrorType error_type) {
  switch (error_type) {
    case blink::mojom::GenericIssueErrorType::kFormLabelForNameError:
      return protocol::Audits::GenericIssueErrorTypeEnum::FormLabelForNameError;
    case blink::mojom::GenericIssueErrorType::kFormDuplicateIdForInputError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormDuplicateIdForInputError;
    case blink::mojom::GenericIssueErrorType::kFormInputWithNoLabelError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormInputWithNoLabelError;
    case blink::mojom::GenericIssueErrorType::
        kFormAutocompleteAttributeEmptyError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormAutocompleteAttributeEmptyError;
    case blink::mojom::GenericIssueErrorType::
        kFormEmptyIdAndNameAttributesForInputError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormEmptyIdAndNameAttributesForInputError;
    case blink::mojom::GenericIssueErrorType::
        kFormAriaLabelledByToNonExistingIdError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormAriaLabelledByToNonExistingIdError;
    case blink::mojom::GenericIssueErrorType::
        kFormInputAssignedAutocompleteValueToIdOrNameAttributeError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormInputAssignedAutocompleteValueToIdOrNameAttributeError;
    case blink::mojom::GenericIssueErrorType::
        kFormLabelHasNeitherForNorNestedInputError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormLabelHasNeitherForNorNestedInputError;
    case blink::mojom::GenericIssueErrorType::
        kFormLabelForMatchesNonExistingIdError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormLabelForMatchesNonExistingIdError;
    case blink::mojom::GenericIssueErrorType::
        kFormInputHasWrongButWellIntendedAutocompleteValueError:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          FormInputHasWrongButWellIntendedAutocompleteValueError;
    case blink::mojom::GenericIssueErrorType::kResponseWasBlockedByORB:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          ResponseWasBlockedByORB;
    case blink::mojom::GenericIssueErrorType::kNavigationEntryMarkedSkippable:
      return protocol::Audits::GenericIssueErrorTypeEnum::
          NavigationEntryMarkedSkippable;
  }
}

void UpdateDeviceRequestPrompt(RenderFrameHost* render_frame_host,
                               DevtoolsDeviceRequestPromptInfo* prompt_info) {
  FrameTreeNode* ftn = FrameTreeNode::From(render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn,
                   &protocol::DeviceAccessHandler::UpdateDeviceRequestPrompt,
                   prompt_info);
}

void CleanUpDeviceRequestPrompt(RenderFrameHost* render_frame_host,
                                DevtoolsDeviceRequestPromptInfo* prompt_info) {
  FrameTreeNode* ftn = FrameTreeNode::From(render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn,
                   &protocol::DeviceAccessHandler::CleanUpDeviceRequestPrompt,
                   prompt_info);
}

void WillSendFedCmRequest(RenderFrameHost& render_frame_host,
                          bool* intercept,
                          bool* disable_delay) {
  FrameTreeNode* ftn = FrameTreeNode::From(&render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn, &protocol::FedCmHandler::WillSendRequest, intercept,
                   disable_delay);
}

void WillShowFedCmDialog(RenderFrameHost& render_frame_host, bool* intercept) {
  FrameTreeNode* ftn = FrameTreeNode::From(&render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn, &protocol::FedCmHandler::WillShowDialog, intercept);
}

void DidShowFedCmDialog(RenderFrameHost& render_frame_host) {
  FrameTreeNode* ftn = FrameTreeNode::From(&render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn, &protocol::FedCmHandler::DidShowDialog);
}

void DidCloseFedCmDialog(RenderFrameHost& render_frame_host) {
  FrameTreeNode* ftn = FrameTreeNode::From(&render_frame_host);
  if (!ftn) {
    return;
  }
  DispatchToAgents(ftn, &protocol::FedCmHandler::DidCloseDialog);
}

void WillSendFedCmNetworkRequest(
    FrameTreeNodeId frame_tree_node_id,
    const network::ResourceRequest& request,
    const std::optional<std::string>& request_body) {
  FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (!ftn) {
    return;
  }

  // Get the DevTools navigation token from the current frame host
  const std::optional<base::UnguessableToken>& loader_id =
      ftn->current_frame_host()->GetDevToolsNavigationToken();

  if (!loader_id.has_value()) {
    return;
  }

  std::optional<base::UnguessableToken> frame_token =
      ftn->current_frame_host()->devtools_frame_token();

  GURL initiator_url;
  if (request.request_initiator.has_value()) {
    initiator_url = request.request_initiator->GetURL();
  }

  DispatchToAgents(frame_tree_node_id,
                   &protocol::NetworkHandler::FedCmRequestWillBeSent,
                   request.devtools_request_id.value(),
                   loader_id.value().ToString(), request, request_body,
                   initiator_url, frame_token, base::TimeTicks::Now());
}

void DidReceiveFedCmNetworkResponse(
    FrameTreeNodeId frame_tree_node_id,
    const std::string& devtools_request_id,
    const GURL& url,
    const network::mojom::URLResponseHead* response_head,
    const std::string& response_body,
    const network::URLLoaderCompletionStatus& status) {
  FrameTreeNode* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
  if (!ftn) {
    return;
  }

  // Get the DevTools navigation token from the current frame host
  const std::optional<base::UnguessableToken>& loader_id =
      ftn->current_frame_host()->GetDevToolsNavigationToken();

  if (!loader_id.has_value()) {
    return;
  }

  if (response_head) {
    network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
        network::ExtractDevToolsInfo(*response_head);

    std::optional<base::UnguessableToken> frame_token =
        ftn->current_frame_host()->devtools_frame_token();

    DispatchToAgents(frame_tree_node_id,
                     &protocol::NetworkHandler::ResponseReceived,
                     devtools_request_id, loader_id.value().ToString(), url,
                     protocol::Network::ResourceTypeEnum::FedCM, *head_info,
                     frame_token.value().ToString());
  }

  if (status.error_code == net::OK) {
    DispatchToAgents(frame_tree_node_id,
                     &protocol::NetworkHandler::BodyDataReceived,
                     devtools_request_id, response_body,
                     /*is_base64_encoded=*/false);
  }

  DispatchToAgents(
      frame_tree_node_id, &protocol::NetworkHandler::LoadingComplete,
      devtools_request_id, protocol::Network::ResourceTypeEnum::FedCM, status);
}

void OnFencedFrameReportRequestSent(
    FrameTreeNodeId initiator_frame_tree_node_id,
    const std::string& devtools_request_id,
    network::ResourceRequest& request,
    const std::string& event_data) {
  DispatchToAgents(initiator_frame_tree_node_id,
                   &protocol::NetworkHandler::FencedFrameReportRequestSent,
                   /*request_id=*/devtools_request_id, request, event_data,
                   base::TimeTicks::Now());
}

void OnFencedFrameReportResponseReceived(
    FrameTreeNodeId initiator_frame_tree_node_id,
    const std::string& devtools_request_id,
    const GURL& final_url,
    scoped_refptr<net::HttpResponseHeaders> headers) {
  network::mojom::URLResponseHeadDevToolsInfoPtr response_info =
      network::mojom::URLResponseHeadDevToolsInfo::New();
  response_info->headers = headers;

  DispatchToAgents(initiator_frame_tree_node_id,
                   &protocol::NetworkHandler::ResponseReceived,
                   /*request_id=*/devtools_request_id,
                   /*loader_id=*/devtools_request_id, final_url,
                   protocol::Network::ResourceTypeEnum::Other, *response_info,
                   /*frame_id=*/std::nullopt);

  DispatchToAgents(initiator_frame_tree_node_id,
                   &protocol::NetworkHandler::LoadingComplete,
                   /*request_id=*/devtools_request_id,
                   protocol::Network::Initiator::TypeEnum::Other,
                   network::URLLoaderCompletionStatus(net::OK));
}

void DidChangeFrameLoadingState(FrameTreeNode& ftn) {
  DispatchToAgents(&ftn, &protocol::PageHandler::DidChangeFrameLoadingState,
                   ftn);
}

void DidStartNavigating(FrameTreeNode& ftn,
                        const GURL& url,
                        const base::UnguessableToken& loader_id,
                        const blink::mojom::NavigationType& navigation_type) {
  DispatchToAgents(&ftn, &protocol::PageHandler::DidStartNavigating, ftn, url,
                   loader_id, navigation_type);
}

}  // namespace devtools_instrumentation

}  // namespace content