// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/devtools/protocol/network_handler.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <memory>
#include <string_view>
#include <utility>
#include <variant>

#include "base/barrier_closure.h"
#include "base/base64.h"
#include "base/byte_size.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/i18n/i18n_constants.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/memory/raw_ptr.h"
#include "base/process/process_handle.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_view_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "content/browser/background_sync/background_sync_manager.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_io_context.h"
#include "content/browser/devtools/devtools_stream_file.h"
#include "content/browser/devtools/devtools_stream_pipe.h"
#include "content/browser/devtools/protocol/devtools_network_resource_loader.h"
#include "content/browser/devtools/protocol/handler_helpers.h"
#include "content/browser/devtools/protocol/network.h"
#include "content/browser/devtools/protocol/page.h"
#include "content/browser/devtools/protocol/protocol.h"
#include "content/browser/devtools/protocol/security.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/service_worker_devtools_manager.h"
#include "content/browser/loader/url_loader_factory_utils.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_csp_context.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_params_helper.h"
#include "content/browser/web_package/signed_exchange_envelope.h"
#include "content/browser/web_package/signed_exchange_error.h"
#include "content/common/web_package/signed_exchange_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/devtools_agent_host_client.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "ipc/constants.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/cert/ct_policy_status.h"
#include "net/cert/ct_sct_to_string.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_util.h"
#include "net/filter/source_stream_type.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/ssl/ssl_cipher_suite_names.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/url_request/referrer_policy.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/data_element.h"
#include "services/network/public/cpp/devtools_observer_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/client_security_state.mojom-shared.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "services/network/public/mojom/device_bound_sessions.mojom.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
#include "services/network/public/mojom/http_raw_headers.mojom.h"
#include "services/network/public/mojom/network_context.mojom-forward.h"
#include "services/network/public/mojom/service_worker_router_info.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/blink/public/common/loader/referrer_utils.h"
#include "third_party/blink/public/mojom/navigation/navigation_params.mojom.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "url/third_party/mozilla/url_parse.h"

namespace content {
namespace protocol {

namespace {

using GetCookiesCallback = Network::Backend::GetCookiesCallback;
using GetAllCookiesCallback = Network::Backend::GetAllCookiesCallback;
using SetCookieCallback = Network::Backend::SetCookieCallback;
using SetCookiesCallback = Network::Backend::SetCookiesCallback;
using DeleteCookiesCallback = Network::Backend::DeleteCookiesCallback;
using ClearBrowserCookiesCallback =
    Network::Backend::ClearBrowserCookiesCallback;

static constexpr char kInvalidCookieFields[] = "Invalid cookie fields";

Network::CertificateTransparencyCompliance SerializeCTPolicyCompliance(
    net::ct::CTPolicyCompliance ct_compliance) {
  switch (ct_compliance) {
    case net::ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS:
      return Network::CertificateTransparencyComplianceEnum::Compliant;
    case net::ct::CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS:
    case net::ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS:
      return Network::CertificateTransparencyComplianceEnum::NotCompliant;
    case net::ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY:
    case net::ct::CTPolicyCompliance::
        CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE:
      return Network::CertificateTransparencyComplianceEnum::Unknown;
    case net::ct::CTPolicyCompliance::CT_POLICY_COUNT:
      NOTREACHED();
  }
  NOTREACHED();
}

namespace {
Network::CookiePriority BuildCookiePriority(net::CookiePriority priority) {
  switch (priority) {
    case net::CookiePriority::COOKIE_PRIORITY_HIGH:
      return Network::CookiePriorityEnum::High;
    case net::CookiePriority::COOKIE_PRIORITY_MEDIUM:
      return Network::CookiePriorityEnum::Medium;
    case net::CookiePriority::COOKIE_PRIORITY_LOW:
      return Network::CookiePriorityEnum::Low;
  }
}
Network::CookieSourceScheme BuildCookieSourceScheme(
    net::CookieSourceScheme scheme) {
  switch (scheme) {
    case net::CookieSourceScheme::kUnset:
      return Network::CookieSourceSchemeEnum::Unset;
    case net::CookieSourceScheme::kNonSecure:
      return Network::CookieSourceSchemeEnum::NonSecure;
    case net::CookieSourceScheme::kSecure:
      return Network::CookieSourceSchemeEnum::Secure;
  }
}
std::optional<Network::CookieSameSite> BuildCookieSameSite(
    net::CookieSameSite same_site) {
  switch (same_site) {
    case net::CookieSameSite::STRICT_MODE:
      return Network::CookieSameSiteEnum::Strict;
    case net::CookieSameSite::LAX_MODE:
      return Network::CookieSameSiteEnum::Lax;
    case net::CookieSameSite::NO_RESTRICTION:
      return Network::CookieSameSiteEnum::None;
    case net::CookieSameSite::UNSPECIFIED:
      return std::nullopt;
  }
}

std::unique_ptr<Network::CookiePartitionKey> BuildCookiePartitionKey(
    const std::string& top_level_site,
    bool has_cross_site_ancestor) {
  return Network::CookiePartitionKey::Create()
      .SetTopLevelSite(top_level_site)
      .SetHasCrossSiteAncestor(has_cross_site_ancestor)
      .Build();
}

}  // namespace

std::unique_ptr<Network::Cookie> BuildCookie(
    const net::CanonicalCookie& cookie) {
  DCHECK(cookie.ExpiryDate().is_null() || !cookie.ExpiryDate().is_inf());
  std::unique_ptr<Network::Cookie> devtools_cookie =
      Network::Cookie::Create()
          .SetName(cookie.Name())
          .SetValue(cookie.Value())
          .SetDomain(cookie.Domain())
          .SetPath(cookie.Path())
          .SetExpires(cookie.ExpiryDate().is_null()
                          ? -1
                          : cookie.ExpiryDate().InSecondsFSinceUnixEpoch())
          .SetSize(cookie.Name().length() + cookie.Value().length())
          .SetHttpOnly(cookie.IsHttpOnly())
          .SetSecure(cookie.SecureAttribute())
          .SetSession(!cookie.IsPersistent())
          .SetPriority(BuildCookiePriority(cookie.Priority()))
          .SetSourceScheme(BuildCookieSourceScheme(cookie.SourceScheme()))
          .SetSourcePort(cookie.SourcePort())
          .Build();

  std::optional<Network::CookieSourceScheme> maybe_same_site =
      BuildCookieSameSite(cookie.SameSite());
  if (maybe_same_site) {
    devtools_cookie->SetSameSite(*maybe_same_site);
  }
  std::optional<net::CookiePartitionKey> partition_key = cookie.PartitionKey();
  if (partition_key) {
    if (partition_key->IsSerializeable()) {
      base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
                     std::string>
          key_serialized_result =
              net::CookiePartitionKey::Serialize(partition_key);
      CHECK(key_serialized_result.has_value());
      devtools_cookie->SetPartitionKey(BuildCookiePartitionKey(
          key_serialized_result->TopLevelSite(),
          key_serialized_result->has_cross_site_ancestor()));
    } else {
      devtools_cookie->SetPartitionKeyOpaque(partition_key->site().opaque());
      // IsSerializeable may return false when the partition key's site is not
      // opaque since we introduced nonce-based cookie partitioning in
      // https://crrev.com/c/3285244.
      // TODO(1225444,1229638): Surface nonce-based cookie partition keys in
      // DevTools.
    }
  }
  return devtools_cookie;
}

class CookieRetrieverNetworkService
    : public base::RefCounted<CookieRetrieverNetworkService> {
 public:
  static void Retrieve(network::mojom::CookieManager* cookie_manager,
                       const std::vector<GURL>& urls,
                       const net::NetworkIsolationKey& network_isolation_key,
                       const net::SiteForCookies& site_for_cookies,
                       std::unique_ptr<GetCookiesCallback> callback) {
    scoped_refptr<CookieRetrieverNetworkService> self =
        new CookieRetrieverNetworkService(std::move(callback));
    net::CookieOptions cookie_options = net::CookieOptions::MakeAllInclusive();
    for (const auto& url : urls) {
      cookie_manager->GetCookieList(
          url, cookie_options,
          net::CookiePartitionKeyCollection(
              net::CookiePartitionKey::FromNetworkIsolationKey(
                  network_isolation_key, site_for_cookies,
                  net::SchemefulSite(url), /*main_frame_navigation=*/false)),
          base::BindOnce(&CookieRetrieverNetworkService::GotCookies, self));
    }
  }

 private:
  friend class base::RefCounted<CookieRetrieverNetworkService>;

  CookieRetrieverNetworkService(std::unique_ptr<GetCookiesCallback> callback)
      : callback_(std::move(callback)) {}

  void GotCookies(const net::CookieAccessResultList& cookies,
                  const net::CookieAccessResultList& excluded_cookies) {
    for (const auto& cookie_with_access_result : cookies) {
      const net::CanonicalCookie& cookie = cookie_with_access_result.cookie;

      base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
                     std::string>
          serialized_partition_key =
              net::CookiePartitionKey::Serialize(cookie.PartitionKey());
      // We could be missing cookies that have unserializable partition key.
      // Reference the CookiePartitionKey::IsSerializable docs for more details.
      // Default to true for has_cross_site_ancestor if the partition key is
      // unserializable to avoid false positives.
      std::string key = base::StringPrintf(
          "%s::%s::%s::%d::%s::%d", cookie.Name().c_str(),
          cookie.Domain().c_str(), cookie.Path().c_str(),
          cookie.SecureAttribute(),
          serialized_partition_key.has_value()
              ? serialized_partition_key->TopLevelSite().c_str()
              : serialized_partition_key.error().c_str(),
          serialized_partition_key.has_value()
              ? serialized_partition_key->has_cross_site_ancestor()
              : true);
      all_cookies_.emplace(std::move(key), cookie);
    }
  }

  ~CookieRetrieverNetworkService() {
    auto cookies = std::make_unique<Array<Network::Cookie>>();
    for (const auto& entry : all_cookies_) {
      cookies->emplace_back(BuildCookie(entry.second));
    }
    callback_->sendSuccess(std::move(cookies));
  }

  std::unique_ptr<GetCookiesCallback> callback_;
  absl::flat_hash_map<std::string, net::CanonicalCookie> all_cookies_;
};

namespace {
std::vector<net::CanonicalCookie> FilterCookies(
    const std::vector<net::CanonicalCookie>& cookies,
    const std::optional<std::string>& name,
    const std::optional<std::string>& normalized_domain,
    const std::optional<std::string>& path,
    const Network::CookiePartitionKey* partition_key,
    bool filter_by_partition_key) {
  std::vector<net::CanonicalCookie> result;

  for (const auto& cookie : cookies) {
    if (name.has_value() && cookie.Name() != name.value()) {
      continue;
    }
    if (normalized_domain.has_value() &&
        cookie.Domain() != normalized_domain.value()) {
      continue;
    }
    if (path.has_value() && !path.value().empty() &&
        cookie.Path() != path.value()) {
      continue;
    }

    if (filter_by_partition_key) {
      if (!!cookie.PartitionKey() != !!partition_key) {
        continue;
      }

      if (cookie.PartitionKey().has_value()) {
        base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
                       std::string>
            serialized_result =
                net::CookiePartitionKey::Serialize(cookie.PartitionKey());

        if (!serialized_result.has_value() ||
            (serialized_result->TopLevelSite() !=
             const_cast<Network::CookiePartitionKey*>(partition_key)
                 ->GetTopLevelSite())) {
          continue;
        }

        if (serialized_result->has_cross_site_ancestor() !=
            const_cast<Network::CookiePartitionKey*>(partition_key)
                ->GetHasCrossSiteAncestor()) {
          continue;
        }
      }
    }

    result.push_back(cookie);
  }

  return result;
}

void DeleteFilteredCookies(
    network::mojom::CookieManager* cookie_manager,
    const std::optional<std::string>& name,
    const std::optional<std::string>& normalized_domain,
    const std::optional<std::string>& path,
    const Network::CookiePartitionKey* partition_key,
    bool filter_by_partition_key,
    base::RepeatingCallback<bool(const net::CanonicalCookie&)> filter,
    base::OnceClosure success_callback,
    const std::vector<net::CanonicalCookie>& cookies) {
  std::vector<net::CanonicalCookie> filtered_list =
      FilterCookies(cookies, name, normalized_domain, path, partition_key,
                    filter_by_partition_key);

  std::erase_if(filtered_list, [&](const net::CanonicalCookie& cookie) {
    return !filter.Run(cookie);
  });

  base::RepeatingClosure barrier_closure =
      base::BarrierClosure(filtered_list.size(), std::move(success_callback));

  for (auto& cookie : filtered_list) {
    cookie_manager->DeleteCanonicalCookie(
        cookie, base::BindOnce([](base::RepeatingClosure callback,
                                  bool) { callback.Run(); },
                               barrier_closure));
  }
}

std::variant<net::CookieSourceScheme, Response> GetSourceSchemeFromProtocol(
    const std::string& source_scheme) {
  if (source_scheme == Network::CookieSourceSchemeEnum::Unset) {
    return net::CookieSourceScheme::kUnset;
  } else if (source_scheme == Network::CookieSourceSchemeEnum::NonSecure) {
    return net::CookieSourceScheme::kNonSecure;
  } else if (source_scheme == Network::CookieSourceSchemeEnum::Secure) {
    return net::CookieSourceScheme::kSecure;
  }
  return Response::InvalidParams("Invalid cookie source scheme");
}

std::variant<int, Response> GetCookieSourcePort(int source_port) {
  // Only {url::PORT_UNSPECIFIED, [1,65535]} are valid.
  if (source_port == url::PORT_UNSPECIFIED ||
      (source_port >= 1 && source_port <= 65535)) {
    return source_port;
  }

  return Response::InvalidParams("Invalid source port");
}

}  // namespace

std::variant<std::unique_ptr<net::CanonicalCookie>, Response>
MakeCookieFromProtocolValues(const std::string& name,
                             const std::string& value,
                             const std::string& url_spec,
                             const std::string& domain,
                             const std::string& path,
                             bool secure,
                             bool http_only,
                             const std::string& same_site,
                             double expires,
                             const std::string& priority,
                             const std::optional<std::string>& source_scheme,
                             const std::optional<int>& source_port,
                             const Network::CookiePartitionKey* partition_key) {
  std::string normalized_domain = domain;

  if (url_spec.empty() && domain.empty()) {
    return Response::InvalidParams(
        "At least one of the url or domain needs to be specified");
  }

  GURL source_url;
  if (!url_spec.empty()) {
    source_url = GURL(url_spec);
    if (!source_url.SchemeIsHTTPOrHTTPS()) {
      return Response::InvalidParams("URL must have scheme http or https");
    }

    secure = secure || source_url.SchemeIsCryptographic();
    if (normalized_domain.empty()) {
      normalized_domain = source_url.GetHost();
    }
  }

  std::string url_host = normalized_domain;
  if (!normalized_domain.empty()) {
    // The value of |url_host| may have trickled down from a cookie domain,
    // where leading periods are legal. However, since we want to use it as a
    // URL host, we must the leading period if it exists.
    if (normalized_domain[0] == '.') {
      url_host.erase(0, 1);
    }
    // If there is no leading period, clear out |normalized_domain|, but keep
    // the value of |url_host|. CreateSanitizedCookie will determine the proper
    // domain from the URL we construct with |url_host|.
    else {
      normalized_domain = "";
    }
  }
  GURL url = GURL((secure ? "https://" : "http://") + url_host);

  base::Time expiration_date;
  if (expires >= 0) {
    expiration_date = expires ? base::Time::FromSecondsSinceUnixEpoch(expires)
                              : base::Time::UnixEpoch();
  }

  net::CookieSameSite css = net::CookieSameSite::UNSPECIFIED;
  if (same_site == Network::CookieSameSiteEnum::Lax) {
    css = net::CookieSameSite::LAX_MODE;
  }
  if (same_site == Network::CookieSameSiteEnum::Strict) {
    css = net::CookieSameSite::STRICT_MODE;
  }
  if (same_site == Network::CookieSameSiteEnum::None) {
    css = net::CookieSameSite::NO_RESTRICTION;
  }

  net::CookiePriority cp = net::CookiePriority::COOKIE_PRIORITY_MEDIUM;
  if (priority == Network::CookiePriorityEnum::High) {
    cp = net::CookiePriority::COOKIE_PRIORITY_HIGH;
  } else if (priority == Network::CookiePriorityEnum::Medium) {
    cp = net::CookiePriority::COOKIE_PRIORITY_MEDIUM;
  } else if (priority == Network::CookiePriorityEnum::Low) {
    cp = net::CookiePriority::COOKIE_PRIORITY_LOW;
  }

  std::optional<net::CookiePartitionKey> cookie_partition_key;
  if (partition_key && !const_cast<Network::CookiePartitionKey*>(partition_key)
                            ->GetTopLevelSite()
                            .empty()) {
    base::expected<net::CookiePartitionKey, std::string>
        deserialized_partition_key =
            net::CookiePartitionKey::FromUntrustedInput(
                const_cast<Network::CookiePartitionKey*>(partition_key)
                    ->GetTopLevelSite(),
                const_cast<Network::CookiePartitionKey*>(partition_key)
                    ->GetHasCrossSiteAncestor());
    if (!deserialized_partition_key.has_value()) {
      return Response::InvalidParams(
          "Deserializing cookie partition key failed");
    }
    cookie_partition_key = deserialized_partition_key.value();
  }
  // TODO(crbug.com/40188414) Add Partitioned to DevTools cookie structures.
  std::unique_ptr<net::CanonicalCookie> cookie =
      net::CanonicalCookie::CreateSanitizedCookie(
          url, name, value, normalized_domain, path, base::Time(),
          expiration_date, base::Time(), secure, http_only, css, cp,
          cookie_partition_key, /*status=*/nullptr);

  if (!cookie) {
    return Response::InvalidParams("Sanitizing cookie failed");
  }

  // Update the cookie's sourceScheme unless it's undefined in which case we'll
  // keep the value that was implied from `url` via CreateSanitizedCookie.
  if (source_scheme.has_value()) {
    auto cookie_source_scheme_or_error =
        GetSourceSchemeFromProtocol(source_scheme.value());
    if (std::holds_alternative<Response>(cookie_source_scheme_or_error)) {
      return std::get<Response>(std::move(cookie_source_scheme_or_error));
    }
    net::CookieSourceScheme cookie_source_scheme =
        std::get<net::CookieSourceScheme>(cookie_source_scheme_or_error);
    if (cookie->SecureAttribute() &&
        cookie_source_scheme == net::CookieSourceScheme::kNonSecure) {
      return Response::InvalidParams(
          "Secure attribute cannot be set for a cookie with an insecure source "
          "scheme");
    }

    cookie->SetSourceScheme(cookie_source_scheme);
  }

  // Update the cookie's port unless it's undefined in which case we'll
  // keep the value that was implied from `url` via CreateSanitizedCookie.
  if (source_port.has_value()) {
    auto cookie_source_port_or_error = GetCookieSourcePort(source_port.value());
    if (std::holds_alternative<Response>(cookie_source_port_or_error)) {
      return std::get<Response>(std::move(cookie_source_port_or_error));
    }
    int port_value = std::get<int>(cookie_source_port_or_error);

    // If the url has a port specified it must match the source_port value.
    // Otherwise this set cookie request is considered malformed.
    // Note: Default port values (https: 443, http: 80) are ignored. They will
    // be treated as if they were not specified.
    if (source_url.has_port() && source_url.IntPort() != port_value) {
      return Response::InvalidParams(
          "Source port does not match the url's specified port");
    }

    cookie->SetSourcePort(port_value);
  }

  return cookie;
}

std::vector<GURL> ComputeCookieURLs(
    RenderFrameHostImpl* frame_host,
    std::unique_ptr<Array<String>>& protocol_urls) {
  std::vector<GURL> urls;

  if (protocol_urls) {
    for (const std::string& url : *protocol_urls) {
      urls.emplace_back(url);
    }
  } else {
    base::queue<RenderFrameHostImpl*> queue;
    queue.push(frame_host);
    while (!queue.empty()) {
      RenderFrameHostImpl* node = queue.front();
      queue.pop();

      urls.push_back(node->GetLastCommittedURL());
      for (size_t i = 0; i < node->child_count(); ++i) {
        queue.push(node->child_at(i)->current_frame_host());
      }
    }
  }

  return urls;
}

String resourcePriority(net::RequestPriority priority) {
  switch (priority) {
    case net::MINIMUM_PRIORITY:
    case net::IDLE:
      return Network::ResourcePriorityEnum::VeryLow;
    case net::LOWEST:
      return Network::ResourcePriorityEnum::Low;
    case net::LOW:
      return Network::ResourcePriorityEnum::Medium;
    case net::MEDIUM:
      return Network::ResourcePriorityEnum::High;
    case net::HIGHEST:
      return Network::ResourcePriorityEnum::VeryHigh;
  }
  NOTREACHED();
}

String referrerPolicy(network::mojom::ReferrerPolicy referrer_policy) {
  switch (referrer_policy) {
    case network::mojom::ReferrerPolicy::kAlways:
      return Network::Request::ReferrerPolicyEnum::UnsafeUrl;
    case network::mojom::ReferrerPolicy::kDefault:
      return referrerPolicy(blink::ReferrerUtils::NetToMojoReferrerPolicy(
          blink::ReferrerUtils::GetDefaultNetReferrerPolicy()));
    case network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade:
      return Network::Request::ReferrerPolicyEnum::NoReferrerWhenDowngrade;
    case network::mojom::ReferrerPolicy::kNever:
      return Network::Request::ReferrerPolicyEnum::NoReferrer;
    case network::mojom::ReferrerPolicy::kOrigin:
      return Network::Request::ReferrerPolicyEnum::Origin;
    case network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin:
      return Network::Request::ReferrerPolicyEnum::OriginWhenCrossOrigin;
    case network::mojom::ReferrerPolicy::kSameOrigin:
      return Network::Request::ReferrerPolicyEnum::SameOrigin;
    case network::mojom::ReferrerPolicy::kStrictOrigin:
      return Network::Request::ReferrerPolicyEnum::StrictOrigin;
    case network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin:
      return Network::Request::ReferrerPolicyEnum::StrictOriginWhenCrossOrigin;
  }
  NOTREACHED();
}

String referrerPolicy(net::ReferrerPolicy referrer_policy) {
  return referrerPolicy(
      blink::ReferrerUtils::NetToMojoReferrerPolicy(referrer_policy));
}

String securityState(const GURL& url, const net::CertStatus& cert_status) {
  if (!url.SchemeIsCryptographic()) {
    // Some origins are considered secure even though they're not cryptographic,
    // so treat them as secure in the UI.
    if (network::IsUrlPotentiallyTrustworthy(url)) {
      return Security::SecurityStateEnum::Secure;
    }
    return Security::SecurityStateEnum::Insecure;
  }
  if (net::IsCertStatusError(cert_status)) {
    return Security::SecurityStateEnum::Insecure;
  }
  return Security::SecurityStateEnum::Secure;
}

double timeDelta(base::TimeTicks time,
                 base::TimeTicks start,
                 double invalid_value = -1) {
  return time.is_null() ? invalid_value : (time - start).InMillisecondsF();
}

std::unique_ptr<Network::ResourceTiming> GetTiming(
    const net::LoadTimingInfo& load_timing) {
  if (load_timing.receive_headers_end.is_null()) {
    return nullptr;
  }

  const base::TimeTicks kNullTicks;
  auto timing =
      Network::ResourceTiming::Create()
          .SetRequestTime((load_timing.request_start - kNullTicks).InSecondsF())
          .SetProxyStart(timeDelta(load_timing.proxy_resolve_start,
                                   load_timing.request_start))
          .SetProxyEnd(timeDelta(load_timing.proxy_resolve_end,
                                 load_timing.request_start))
          .SetDnsStart(timeDelta(load_timing.connect_timing.domain_lookup_start,
                                 load_timing.request_start))
          .SetDnsEnd(timeDelta(load_timing.connect_timing.domain_lookup_end,
                               load_timing.request_start))
          .SetConnectStart(timeDelta(load_timing.connect_timing.connect_start,
                                     load_timing.request_start))
          .SetConnectEnd(timeDelta(load_timing.connect_timing.connect_end,
                                   load_timing.request_start))
          .SetSslStart(timeDelta(load_timing.connect_timing.ssl_start,
                                 load_timing.request_start))
          .SetSslEnd(timeDelta(load_timing.connect_timing.ssl_end,
                               load_timing.request_start))
          .SetWorkerStart(-1)
          .SetWorkerReady(-1)
          .SetWorkerFetchStart(timeDelta(load_timing.service_worker_fetch_start,
                                         load_timing.request_start))
          .SetWorkerRespondWithSettled(
              timeDelta(load_timing.service_worker_respond_with_settled,
                        load_timing.request_start))
          .SetSendStart(
              timeDelta(load_timing.send_start, load_timing.request_start))
          .SetSendEnd(
              timeDelta(load_timing.send_end, load_timing.request_start))
          .SetPushStart(
              timeDelta(load_timing.push_start, load_timing.request_start, 0))
          .SetPushEnd(
              timeDelta(load_timing.push_end, load_timing.request_start, 0))
          .SetReceiveHeadersStart(timeDelta(load_timing.receive_headers_start,
                                            load_timing.request_start))
          .SetReceiveHeadersEnd(timeDelta(load_timing.receive_headers_end,
                                          load_timing.request_start))
          .Build();

  if (base::FeatureList::IsEnabled(
          blink::features::kServiceWorkerStaticRouterTimingInfo)) {
    if (!load_timing.service_worker_router_evaluation_start.is_null()) {
      timing->SetWorkerRouterEvaluationStart(
          timeDelta(load_timing.service_worker_router_evaluation_start,
                    load_timing.request_start));
    }

    if (!load_timing.service_worker_cache_lookup_start.is_null()) {
      timing->SetWorkerCacheLookupStart(
          timeDelta(load_timing.service_worker_cache_lookup_start,
                    load_timing.request_start));
    }
  }

  return timing;
}

std::unique_ptr<Network::ConnectTiming> GetConnectTiming(
    const base::TimeTicks timestamp) {
  const base::TimeTicks kNullTicks;
  return Network::ConnectTiming::Create()
      .SetRequestTime((timestamp - kNullTicks).InSecondsF())
      .Build();
}

std::unique_ptr<base::DictValue> GetRawHeaders(
    const std::vector<network::mojom::HttpRawHeaderPairPtr>& headers) {
  auto headers_dict = std::make_unique<base::DictValue>();
  for (const auto& header : headers) {
    std::string header_value;
    if (!base::ConvertToUtf8AndNormalize(header->value, base::kCodepageLatin1,
                                         &header_value)) {
      // For response headers, the encoding could be anything, so conversion
      // might fail; in that case this is the most useful thing we can do.
      header_value = header->value;
    }
    // TODO(crbug.com/40228605): Once there's an API to do this without
    // a double lookup, switch do doing so.
    base::Value* value = headers_dict->Find(header->key);
    if (value) {
      *value = base::Value(value->GetString() + '\n' + header_value);
    } else {
      headers_dict->Set(header->key, header_value);
    }
  }
  return headers_dict;
}

String GetProtocol(const GURL& url,
                   const network::mojom::URLResponseHeadDevToolsInfo& info) {
  std::string protocol = info.alpn_negotiated_protocol;
  if (protocol.empty() || protocol == "unknown") {
    if (info.was_fetched_via_spdy) {
      protocol = "h2";
    } else if (url.SchemeIsHTTPOrHTTPS()) {
      protocol = "http";
      if (info.headers) {
        if (info.headers->GetHttpVersion() == net::HttpVersion(0, 9)) {
          protocol = "http/0.9";
        } else if (info.headers->GetHttpVersion() == net::HttpVersion(1, 0)) {
          protocol = "http/1.0";
        } else if (info.headers->GetHttpVersion() == net::HttpVersion(1, 1)) {
          protocol = "http/1.1";
        }
      }
    } else {
      protocol = url.GetScheme();
    }
  }
  return protocol;
}

bool GetPostData(
    const network::ResourceRequestBody& request_body,
    protocol::Array<protocol::Network::PostDataEntry>* data_entries,
    std::string* result) {
  const std::vector<network::DataElement>* elements = request_body.elements();
  if (elements->empty()) {
    return false;
  }
  for (const auto& element : *elements) {
    // TODO(caseq): Also support blobs.
    const auto* bytes_element = element.TryAs<network::DataElementBytes>();
    if (!bytes_element) {
      return false;
    }
    base::span<const uint8_t> bytes = bytes_element->bytes();
    auto data_entry = protocol::Network::PostDataEntry::Create().Build();
    data_entry->SetBytes(protocol::Binary::fromSpan(bytes));
    data_entries->push_back(std::move(data_entry));
    result->append(base::as_string_view(bytes));
  }
  return true;
}

String SignedExchangeErrorErrorFieldToString(SignedExchangeError::Field field) {
  switch (field) {
    case SignedExchangeError::Field::kSignatureSig:
      return Network::SignedExchangeErrorFieldEnum::SignatureSig;
    case SignedExchangeError::Field::kSignatureIintegrity:
      return Network::SignedExchangeErrorFieldEnum::SignatureIntegrity;
    case SignedExchangeError::Field::kSignatureCertUrl:
      return Network::SignedExchangeErrorFieldEnum::SignatureCertUrl;
    case SignedExchangeError::Field::kSignatureCertSha256:
      return Network::SignedExchangeErrorFieldEnum::SignatureCertSha256;
    case SignedExchangeError::Field::kSignatureValidityUrl:
      return Network::SignedExchangeErrorFieldEnum::SignatureValidityUrl;
    case SignedExchangeError::Field::kSignatureTimestamps:
      return Network::SignedExchangeErrorFieldEnum::SignatureTimestamps;
  }
  NOTREACHED();
}

std::unique_ptr<Network::SignedExchangeError> BuildSignedExchangeError(
    const SignedExchangeError& error) {
  std::unique_ptr<Network::SignedExchangeError> signed_exchange_error =
      Network::SignedExchangeError::Create().SetMessage(error.message).Build();
  if (error.field) {
    signed_exchange_error->SetSignatureIndex(error.field->first);
    signed_exchange_error->SetErrorField(
        SignedExchangeErrorErrorFieldToString(error.field->second));
  }
  return signed_exchange_error;
}

std::unique_ptr<Array<Network::SignedExchangeError>> BuildSignedExchangeErrors(
    const std::vector<SignedExchangeError>& errors) {
  auto signed_exchange_errors =
      std::make_unique<protocol::Array<Network::SignedExchangeError>>();
  for (const auto& error : errors) {
    signed_exchange_errors->emplace_back(BuildSignedExchangeError(error));
  }
  return signed_exchange_errors;
}

std::unique_ptr<Array<Network::SetCookieBlockedReason>>
GetProtocolBlockedSetCookieReason(net::CookieInclusionStatus status) {
  std::unique_ptr<Array<Network::SetCookieBlockedReason>> blockedReasons =
      std::make_unique<Array<Network::SetCookieBlockedReason>>();
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SECURE_ONLY)) {
    blockedReasons->push_back(Network::SetCookieBlockedReasonEnum::SecureOnly);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_STRICT)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::SchemefulSameSiteStrict);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SAMESITE_LAX)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::SchemefulSameSiteLax);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX)) {
    blockedReasons->push_back(Network::SetCookieBlockedReasonEnum::
                                  SchemefulSameSiteUnspecifiedTreatedAsLax);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_NONE_INSECURE)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::SameSiteNoneInsecure);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_USER_PREFERENCES)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::UserPreferences);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::ThirdPartyBlockedInFirstPartySet);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_THIRD_PARTY_PHASEOUT)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::ThirdPartyPhaseout);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_FAILURE_TO_STORE)) {
    blockedReasons->push_back(Network::SetCookieBlockedReasonEnum::SyntaxError);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_NONCOOKIEABLE_SCHEME)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::SchemeNotSupported);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_OVERWRITE_SECURE)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::OverwriteSecure);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_INVALID_DOMAIN)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::InvalidDomain);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_INVALID_PREFIX)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::InvalidPrefix);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::NameValuePairExceedsMaxSize);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_DISALLOWED_CHARACTER)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::DisallowedCharacter);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_UNKNOWN_ERROR)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::UnknownError);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_NO_COOKIE_CONTENT)) {
    blockedReasons->push_back(
        Network::SetCookieBlockedReasonEnum::NoCookieContent);
  }

  return blockedReasons;
}

std::unique_ptr<Array<Network::CookieBlockedReason>>
GetProtocolBlockedCookieReason(net::CookieInclusionStatus status) {
  std::unique_ptr<Array<Network::CookieBlockedReason>> blockedReasons =
      std::make_unique<Array<Network::CookieBlockedReason>>();

  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SECURE_ONLY)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::SecureOnly);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_NOT_ON_PATH)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::NotOnPath);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_DOMAIN_MISMATCH)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::DomainMismatch);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_STRICT)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::SchemefulSameSiteStrict);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_SAMESITE_LAX)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::SchemefulSameSiteLax);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::
                                  SchemefulSameSiteUnspecifiedTreatedAsLax);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SAMESITE_NONE_INSECURE)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::SameSiteNoneInsecure);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_USER_PREFERENCES)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::UserPreferences);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::
              EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::ThirdPartyBlockedInFirstPartySet);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_THIRD_PARTY_PHASEOUT)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::ThirdPartyPhaseout);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::NameValuePairExceedsMaxSize);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_UNKNOWN_ERROR)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::UnknownError);
  }
  if (status.HasExclusionReason(
          net::CookieInclusionStatus::ExclusionReason::EXCLUDE_PORT_MISMATCH)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::PortMismatch);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_SCHEME_MISMATCH)) {
    blockedReasons->push_back(Network::CookieBlockedReasonEnum::SchemeMismatch);
  }
  if (status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
                                    EXCLUDE_ANONYMOUS_CONTEXT)) {
    blockedReasons->push_back(
        Network::CookieBlockedReasonEnum::AnonymousContext);
  }
  return blockedReasons;
}

std::unique_ptr<Array<Network::BlockedSetCookieWithReason>>
BuildProtocolBlockedSetCookies(
    const net::CookieAndLineAccessResultList& net_list) {
  std::unique_ptr<Array<Network::BlockedSetCookieWithReason>> protocol_list =
      std::make_unique<Array<Network::BlockedSetCookieWithReason>>();

  for (const net::CookieAndLineWithAccessResult& cookie : net_list) {
    std::unique_ptr<Array<Network::SetCookieBlockedReason>> blocked_reasons =
        GetProtocolBlockedSetCookieReason(cookie.access_result.status);
    if (!blocked_reasons->size()) {
      continue;
    }

    protocol_list->push_back(
        Network::BlockedSetCookieWithReason::Create()
            .SetBlockedReasons(std::move(blocked_reasons))
            .SetCookieLine(cookie.cookie_string)
            .SetCookie(cookie.cookie.has_value()
                           ? BuildCookie(cookie.cookie.value())
                           : nullptr)
            .Build());
  }
  return protocol_list;
}

Network::CookieExemptionReason GetProtocolCookieExemptionReason(
    net::CookieInclusionStatus status) {
  switch (status.exemption_reason()) {
    case net::CookieInclusionStatus::ExemptionReason::kNone:
      return Network::CookieExemptionReasonEnum::None;
    case net::CookieInclusionStatus::ExemptionReason::kUserSetting:
      return Network::CookieExemptionReasonEnum::UserSetting;
    case net::CookieInclusionStatus::ExemptionReason::kEnterprisePolicy:
      return Network::CookieExemptionReasonEnum::EnterprisePolicy;
    case net::CookieInclusionStatus::ExemptionReason::kStorageAccess:
      return Network::CookieExemptionReasonEnum::StorageAccess;
    case net::CookieInclusionStatus::ExemptionReason::kTopLevelStorageAccess:
      return Network::CookieExemptionReasonEnum::TopLevelStorageAccess;
    case net::CookieInclusionStatus::ExemptionReason::kScheme:
      return Network::CookieExemptionReasonEnum::Scheme;
    case net::CookieInclusionStatus::ExemptionReason::
        kSameSiteNoneCookiesInSandbox:
      return Network::CookieExemptionReasonEnum::SameSiteNoneCookiesInSandbox;
  }
}

std::unique_ptr<Array<Network::ExemptedSetCookieWithReason>>
BuildProtocolExemptedSetCookies(
    const net::CookieAndLineAccessResultList& net_list) {
  std::unique_ptr<Array<Network::ExemptedSetCookieWithReason>> protocol_list =
      std::make_unique<Array<Network::ExemptedSetCookieWithReason>>();

  for (const auto& cookie : net_list) {
    Network::CookieExemptionReason exemption_reason =
        GetProtocolCookieExemptionReason(cookie.access_result.status);
    if (exemption_reason != Network::CookieExemptionReasonEnum::None) {
      // The exempted cookies are guaranteed to be valid.
      protocol_list->push_back(
          Network::ExemptedSetCookieWithReason::Create()
              .SetExemptionReason(std::move(exemption_reason))
              .SetCookieLine(cookie.cookie_string)
              .SetCookie(BuildCookie(cookie.cookie.value()))
              .Build());
    }
  }
  return protocol_list;
}

std::pair<std::unique_ptr<Array<Network::CookieBlockedReason>>,
          Network::CookieExemptionReason>
GetProtocolAssociatedCookie(net::CookieInclusionStatus status) {
  std::unique_ptr<Array<Network::CookieBlockedReason>> blocked_reasons =
      std::make_unique<Array<Network::CookieBlockedReason>>();
  blocked_reasons = GetProtocolBlockedCookieReason(status);

  Network::CookieExemptionReason exemption_reason =
      GetProtocolCookieExemptionReason(status);

  return std::make_pair(std::move(blocked_reasons),
                        std::move(exemption_reason));
}

std::unique_ptr<Array<Network::AssociatedCookie>>
BuildProtocolAssociatedCookies(const net::CookieAccessResultList& net_list) {
  auto protocol_list = std::make_unique<Array<Network::AssociatedCookie>>();

  for (const net::CookieWithAccessResult& cookie : net_list) {
    std::pair<std::unique_ptr<Array<Network::CookieBlockedReason>>,
              Network::CookieExemptionReason>
        cookie_with_reasons =
            GetProtocolAssociatedCookie(cookie.access_result.status);
    // Note that the condition below is not always true, as there might be
    // blocked reasons that we do not report.
    if (cookie_with_reasons.first->size() ||
        cookie.access_result.status.IsInclude()) {
      protocol_list->push_back(
          Network::AssociatedCookie::Create()
              .SetCookie(BuildCookie(cookie.cookie))
              .SetBlockedReasons(std::move(cookie_with_reasons.first))
              .SetExemptionReason(std::move(cookie_with_reasons.second))
              .Build());
    }
  }
  return protocol_list;
}

std::unique_ptr<protocol::Network::DeviceBoundSessionKey>
BuildProtocolDeviceBoundSessionKey(
    const net::device_bound_sessions::SessionKey& key) {
  return protocol::Network::DeviceBoundSessionKey::Create()
      .SetSite(key.site.Serialize())
      .SetId(key.id.value())
      .Build();
}

const char* GetProtocolSessionUsage(
    network::mojom::DeviceBoundSessionUsage usage) {
  switch (usage) {
    case network::mojom::DeviceBoundSessionUsage::kSiteMatchNotInScope:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::NotInScope;
    case network::mojom::DeviceBoundSessionUsage::kInScopeRefreshNotYetNeeded:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::
          InScopeRefreshNotYetNeeded;
    case network::mojom::DeviceBoundSessionUsage::kInScopeRefreshNotAllowed:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::
          InScopeRefreshNotAllowed;
    case network::mojom::DeviceBoundSessionUsage::
        kInScopeProactiveRefreshNotPossible:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::
          ProactiveRefreshNotPossible;
    case network::mojom::DeviceBoundSessionUsage::
        kInScopeProactiveRefreshAttempted:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::
          ProactiveRefreshAttempted;
    case network::mojom::DeviceBoundSessionUsage::kDeferred:
      return Network::DeviceBoundSessionWithUsage::UsageEnum::Deferred;
    case network::mojom::DeviceBoundSessionUsage::kUnknown:
    case network::mojom::DeviceBoundSessionUsage::kNoSiteMatchNotInScope:
      NOTREACHED();
  }
}

std::unique_ptr<protocol::Array<protocol::Network::DeviceBoundSessionWithUsage>>
BuildProtocolDeviceBoundSessionUsages(
    const std::vector<network::mojom::DeviceBoundSessionWithUsagePtr>&
        device_bound_session_usages) {
  if (!base::FeatureList::IsEnabled(features::kDeviceBoundSessionsDevTools)) {
    return nullptr;
  }
  auto protocol_list = std::make_unique<
      protocol::Array<protocol::Network::DeviceBoundSessionWithUsage>>();
  for (const auto& session_usage : device_bound_session_usages) {
    // Don't send the usage if the usage is unknown or if the session's site is
    // irrelevant.
    if (session_usage->usage ==
            network::mojom::DeviceBoundSessionUsage::kNoSiteMatchNotInScope ||
        session_usage->usage ==
            network::mojom::DeviceBoundSessionUsage::kUnknown) {
      continue;
    }
    protocol_list->push_back(
        protocol::Network::DeviceBoundSessionWithUsage::Create()
            .SetSessionKey(
                BuildProtocolDeviceBoundSessionKey(session_usage->session_key))
            .SetUsage(GetProtocolSessionUsage(session_usage->usage))
            .Build());
  }
  if (protocol_list->empty()) {
    return nullptr;
  }
  return protocol_list;
}

}  // namespace

class BackgroundSyncRestorer {
 public:
  BackgroundSyncRestorer(const std::string& host_id,
                         StoragePartition* storage_partition)
      : host_id_(host_id), storage_partition_(storage_partition) {
    SetServiceWorkerOfflineStatus(true);
  }

  BackgroundSyncRestorer(const BackgroundSyncRestorer&) = delete;
  BackgroundSyncRestorer& operator=(const BackgroundSyncRestorer&) = delete;

  ~BackgroundSyncRestorer() { SetServiceWorkerOfflineStatus(false); }

  void SetStoragePartition(StoragePartition* storage_partition) {
    storage_partition_ = storage_partition;
  }

 private:
  void SetServiceWorkerOfflineStatus(bool offline) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    scoped_refptr<DevToolsAgentHost> host =
        DevToolsAgentHost::GetForId(host_id_);
    if (!host || !storage_partition_ ||
        host->GetType() != DevToolsAgentHost::kTypeServiceWorker) {
      return;
    }
    scoped_refptr<ServiceWorkerDevToolsAgentHost> service_worker_host =
        static_cast<ServiceWorkerDevToolsAgentHost*>(host.get());
    scoped_refptr<BackgroundSyncContextImpl> sync_context =
        static_cast<StoragePartitionImpl*>(storage_partition_)
            ->GetBackgroundSyncContext();
    if (offline) {
      auto* swcontext = static_cast<ServiceWorkerContextWrapper*>(
          storage_partition_->GetServiceWorkerContext());
      ServiceWorkerVersion* version =
          swcontext->GetLiveVersion(service_worker_host->version_id());
      if (!version) {
        return;
      }
      offline_sw_registration_id_ = version->registration_id();
    }
    if (offline_sw_registration_id_ ==
        blink::mojom::kInvalidServiceWorkerRegistrationId) {
      return;
    }
    sync_context->background_sync_manager()->EmulateServiceWorkerOffline(
        offline_sw_registration_id_, offline);
  }

  std::string host_id_;
  raw_ptr<StoragePartition> storage_partition_;
  int64_t offline_sw_registration_id_ =
      blink::mojom::kInvalidServiceWorkerRegistrationId;
};

NetworkHandler::NetworkHandler(
    const std::string& host_id,
    const base::UnguessableToken& devtools_token,
    DevToolsIOContext* io_context,
    DevToolsSession* session,
    StoragePartition* maybe_storage_partition,
    DevToolsAgentHostClient* client,
    base::OnceClosure cleanup_after_modifications_callback)
    : DevToolsDomainHandler(Network::Metainfo::domainName),
      host_id_(host_id),
      devtools_token_(devtools_token),
      io_context_(io_context),
      client_(client),
      browser_context_(nullptr),
      storage_partition_(maybe_storage_partition),
      host_(nullptr),
      enabled_(false),
#if BUILDFLAG(ENABLE_REPORTING)
      reporting_receiver_(this),
#endif  // BUILDFLAG(ENABLE_REPORTING)
#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
      device_bound_session_receiver_(this),
#endif  // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
      bypass_service_worker_(false),
      cache_disabled_(false),
      cleanup_after_modifications_callback_(
          std::move(cleanup_after_modifications_callback)),
      root_session_(*session->GetRootSession()),
      throttling_client_id_(base::UnguessableToken::Create()) {
  DCHECK(io_context_);
  static bool have_configured_service_worker_context = false;
  if (have_configured_service_worker_context) {
    return;
  }
  have_configured_service_worker_context = true;
}

NetworkHandler::~NetworkHandler() {
  if (did_modifications_ && cleanup_after_modifications_callback_) {
    std::move(cleanup_after_modifications_callback_).Run();
  }
}

// static
std::unique_ptr<Array<Network::Cookie>> NetworkHandler::BuildCookieArray(
    const std::vector<net::CanonicalCookie>& cookie_list) {
  auto cookies = std::make_unique<Array<Network::Cookie>>();

  for (const net::CanonicalCookie& cookie : cookie_list) {
    cookies->emplace_back(BuildCookie(cookie));
  }

  return cookies;
}

// static
String NetworkHandler::NetErrorToString(int net_error) {
  switch (net_error) {
    case net::ERR_ABORTED:
      return Network::ErrorReasonEnum::Aborted;
    case net::ERR_TIMED_OUT:
      return Network::ErrorReasonEnum::TimedOut;
    case net::ERR_ACCESS_DENIED:
      return Network::ErrorReasonEnum::AccessDenied;
    case net::ERR_CONNECTION_CLOSED:
      return Network::ErrorReasonEnum::ConnectionClosed;
    case net::ERR_CONNECTION_RESET:
      return Network::ErrorReasonEnum::ConnectionReset;
    case net::ERR_CONNECTION_REFUSED:
      return Network::ErrorReasonEnum::ConnectionRefused;
    case net::ERR_CONNECTION_ABORTED:
      return Network::ErrorReasonEnum::ConnectionAborted;
    case net::ERR_CONNECTION_FAILED:
      return Network::ErrorReasonEnum::ConnectionFailed;
    case net::ERR_NAME_NOT_RESOLVED:
      return Network::ErrorReasonEnum::NameNotResolved;
    case net::ERR_INTERNET_DISCONNECTED:
      return Network::ErrorReasonEnum::InternetDisconnected;
    case net::ERR_ADDRESS_UNREACHABLE:
      return Network::ErrorReasonEnum::AddressUnreachable;
    case net::ERR_BLOCKED_BY_CLIENT:
      return Network::ErrorReasonEnum::BlockedByClient;
    case net::ERR_BLOCKED_BY_RESPONSE:
      return Network::ErrorReasonEnum::BlockedByResponse;
    default:
      return Network::ErrorReasonEnum::Failed;
  }
}


// static
const char* NetworkHandler::ResourceTypeToString(
    blink::mojom::ResourceType resource_type) {
  switch (resource_type) {
    case blink::mojom::ResourceType::kMainFrame:
      return protocol::Network::ResourceTypeEnum::Document;
    case blink::mojom::ResourceType::kSubFrame:
      return protocol::Network::ResourceTypeEnum::Document;
    case blink::mojom::ResourceType::kStylesheet:
      return protocol::Network::ResourceTypeEnum::Stylesheet;
    case blink::mojom::ResourceType::kScript:
      return protocol::Network::ResourceTypeEnum::Script;
    case blink::mojom::ResourceType::kImage:
      return protocol::Network::ResourceTypeEnum::Image;
    case blink::mojom::ResourceType::kFontResource:
      return protocol::Network::ResourceTypeEnum::Font;
    case blink::mojom::ResourceType::kSubResource:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kObject:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kMedia:
      return protocol::Network::ResourceTypeEnum::Media;
    case blink::mojom::ResourceType::kWorker:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kSharedWorker:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kPrefetch:
      return protocol::Network::ResourceTypeEnum::Fetch;
    case blink::mojom::ResourceType::kFavicon:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kXhr:
      return protocol::Network::ResourceTypeEnum::XHR;
    case blink::mojom::ResourceType::kPing:
      return protocol::Network::ResourceTypeEnum::Ping;
    case blink::mojom::ResourceType::kServiceWorker:
      return protocol::Network::ResourceTypeEnum::Other;
    case blink::mojom::ResourceType::kCspReport:
      return protocol::Network::ResourceTypeEnum::CSPViolationReport;
    case blink::mojom::ResourceType::kPluginResource:
      return protocol::Network::ResourceTypeEnum::Other;
    default:
      return protocol::Network::ResourceTypeEnum::Other;
  }
}

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

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

void NetworkHandler::SetRenderer(int render_process_host_id,
                                 RenderFrameHostImpl* frame_host) {
  RenderProcessHost* process_host =
      RenderProcessHost::FromID(render_process_host_id);
  if (process_host) {
    storage_partition_ = process_host->GetStoragePartition();
    browser_context_ = process_host->GetBrowserContext();
  } else {
    storage_partition_ = nullptr;
    browser_context_ = nullptr;
  }
  MaybeEnableDurableMessages(base::DoNothing());
  host_ = frame_host;
  if (background_sync_restorer_) {
    background_sync_restorer_->SetStoragePartition(storage_partition_);
  }
}

void NetworkHandler::SetStoragePartition(StoragePartition* storage_partition) {
  storage_partition_ = storage_partition;
}

Response NetworkHandler::Enable(
    std::optional<int> max_total_size,
    std::optional<int> max_resource_size,
    std::optional<int> max_post_data_size,
    std::optional<bool> report_direct_socket_traffic,
    std::optional<bool> enable_durable_messages) {
  // Durable Messages require a maxTotalBufferSize to be set, for enabling
  // collection.
  durable_message_max_total_size_ = max_total_size.value_or(0);
  if (enable_durable_messages.value_or(false) &&
      !durable_message_max_total_size_) {
    return Response::InvalidParams(
        "maxTotalBufferSize is required with enableDurableMessages");
  }
  enable_durable_messages_ = enable_durable_messages.value_or(false);
  enabled_ = true;
  if (enable_durable_messages_) {
    // MaybeEnableDurableMessages will asynchronously enable durable messages
    // collection if possible, if used with enable(). This will be deprecated,
    // in favor of enableDurableMessages in the future.
    MaybeEnableDurableMessages(base::DoNothing());
  }
  if (enable_durable_messages.has_value() &&
      enable_durable_messages.value() == false) {
    // If an explicit `false` is passed, any active collector should be
    // disabled for this profile.
    DisableDurableMessages();
  }
  return Response::FallThrough();
}

DispatchResponse NetworkHandler::Disable() {
  enabled_ = false;
  if (network_conditions_configured_) {
    SetNetworkConditions({}, /*offline=*/false);
  }
  extra_headers_.clear();
  session()->browser_originating_session_state()->extra_request_headers.clear();
  enable_third_party_cookie_restriction_ = false;
#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
  device_bound_session_receiver_.reset();
#endif  // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
  DisableDurableMessages();
  return Response::FallThrough();
}

#if BUILDFLAG(ENABLE_REPORTING)

namespace {

String BuildReportStatus(const net::ReportingReport::Status status) {
  switch (status) {
    case net::ReportingReport::Status::QUEUED:
      return protocol::Network::ReportStatusEnum::Queued;
    case net::ReportingReport::Status::PENDING:
      return protocol::Network::ReportStatusEnum::Pending;
    case net::ReportingReport::Status::DOOMED:
      return protocol::Network::ReportStatusEnum::MarkedForRemoval;
    case net::ReportingReport::Status::SUCCESS:
      return protocol::Network::ReportStatusEnum::Success;
  }
}

std::vector<GURL> ComputeReportingURLs(RenderFrameHostImpl* frame_host) {
  std::vector<GURL> urls;
  frame_host->ForEachRenderFrameHostImplWithAction(
      [frame_host, &urls](content::RenderFrameHostImpl* rfh) {
        if (rfh != frame_host && (rfh->is_local_root_subframe() ||
                                  &rfh->GetPage() != &frame_host->GetPage())) {
          return content::RenderFrameHost::FrameIterationAction::kSkipChildren;
        }
        urls.push_back(frame_host->GetLastCommittedURL());
        return content::RenderFrameHost::FrameIterationAction::kContinue;
      });
  return urls;
}

}  // namespace

std::unique_ptr<protocol::Network::ReportingApiReport>
NetworkHandler::BuildProtocolReport(const net::ReportingReport& report) {
  if (!host_) {
    return nullptr;
  }
  std::vector<GURL> reporting_filter_urls = ComputeReportingURLs(host_);
  if (std::ranges::contains(reporting_filter_urls, report.url)) {
    return protocol::Network::ReportingApiReport::Create()
        .SetId(report.id.ToString())
        .SetInitiatorUrl(report.url.spec())
        .SetDestination(report.group)
        .SetType(report.type)
        .SetTimestamp((base::Time::Now() -
                       (base::TimeTicks::Now() - report.queued) -
                       base::Time::UnixEpoch())
                          .InSecondsF())
        .SetDepth(report.depth)
        .SetCompletedAttempts(report.attempts)
        .SetBody(std::make_unique<base::DictValue>(report.body.Clone()))
        .SetStatus(BuildReportStatus(report.status))
        .Build();
  }
  return nullptr;
}

void NetworkHandler::OnReportAdded(const net::ReportingReport& report) {
  auto protocol_report = BuildProtocolReport(report);
  if (protocol_report) {
    frontend_->ReportingApiReportAdded(std::move(protocol_report));
  }
}

void NetworkHandler::OnReportUpdated(const net::ReportingReport& report) {
  auto protocol_report = BuildProtocolReport(report);
  if (protocol_report) {
    frontend_->ReportingApiReportUpdated(std::move(protocol_report));
  }
}

std::unique_ptr<protocol::Network::ReportingApiEndpoint>
NetworkHandler::BuildProtocolEndpoint(const net::ReportingEndpoint& endpoint) {
  return protocol::Network::ReportingApiEndpoint::Create()
      .SetUrl(endpoint.info.url.spec())
      .SetGroupName(endpoint.group_key.group_name)
      .Build();
}

void NetworkHandler::OnEndpointsUpdatedForOrigin(
    const std::vector<::net::ReportingEndpoint>& endpoints) {
  if (!host_ || endpoints.empty()) {
    return;
  }
  // Endpoint should have an origin.
  DCHECK(endpoints[0].group_key.origin.has_value());
  url::Origin origin = endpoints[0].group_key.origin.value();
  DCHECK(std::ranges::all_of(endpoints, [&](auto const& endpoint) {
    // Endpoint should have an origin.
    DCHECK(endpoint.group_key.origin.has_value());
    return endpoint.group_key.origin.value() == origin;
  }));
  std::vector<GURL> reporting_filter_urls = ComputeReportingURLs(host_);

  // Only send protocol event if the origin of the updated endpoints matches
  // an origin in the local frame tree.
  if (std::ranges::any_of(reporting_filter_urls, [&](auto const& url) {
        return url::Origin::Create(url) == origin;
      })) {
    auto protocol_endpoints = std::make_unique<
        protocol::Array<protocol::Network::ReportingApiEndpoint>>();
    protocol_endpoints->reserve(endpoints.size());
    for (const auto& endpoint : endpoints) {
      protocol_endpoints->push_back(BuildProtocolEndpoint(endpoint));
    }
    frontend_->ReportingApiEndpointsChangedForOrigin(
        origin.Serialize(), std::move(protocol_endpoints));
  }
}

Response NetworkHandler::EnableReportingApi(const bool enable) {
  if (!storage_partition_ || !host_) {
    return Response::InternalError();
  }

  if (enable) {
    if (!reporting_receiver_.is_bound()) {
      mojo::PendingRemote<network::mojom::ReportingApiObserver> observer;
      reporting_receiver_.Bind(observer.InitWithNewPipeAndPassReceiver());
      storage_partition_->GetNetworkContext()->AddReportingApiObserver(
          std::move(observer));
    }
  } else {
    reporting_receiver_.reset();
  }
  return Response::Success();
}

#else
Response NetworkHandler::EnableReportingApi(const bool enable) {
  return Response::InternalError();
}
#endif  // BUILDFLAG(ENABLE_REPORTING)

#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)

namespace {
String BuildProtocolDeviceBoundSessionUrlRuleType(
    net::device_bound_sessions::InclusionResult rule_type) {
  switch (rule_type) {
    case net::device_bound_sessions::InclusionResult::kExclude:
      return protocol::Network::DeviceBoundSessionUrlRule::RuleTypeEnum::
          Exclude;
    case net::device_bound_sessions::InclusionResult::kInclude:
      return protocol::Network::DeviceBoundSessionUrlRule::RuleTypeEnum::
          Include;
  }
}

std::unique_ptr<protocol::Network::DeviceBoundSessionUrlRule>
BuildProtocolDeviceBoundSessionUrlRule(
    const net::device_bound_sessions::UrlRuleDisplay& rule) {
  return protocol::Network::DeviceBoundSessionUrlRule::Create()
      .SetRuleType(BuildProtocolDeviceBoundSessionUrlRuleType(rule.rule_type))
      .SetHostPattern(rule.host_pattern)
      .SetPathPrefix(rule.path_prefix)
      .Build();
}

std::unique_ptr<protocol::Network::DeviceBoundSessionInclusionRules>
BuildProtocolDeviceBoundDeviceBoundSessionInclusionRules(
    const net::device_bound_sessions::SessionInclusionRulesDisplay& rules) {
  auto protocol_rules = std::make_unique<
      protocol::Array<protocol::Network::DeviceBoundSessionUrlRule>>();
  protocol_rules->reserve(rules.url_rules.size());
  for (const auto& rule : rules.url_rules) {
    protocol_rules->emplace_back(BuildProtocolDeviceBoundSessionUrlRule(rule));
  }
  return protocol::Network::DeviceBoundSessionInclusionRules::Create()
      .SetOrigin(rules.origin)
      .SetIncludeSite(rules.include_site)
      .SetUrlRules(std::move(protocol_rules))
      .Build();
}

std::unique_ptr<protocol::Network::DeviceBoundSessionCookieCraving>
BuildProtocolDeviceBoundDeviceBoundSessionCookieCraving(
    const net::device_bound_sessions::CookieCravingDisplay& craving) {
  auto protocol_craving =
      protocol::Network::DeviceBoundSessionCookieCraving::Create()
          .SetName(craving.name)
          .SetDomain(craving.domain)
          .SetPath(craving.path)
          .SetSecure(craving.secure)
          .SetHttpOnly(craving.http_only)
          .Build();
  std::optional<Network::CookieSameSite> same_site =
      BuildCookieSameSite(craving.same_site);
  if (same_site.has_value()) {
    protocol_craving->SetSameSite(same_site.value());
  }
  return protocol_craving;
}

std::unique_ptr<protocol::Network::DeviceBoundSession>
BuildProtocolDeviceBoundSession(
    const net::device_bound_sessions::SessionDisplay& session) {
  auto protocol_cravings = std::make_unique<
      protocol::Array<protocol::Network::DeviceBoundSessionCookieCraving>>();
  protocol_cravings->reserve(session.cookie_cravings.size());
  for (const auto& craving : session.cookie_cravings) {
    protocol_cravings->emplace_back(
        BuildProtocolDeviceBoundDeviceBoundSessionCookieCraving(craving));
  }
  auto protocol_initiators = std::make_unique<protocol::Array<std::string>>();
  protocol_initiators->reserve(session.allowed_refresh_initiators.size());
  for (const auto& initiator : session.allowed_refresh_initiators) {
    protocol_initiators->emplace_back(initiator);
  }

  auto protocol_session =
      protocol::Network::DeviceBoundSession::Create()
          .SetKey(BuildProtocolDeviceBoundSessionKey(session.key))
          .SetRefreshUrl(session.refresh_url.spec())
          .SetInclusionRules(
              BuildProtocolDeviceBoundDeviceBoundSessionInclusionRules(
                  session.inclusion_rules))
          .SetCookieCravings(std::move(protocol_cravings))
          .SetExpiryDate(session.expiry_date.InSecondsFSinceUnixEpoch())
          .SetAllowedRefreshInitiators(std::move(protocol_initiators))
          .Build();
  if (session.cached_challenge) {
    protocol_session->SetCachedChallenge(session.cached_challenge.value());
  }
  return protocol_session;
}

std::unique_ptr<protocol::Network::DeviceBoundSessionFailedRequest>
BuildProtocolDeviceBoundSessionFailedRequest(
    const net::device_bound_sessions::FailedRequest& failed_request) {
  auto protocol_failed_request =
      protocol::Network::DeviceBoundSessionFailedRequest::Create()
          .SetRequestUrl(failed_request.request_url.spec())
          .Build();
  if (failed_request.net_error.has_value()) {
    protocol_failed_request->SetNetError(
        net::ErrorToString(failed_request.net_error.value()));
  }
  if (failed_request.response_error.has_value()) {
    protocol_failed_request->SetResponseError(
        failed_request.response_error.value());
  }
  if (failed_request.response_error_body.has_value()) {
    protocol_failed_request->SetResponseErrorBody(
        failed_request.response_error_body.value());
  }
  return protocol_failed_request;
}

// LINT.IfChange(DeviceBoundSessionFetchResult)
String BuildProtocolDeviceBoundSessionFetchResult(
    net::device_bound_sessions::SessionError::ErrorType type) {
  switch (type) {
    case net::device_bound_sessions::SessionError::ErrorType::kSuccess:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::Success;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSigningKeyGenerationError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SigningKeyGenerationError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kAttestationKeyGenerationError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          AttestationKeyGenerationError;
    case net::device_bound_sessions::SessionError::ErrorType::kSigningError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::SigningError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kTransientSigningError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          TransientSigningError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kServerRequestedTermination:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          ServerRequestedTermination;
    case net::device_bound_sessions::SessionError::ErrorType::kInvalidSessionId:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidSessionId;
    case net::device_bound_sessions::SessionError::ErrorType::kInvalidChallenge:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidChallenge;
    case net::device_bound_sessions::SessionError::ErrorType::
        kTooManyChallenges:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          TooManyChallenges;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFetcherUrl:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFetcherUrl;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidRefreshUrl:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidRefreshUrl;
    case net::device_bound_sessions::SessionError::ErrorType::
        kTransientHttpError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          TransientHttpError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kScopeOriginSameSiteMismatch:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          ScopeOriginSameSiteMismatch;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRefreshUrlSameSiteMismatch:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RefreshUrlSameSiteMismatch;
    case net::device_bound_sessions::SessionError::ErrorType::
        kMismatchedSessionId:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          MismatchedSessionId;
    case net::device_bound_sessions::SessionError::ErrorType::kMissingScope:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::MissingScope;
    case net::device_bound_sessions::SessionError::ErrorType::kNoCredentials:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          NoCredentials;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSubdomainRegistrationWellKnownUnavailable:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SubdomainRegistrationWellKnownUnavailable;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSubdomainRegistrationUnauthorized:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SubdomainRegistrationUnauthorized;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSubdomainRegistrationWellKnownMalformed:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SubdomainRegistrationWellKnownMalformed;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSessionProviderWellKnownUnavailable:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SessionProviderWellKnownUnavailable;
    case net::device_bound_sessions::SessionError::ErrorType::
        kFederatedKeyThumbprintMismatch:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          FederatedKeyThumbprintMismatch;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFederatedSessionUrl:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFederatedSessionUrl;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFederatedKey:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFederatedKey;
    case net::device_bound_sessions::SessionError::ErrorType::
        kTooManyRelyingOriginLabels:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          TooManyRelyingOriginLabels;
    case net::device_bound_sessions::SessionError::ErrorType::
        kBoundCookieSetForbidden:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          BoundCookieSetForbidden;
    case net::device_bound_sessions::SessionError::ErrorType::kNetError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::NetError;
    case net::device_bound_sessions::SessionError::ErrorType::kProxyError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::ProxyError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidConfigJson:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidConfigJson;
    case net::device_bound_sessions::SessionError::ErrorType::
        kEmptySessionConfig:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          EmptySessionConfig;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsConfig:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsConfig;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsType:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsType;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsEmptyName:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsEmptyName;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookie:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookie;
    case net::device_bound_sessions::SessionError::ErrorType::
        kPersistentHttpError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          PersistentHttpError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRegistrationAttemptedChallenge:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RegistrationAttemptedChallenge;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeOrigin:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeOrigin;
    case net::device_bound_sessions::SessionError::ErrorType::
        kScopeOriginContainsPath:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          ScopeOriginContainsPath;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRefreshInitiatorNotString:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RefreshInitiatorNotString;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRefreshInitiatorInvalidHostPattern:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RefreshInitiatorInvalidHostPattern;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeSpecification:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeSpecification;
    case net::device_bound_sessions::SessionError::ErrorType::
        kMissingScopeSpecificationType:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          MissingScopeSpecificationType;
    case net::device_bound_sessions::SessionError::ErrorType::
        kEmptyScopeSpecificationDomain:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          EmptyScopeSpecificationDomain;
    case net::device_bound_sessions::SessionError::ErrorType::
        kEmptyScopeSpecificationPath:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          EmptyScopeSpecificationPath;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeSpecificationType:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeSpecificationType;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeIncludeSite:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeIncludeSite;
    case net::device_bound_sessions::SessionError::ErrorType::
        kMissingScopeIncludeSite:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          MissingScopeIncludeSite;
    case net::device_bound_sessions::SessionError::ErrorType::
        kFederatedNotAuthorizedByProvider:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          FederatedNotAuthorizedByProvider;
    case net::device_bound_sessions::SessionError::ErrorType::
        kFederatedNotAuthorizedByRelyingParty:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          FederatedNotAuthorizedByRelyingParty;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSessionProviderWellKnownMalformed:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SessionProviderWellKnownMalformed;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSessionProviderWellKnownHasProviderOrigin:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SessionProviderWellKnownHasProviderOrigin;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRelyingPartyWellKnownMalformed:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RelyingPartyWellKnownMalformed;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRelyingPartyWellKnownHasRelyingOrigins:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RelyingPartyWellKnownHasRelyingOrigins;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFederatedSessionProviderSessionMissing:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFederatedSessionProviderSessionMissing;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFederatedSessionWrongProviderOrigin:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFederatedSessionWrongProviderOrigin;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookieCreationTime:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookieCreationTime;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookieName:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookieName;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookieParsing:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookieParsing;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookieUnpermittedAttribute:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookieUnpermittedAttribute;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookieInvalidDomain:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookieInvalidDomain;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidCredentialsCookiePrefix:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidCredentialsCookiePrefix;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeRulePath:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeRulePath;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidScopeRuleHostPattern:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidScopeRuleHostPattern;
    case net::device_bound_sessions::SessionError::ErrorType::
        kScopeRuleOriginScopedHostPatternMismatch:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          ScopeRuleOriginScopedHostPatternMismatch;
    case net::device_bound_sessions::SessionError::ErrorType::
        kScopeRuleSiteScopedHostPatternMismatch:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          ScopeRuleSiteScopedHostPatternMismatch;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSigningQuotaExceeded:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SigningQuotaExceeded;
    case net::device_bound_sessions::SessionError::ErrorType::
        kRelyingPartyWellKnownUnavailable:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          RelyingPartyWellKnownUnavailable;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidFederatedSessionProviderFailedToRestoreKey:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidFederatedSessionProviderFailedToRestoreKey;
    case net::device_bound_sessions::SessionError::ErrorType::
        kFailedToUnwrapKey:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          FailedToUnwrapKey;
    case net::device_bound_sessions::SessionError::ErrorType::
        kSessionDeletedDuringRefresh:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          SessionDeletedDuringRefresh;
    case net::device_bound_sessions::SessionError::ErrorType::
        kCrossOriginRegistrationSiteNotIncluded:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          CrossOriginRegistrationSiteNotIncluded;
    case net::device_bound_sessions::SessionError::ErrorType::
        kInvalidPreProvisionedKeyInitiatorMissing:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          InvalidPreProvisionedKeyInitiatorMissing;
    case net::device_bound_sessions::SessionError::ErrorType::
        kPreProvisionedKeyAccessNotGranted:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          PreProvisionedKeyAccessNotGranted;
    case net::device_bound_sessions::SessionError::ErrorType::
        kPreProvisionedKeyNotFound:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          PreProvisionedKeyNotFound;
    case net::device_bound_sessions::SessionError::ErrorType::
        kAttestationCertificationError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          AttestationCertificationError;
    case net::device_bound_sessions::SessionError::ErrorType::
        kAttestationSigningError:
      return protocol::Network::DeviceBoundSessionFetchResultEnum::
          AttestationSigningError;
  }
}
// LINT.ThenChange(//third_party/blink/public/devtools_protocol/domains/Network.pdl:DeviceBoundSessionFetchResult)

// LINT.IfChange(DeviceBoundSessionRefreshResult)
String BuildProtocolDeviceBoundSessionRefreshResult(
    net::device_bound_sessions::RefreshResult result) {
  switch (result) {
    case net::device_bound_sessions::RefreshResult::kRefreshed:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          Refreshed;
    case net::device_bound_sessions::RefreshResult::kRefreshedAsWaiter:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          RefreshedAsWaiter;
    case net::device_bound_sessions::RefreshResult::kInitializedService:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          InitializedService;
    case net::device_bound_sessions::RefreshResult::kUnreachable:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          Unreachable;
    case net::device_bound_sessions::RefreshResult::kServerError:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          ServerError;
    case net::device_bound_sessions::RefreshResult::kFatalError:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          FatalError;
    case net::device_bound_sessions::RefreshResult::kSigningQuotaExceeded:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          SigningQuotaExceeded;
    case net::device_bound_sessions::RefreshResult::kTransientSigningError:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          TransientSigningError;
    case net::device_bound_sessions::RefreshResult::kInScopeRefreshNotYetNeeded:
      return protocol::Network::RefreshEventDetails::RefreshResultEnum::
          InScopeRefreshNotYetNeeded;
  }
}
// LINT.ThenChange(//third_party/blink/public/devtools_protocol/domains/Network.pdl:DeviceBoundSessionRefreshResult,//net/device_bound_sessions/refresh_result.h:DeviceBoundSessionRefreshResult)

String BuildProtocolDeviceBoundSessionChallengeResult(
    net::device_bound_sessions::ChallengeResult result) {
  switch (result) {
    case net::device_bound_sessions::ChallengeResult::kSuccess:
      return protocol::Network::ChallengeEventDetails::ChallengeResultEnum::
          Success;
    case net::device_bound_sessions::ChallengeResult::kNoSessionId:
      return protocol::Network::ChallengeEventDetails::ChallengeResultEnum::
          NoSessionId;
    case net::device_bound_sessions::ChallengeResult::kNoSessionMatch:
      return protocol::Network::ChallengeEventDetails::ChallengeResultEnum::
          NoSessionMatch;
    case net::device_bound_sessions::ChallengeResult::kCantSetBoundCookie:
      return protocol::Network::ChallengeEventDetails::ChallengeResultEnum::
          CantSetBoundCookie;
  }
}

String BuildProtocolDeviceBoundSessionDeletionReason(
    net::device_bound_sessions::DeletionReason reason) {
  switch (reason) {
    case net::device_bound_sessions::DeletionReason::kExpired:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          Expired;
    case net::device_bound_sessions::DeletionReason::kFailedToRestoreKey:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          FailedToRestoreKey;
    case net::device_bound_sessions::DeletionReason::kFailedToUnwrapKey:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          FailedToUnwrapKey;
    case net::device_bound_sessions::DeletionReason::kStoragePartitionCleared:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          StoragePartitionCleared;
    case net::device_bound_sessions::DeletionReason::kClearBrowsingData:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          ClearBrowsingData;
    case net::device_bound_sessions::DeletionReason::kServerRequested:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          ServerRequested;
    case net::device_bound_sessions::DeletionReason::kInvalidSessionParams:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          InvalidSessionParams;
    case net::device_bound_sessions::DeletionReason::kRefreshFatalError:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          RefreshFatalError;
    case net::device_bound_sessions::DeletionReason::kDevTools:
      return protocol::Network::TerminationEventDetails::DeletionReasonEnum::
          DevTools;
  }
}

}  // namespace

void NetworkHandler::AddDeviceBoundSessionDisplays(
    const std::vector<::net::device_bound_sessions::SessionDisplay>& sessions) {
  auto protocol_sessions = std::make_unique<
      protocol::Array<protocol::Network::DeviceBoundSession>>();
  protocol_sessions->reserve(sessions.size());
  for (const auto& session : sessions) {
    if (client_->MayAttachToURL(session.key.site.GetURL(),
                                host_ && host_->web_ui())) {
      protocol_sessions->emplace_back(BuildProtocolDeviceBoundSession(session));
    }
  }
  frontend_->DeviceBoundSessionsAdded(std::move(protocol_sessions));
}

void NetworkHandler::OnDeviceBoundSessionEventReceived(
    const net::device_bound_sessions::SessionEvent& event) {
  if (!client_->MayAttachToURL(event.site.GetURL(), host_ && host_->web_ui())) {
    return;
  }
  std::unique_ptr<protocol::Network::CreationEventDetails> creationEventDetails;
  std::unique_ptr<protocol::Network::RefreshEventDetails> refreshEventDetails;
  std::unique_ptr<protocol::Network::TerminationEventDetails>
      terminationEventDetails;
  std::unique_ptr<protocol::Network::ChallengeEventDetails>
      challengeEventDetails;
  std::visit(
      absl::Overload{
          [&creationEventDetails](
              const net::device_bound_sessions::CreationEventDetails& details) {
            creationEventDetails =
                protocol::Network::CreationEventDetails::Create()
                    .SetFetchResult(BuildProtocolDeviceBoundSessionFetchResult(
                        details.fetch_error))
                    .Build();
            if (details.new_session_display.has_value()) {
              creationEventDetails->SetNewSession(
                  BuildProtocolDeviceBoundSession(
                      details.new_session_display.value()));
            }
            if (details.failed_request.has_value()) {
              creationEventDetails->SetFailedRequest(
                  BuildProtocolDeviceBoundSessionFailedRequest(
                      details.failed_request.value()));
            }
          },
          [&refreshEventDetails](
              const net::device_bound_sessions::RefreshEventDetails& details) {
            refreshEventDetails =
                protocol::Network::RefreshEventDetails::Create()
                    .SetRefreshResult(
                        BuildProtocolDeviceBoundSessionRefreshResult(
                            details.refresh_result))
                    .SetWasFullyProactiveRefresh(
                        details.was_fully_proactive_refresh)
                    .Build();
            if (details.fetch_error.has_value()) {
              refreshEventDetails->SetFetchResult(
                  BuildProtocolDeviceBoundSessionFetchResult(
                      details.fetch_error.value()));
            }
            if (details.new_session_display.has_value()) {
              refreshEventDetails->SetNewSession(
                  BuildProtocolDeviceBoundSession(
                      details.new_session_display.value()));
            }
            if (details.failed_request.has_value()) {
              refreshEventDetails->SetFailedRequest(
                  BuildProtocolDeviceBoundSessionFailedRequest(
                      details.failed_request.value()));
            }
          },
          [&terminationEventDetails](
              const net::device_bound_sessions::TerminationEventDetails&
                  details) {
            terminationEventDetails =
                protocol::Network::TerminationEventDetails::Create()
                    .SetDeletionReason(
                        BuildProtocolDeviceBoundSessionDeletionReason(
                            details.deletion_reason))
                    .Build();
          },
          [&challengeEventDetails](
              const net::device_bound_sessions::ChallengeEventDetails&
                  details) {
            challengeEventDetails =
                protocol::Network::ChallengeEventDetails::Create()
                    .SetChallengeResult(
                        BuildProtocolDeviceBoundSessionChallengeResult(
                            details.challenge_result))
                    .SetChallenge(details.challenge)
                    .Build();
          }},
      event.event_type_details);

  frontend_->DeviceBoundSessionEventOccurred(
      event.event_id.ToString(), event.site.Serialize(), event.succeeded,
      event.session_id, std::move(creationEventDetails),
      std::move(refreshEventDetails), std::move(terminationEventDetails),
      std::move(challengeEventDetails));
}

Response NetworkHandler::EnableDeviceBoundSessions(bool enable) {
  if (!storage_partition_ || !host_ ||
      !base::FeatureList::IsEnabled(features::kDeviceBoundSessionsDevTools)) {
    return Response::InternalError();
  }

  if (enable) {
    if (!device_bound_session_receiver_.is_bound()) {
      mojo::Remote<network::mojom::DeviceBoundSessionManager> manager;
      storage_partition_->GetNetworkContext()->GetDeviceBoundSessionManager(
          manager.BindNewPipeAndPassReceiver());
      mojo::PendingRemote<network::mojom::DeviceBoundSessionEventObserver>
          observer;
      device_bound_session_receiver_.Bind(
          observer.InitWithNewPipeAndPassReceiver());
      manager->AddEventObserver(std::move(observer));
    }
  } else {
    device_bound_session_receiver_.reset();
  }

  return Response::Success();
}

Response NetworkHandler::DeleteDeviceBoundSession(
    std::unique_ptr<protocol::Network::DeviceBoundSessionKey> key) {
  if (!storage_partition_ || !host_ ||
      !base::FeatureList::IsEnabled(features::kDeviceBoundSessionsDevTools)) {
    return Response::InternalError();
  }

  GURL site_url(key->GetSite());
  if (!site_url.is_valid()) {
    return Response::InvalidParams("Invalid site URL");
  }

  if (!client_->MayAttachToURL(site_url, host_->web_ui())) {
    return Response::InvalidParams("Cannot access session for this site");
  }

  mojo::Remote<network::mojom::DeviceBoundSessionManager> manager;
  storage_partition_->GetNetworkContext()->GetDeviceBoundSessionManager(
      manager.BindNewPipeAndPassReceiver());

  net::device_bound_sessions::SessionKey session_key(
      net::SchemefulSite(site_url),
      net::device_bound_sessions::Session::Id(key->GetId()));

  manager->DeleteSession(net::device_bound_sessions::DeletionReason::kDevTools,
                         session_key);

  return Response::Success();
}

Response NetworkHandler::FetchSchemefulSite(const std::string& origin,
                                            std::string* schemeful_site) {
  *schemeful_site = net::SchemefulSite(GURL(origin)).Serialize();
  return Response::Success();
}
#else
Response NetworkHandler::EnableDeviceBoundSessions(bool enable) {
  return Response::MethodNotFound("not implemented");
}

Response NetworkHandler::DeleteDeviceBoundSession(
    std::unique_ptr<protocol::Network::DeviceBoundSessionKey> key) {
  return Response::MethodNotFound("not implemented");
}

Response NetworkHandler::FetchSchemefulSite(const std::string& origin,
                                            std::string* schemeful_site) {
  return Response::MethodNotFound("not implemented");
}

#endif  // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)

Response NetworkHandler::SetCacheDisabled(bool cache_disabled) {
  cache_disabled_ = cache_disabled;
  return Response::FallThrough();
}

class DevtoolsClearCacheObserver
    : public content::BrowsingDataRemover::Observer {
 public:
  explicit DevtoolsClearCacheObserver(
      content::BrowsingDataRemover* remover,
      std::unique_ptr<NetworkHandler::ClearBrowserCacheCallback> callback)
      : remover_(remover), callback_(std::move(callback)) {
    remover_->AddObserver(this);
  }

  ~DevtoolsClearCacheObserver() override { remover_->RemoveObserver(this); }
  void OnBrowsingDataRemoverDone(uint64_t failed_data_types) override {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    callback_->sendSuccess();
    delete this;
  }

 private:
  raw_ptr<content::BrowsingDataRemover> remover_;
  std::unique_ptr<NetworkHandler::ClearBrowserCacheCallback> callback_;
};

void NetworkHandler::ClearBrowserCache(
    std::unique_ptr<ClearBrowserCacheCallback> callback) {
  if (!browser_context_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  content::BrowsingDataRemover* remover =
      browser_context_->GetBrowsingDataRemover();
  remover->RemoveAndReply(
      base::Time(), base::Time::Max(),
      content::BrowsingDataRemover::DATA_TYPE_CACHE,
      content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB,
      new DevtoolsClearCacheObserver(remover, std::move(callback)));
}

void NetworkHandler::ClearBrowserCookies(
    std::unique_ptr<ClearBrowserCookiesCallback> callback) {
  if (!storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  storage_partition_->GetCookieManagerForBrowserProcess()->DeleteCookies(
      network::mojom::CookieDeletionFilter::New(),
      base::IgnoreArgs<uint32_t>(base::BindOnce(
          &ClearBrowserCookiesCallback::sendSuccess, std::move(callback))));
}

bool NetworkHandler::CanAccessCookie(const net::CanonicalCookie& cookie) const {
  return CanAccessCookie(CHECK_DEREF(client_.get()), host_ && host_->web_ui(),
                         cookie);
}

// static
bool NetworkHandler::CanAccessCookie(DevToolsAgentHostClient& client,
                                     bool is_webui,
                                     const net::CanonicalCookie& cookie) {
  return client.MayAttachToURL(GURL(base::StrCat({url::kHttpsScheme,
                                                  url::kStandardSchemeSeparator,
                                                  cookie.DomainWithoutDot()})),
                               is_webui) &&
         client.MayAttachToURL(
             GURL(base::StrCat({url::kHttpScheme, url::kStandardSchemeSeparator,
                                cookie.DomainWithoutDot()})),
             is_webui);
}

void NetworkHandler::GetCookies(std::unique_ptr<Array<String>> protocol_urls,
                                std::unique_ptr<GetCookiesCallback> callback) {
  if (!host_ || !storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  std::vector<GURL> urls = ComputeCookieURLs(host_, protocol_urls);
  bool is_webui = host_ && host_->web_ui();

  std::erase_if(urls, [=, this](const GURL& url) {
    return !client_->MayAttachToURL(url, is_webui);
  });

  CookieRetrieverNetworkService::Retrieve(
      storage_partition_->GetCookieManagerForBrowserProcess(), urls,
      host_->GetNetworkIsolationKey(), host_->ComputeSiteForCookies(),
      std::move(callback));
}

void NetworkHandler::GetAllCookies(
    std::unique_ptr<GetAllCookiesCallback> callback) {
  if (!storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  storage_partition_->GetCookieManagerForBrowserProcess()->GetAllCookies(
      base::BindOnce(&NetworkHandler::GotAllCookies, weak_factory_.GetWeakPtr(),
                     std::move(callback)));
}

void NetworkHandler::GotAllCookies(
    std::unique_ptr<GetAllCookiesCallback> callback,
    const std::vector<net::CanonicalCookie>& cookies) {
  bool is_webui = host_ && host_->web_ui();
  std::vector<net::CanonicalCookie> filtered_cookies;
  for (const auto& cookie : cookies) {
    if (NetworkHandler::CanAccessCookie(CHECK_DEREF(client_.get()), is_webui,
                                        cookie)) {
      filtered_cookies.emplace_back(std::move(cookie));
    }
  }
  callback->sendSuccess(NetworkHandler::BuildCookieArray(filtered_cookies));
}

void NetworkHandler::SetCookie(
    const std::string& name,
    const std::string& value,
    std::optional<std::string> url,
    std::optional<std::string> domain,
    std::optional<std::string> path,
    std::optional<bool> secure,
    std::optional<bool> http_only,
    std::optional<std::string> same_site,
    std::optional<double> expires,
    std::optional<std::string> priority,
    std::optional<std::string> source_scheme,
    std::optional<int> source_port,
    std::unique_ptr<Network::CookiePartitionKey> partition_key,
    std::unique_ptr<SetCookieCallback> callback) {
  if (!storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  auto cookie_or_error = MakeCookieFromProtocolValues(
      name, value, url.value_or(""), domain.value_or(""), path.value_or(""),
      secure.value_or(false), http_only.value_or(false), same_site.value_or(""),
      expires.value_or(-1), priority.value_or(""), source_scheme, source_port,
      partition_key.get());

  if (std::holds_alternative<Response>(cookie_or_error)) {
    callback->sendFailure(std::get<Response>(std::move(cookie_or_error)));
    return;
  }
  std::unique_ptr<net::CanonicalCookie> cookie =
      std::get<std::unique_ptr<net::CanonicalCookie>>(
          std::move(cookie_or_error));

  if (!NetworkHandler::CanAccessCookie(CHECK_DEREF(client_.get()),
                                       host_ && host_->web_ui(), *cookie)) {
    callback->sendFailure(Response::ServerError("Permission denied"));
    return;
  }

  net::CookieOptions options;
  // Permit it to set a SameSite cookie if it wants to.
  options.set_same_site_cookie_context(
      net::CookieOptions::SameSiteCookieContext::MakeInclusive());
  options.set_include_httponly();
  storage_partition_->GetCookieManagerForBrowserProcess()->SetCanonicalCookie(
      *cookie, net::cookie_util::SimulatedCookieSource(*cookie, "https"),
      options,
      base::BindOnce(net::cookie_util::IsCookieAccessResultInclude)
          .Then(base::BindOnce(&SetCookieCallback::sendSuccess,
                               std::move(callback))));
}

// static
void NetworkHandler::SetCookies(
    StoragePartition* storage_partition,
    std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,
    DevToolsAgentHostClient& client,
    bool is_webui,
    base::OnceCallback<void(bool)> callback) {
  std::vector<std::unique_ptr<net::CanonicalCookie>> net_cookies;
  for (const std::unique_ptr<Network::CookieParam>& cookie : *cookies) {
    // We need to pass std::optional<> to the function below, but we can't
    // directly get to the `cookie`'s std::optional<> so instead we recreate
    // them.
    const std::optional<std::string> source_scheme =
        cookie->HasSourceScheme()
            ? std::optional<std::string>(cookie->GetSourceScheme(""))
            : std::nullopt;
    const std::optional<int> source_port =
        cookie->HasSourcePort() ? std::optional<int>(cookie->GetSourcePort(0))
                                : std::nullopt;

    std::unique_ptr<Network::CookiePartitionKey> partition_key;
    if (cookie->HasPartitionKey()) {
      protocol::Network::CookiePartitionKey* key =
          cookie->GetPartitionKey(nullptr);
      if (key) {
        std::string site = key->GetTopLevelSite();
        if (!site.empty()) {
          partition_key =
              BuildCookiePartitionKey(site, key->GetHasCrossSiteAncestor());
        }
      }
    }

    auto net_cookie_or_error = MakeCookieFromProtocolValues(
        cookie->GetName(), cookie->GetValue(), cookie->GetUrl(""),
        cookie->GetDomain(""), cookie->GetPath(""), cookie->GetSecure(false),
        cookie->GetHttpOnly(false), cookie->GetSameSite(""),
        cookie->GetExpires(-1), cookie->GetPriority(""), source_scheme,
        source_port, partition_key.get());
    if (std::holds_alternative<Response>(net_cookie_or_error)) {
      // TODO: Investiage whether we can report the error as a protocol error
      // (this might be a breaking CDP change).
      std::move(callback).Run(false);
      return;
    }
    auto net_cookie = std::get<std::unique_ptr<net::CanonicalCookie>>(
        std::move(net_cookie_or_error));
    if (!NetworkHandler::CanAccessCookie(client, is_webui, *net_cookie)) {
      std::move(callback).Run(false);
      return;
    }
    net_cookies.push_back(std::move(net_cookie));
  }

  base::RepeatingClosure barrier_closure = base::BarrierClosure(
      net_cookies.size(), base::BindOnce(std::move(callback), true));

  auto* cookie_manager = storage_partition->GetCookieManagerForBrowserProcess();
  net::CookieOptions options;
  options.set_include_httponly();
  // Permit it to set a SameSite cookie if it wants to.
  options.set_same_site_cookie_context(
      net::CookieOptions::SameSiteCookieContext::MakeInclusive());
  for (const auto& cookie : net_cookies) {
    cookie_manager->SetCanonicalCookie(
        *cookie, net::cookie_util::SimulatedCookieSource(*cookie, "https"),
        options,
        base::BindOnce([](base::RepeatingClosure callback,
                          net::CookieAccessResult) { callback.Run(); },
                       barrier_closure));
  }
}

void NetworkHandler::SetCookies(
    std::unique_ptr<protocol::Array<Network::CookieParam>> cookies,
    std::unique_ptr<SetCookiesCallback> callback) {
  if (!storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  CHECK(host_);

  NetworkHandler::SetCookies(
      storage_partition_, std::move(cookies), CHECK_DEREF(client_.get()),
      host_->web_ui(),
      base::BindOnce(
          [](std::unique_ptr<SetCookiesCallback> callback, bool success) {
            if (success) {
              callback->sendSuccess();
            } else {
              callback->sendFailure(
                  Response::InvalidParams(kInvalidCookieFields));
            }
          },
          std::move(callback)));
}

void NetworkHandler::DeleteCookies(
    const std::string& name,
    std::optional<std::string> url_spec,
    std::optional<std::string> domain,
    std::optional<std::string> path,
    std::unique_ptr<Network::CookiePartitionKey> partition_key,
    std::unique_ptr<DeleteCookiesCallback> callback) {
  if (!storage_partition_) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  if (!url_spec.has_value() && !domain.has_value()) {
    callback->sendFailure(Response::InvalidParams(
        "At least one of the url and domain needs to be specified"));
  }

  std::string normalized_domain = domain.value_or("");
  if (normalized_domain.empty()) {
    GURL url(url_spec.value_or(""));
    if (!url.SchemeIsHTTPOrHTTPS()) {
      callback->sendFailure(Response::InvalidParams(
          "An http or https url URL must be specified"));
      return;
    }
    normalized_domain = url.GetHost();
  }

  auto* cookie_manager =
      storage_partition_->GetCookieManagerForBrowserProcess();

  cookie_manager->GetAllCookies(
      base::BindOnce(&DeleteFilteredCookies, base::Unretained(cookie_manager),
                     name, normalized_domain, path.value_or(""),
                     base::Owned(partition_key.release()),
                     /*filter_by_partition_key=*/true,
                     base::BindRepeating(
                         [](base::WeakPtr<NetworkHandler> handler,
                            const net::CanonicalCookie& cookie) {
                           return handler && handler->CanAccessCookie(cookie);
                         },
                         weak_factory_.GetWeakPtr()),
                     base::BindOnce(&DeleteCookiesCallback::sendSuccess,
                                    std::move(callback))));
}

Response NetworkHandler::SetExtraHTTPHeaders(
    std::unique_ptr<protocol::Network::Headers> headers) {
  std::vector<std::pair<std::string, std::string>> new_headers;
  base::flat_map<std::string, std::string> extra_request_headers;
  for (const auto entry : *headers) {
    if (!entry.second.is_string()) {
      return Response::InvalidParams("Invalid header value, string expected");
    }
    if (!net::HttpUtil::IsValidHeaderName(entry.first)) {
      return Response::InvalidParams("Invalid header name");
    }
    const std::string& value = entry.second.GetString();
    if (!net::HttpUtil::IsValidHeaderValue(value)) {
      return Response::InvalidParams("Invalid header value");
    }
    new_headers.emplace_back(entry.first, value);
    extra_request_headers[entry.first] = value;
  }
  extra_headers_.swap(new_headers);
  session()->browser_originating_session_state()->extra_request_headers =
      std::move(extra_request_headers);
  return Response::FallThrough();
}

Response NetworkHandler::CanEmulateNetworkConditions(bool* result) {
  *result = true;
  return Response::Success();
}

Response NetworkHandler::EmulateNetworkConditions(
    bool offline,
    double latency,
    double download_throughput,
    double upload_throughput,
    std::optional<protocol::Network::ConnectionType>,
    std::optional<double> packet_loss,
    std::optional<int> packet_queue_length,
    std::optional<bool> packet_reordering) {
  std::vector<network::mojom::MatchedNetworkConditionsPtr> network_conditions;
  bool throttling_enabled = offline || latency > 0 || download_throughput > 0 ||
                            upload_throughput > 0;
  if (throttling_enabled) {
    network_conditions.push_back(
        network::mojom::MatchedNetworkConditions::New());
    network_conditions.back()->conditions =
        network::mojom::NetworkConditions::New();
    network_conditions.back()->conditions->offline = offline;
    network_conditions.back()->conditions->latency =
        base::Milliseconds(latency);
    network_conditions.back()->conditions->download_throughput =
        download_throughput;
    network_conditions.back()->conditions->upload_throughput =
        upload_throughput;
    network_conditions.back()->conditions->packet_loss =
        packet_loss.value_or(0.);
    network_conditions.back()->conditions->packet_queue_length =
        packet_queue_length.value_or(0);
    network_conditions.back()->conditions->packet_reordering =
        packet_reordering.value_or(false);
  }
  SetNetworkConditions(std::move(network_conditions), offline);
  return Response::FallThrough();
}

Response NetworkHandler::EmulateNetworkConditionsByRule(
    std::optional<bool> offline,
    std::optional<bool> emulate_offline_service_worker,
    std::unique_ptr<protocol::Array<protocol::Network::NetworkConditions>>
        matched_network_conditions,
    std::unique_ptr<protocol::Array<String>>* rule_ids_result) {
  std::vector<network::mojom::MatchedNetworkConditionsPtr> matched_conditions;
  *rule_ids_result = std::make_unique<protocol::Array<String>>();
  for (auto& matched_condition : *matched_network_conditions) {
    auto rule_id = base::UnguessableToken::Create();
    network::mojom::MatchedNetworkConditionsPtr conditions =
        network::mojom::MatchedNetworkConditions::New();
    conditions->pattern = matched_condition->GetUrlPattern();
    conditions->conditions = network::mojom::NetworkConditions::New();
    conditions->conditions->offline =
        offline.has_value() ? offline.value()
                            : matched_condition->GetOffline(false);
    conditions->conditions->latency =
        base::Milliseconds(matched_condition->GetLatency());
    conditions->conditions->download_throughput =
        matched_condition->GetDownloadThroughput();
    conditions->conditions->upload_throughput =
        matched_condition->GetUploadThroughput();
    conditions->conditions->packet_loss = matched_condition->GetPacketLoss(0.);
    conditions->conditions->packet_queue_length =
        matched_condition->GetPacketQueueLength(0);
    conditions->conditions->packet_reordering =
        matched_condition->GetPacketReordering(false);
    conditions->conditions->rule_id = rule_id;
    rule_ids_result->get()->push_back(rule_id.ToString());
    matched_conditions.emplace_back(std::move(conditions));
  }
  SetNetworkConditions(
      std::move(matched_conditions),
      emulate_offline_service_worker.value_or(offline.value_or(false)));
  return Response::Success();
}

Response NetworkHandler::SetBypassServiceWorker(bool bypass) {
  bypass_service_worker_ = bypass;
  return Response::FallThrough();
}

namespace {

std::unique_ptr<protocol::Network::SecurityDetails> BuildSecurityDetails(
    const net::SSLInfo& ssl_info) {
  // This function should be kept in sync with the corresponding function in
  // inspector_network_agent.cc in //third_party/blink.
  if (!ssl_info.cert) {
    return nullptr;
  }
  auto signed_certificate_timestamp_list =
      std::make_unique<protocol::Array<Network::SignedCertificateTimestamp>>();
  for (auto const& sct : ssl_info.signed_certificate_timestamps) {
    std::unique_ptr<protocol::Network::SignedCertificateTimestamp>
        signed_certificate_timestamp =
            Network::SignedCertificateTimestamp::Create()
                .SetStatus(net::ct::StatusToString(sct.status))
                .SetOrigin(net::ct::OriginToString(sct.sct->origin))
                .SetLogDescription(sct.sct->log_description)
                .SetLogId(base::HexEncode(sct.sct->log_id))
                .SetTimestamp((sct.sct->timestamp - base::Time::UnixEpoch())
                                  .InMillisecondsF())
                .SetHashAlgorithm(net::ct::HashAlgorithmToString(
                    sct.sct->signature.hash_algorithm))
                .SetSignatureAlgorithm(net::ct::SignatureAlgorithmToString(
                    sct.sct->signature.signature_algorithm))
                .SetSignatureData(
                    base::HexEncode(sct.sct->signature.signature_data))
                .Build();
    signed_certificate_timestamp_list->emplace_back(
        std::move(signed_certificate_timestamp));
  }
  std::vector<std::string> san_dns;
  std::vector<std::string> san_ip;
  ssl_info.cert->GetSubjectAltName(&san_dns, &san_ip);
  auto san_list = std::make_unique<protocol::Array<String>>(std::move(san_dns));
  for (const std::string& san : san_ip) {
    san_list->emplace_back(net::IPAddress(base::as_byte_span(san)).ToString());
  }

  const char* protocol = "";
  const char* key_exchange = "";
  const char* cipher = "";
  const char* mac = nullptr;

  int ssl_version =
      net::SSLConnectionStatusToVersion(ssl_info.connection_status);

  if (ssl_info.connection_status) {
    net::SSLVersionToString(&protocol, ssl_version);

    bool is_aead;
    bool is_tls13;
    uint16_t cipher_suite =
        net::SSLConnectionStatusToCipherSuite(ssl_info.connection_status);
    net::SSLCipherSuiteToStrings(&key_exchange, &cipher, &mac, &is_aead,
                                 &is_tls13, cipher_suite);
    if (key_exchange == nullptr) {
      DCHECK(is_tls13);
      key_exchange = "";
    }
  }

  std::unique_ptr<protocol::Network::SecurityDetails> security_details =
      protocol::Network::SecurityDetails::Create()
          .SetProtocol(protocol)
          .SetKeyExchange(key_exchange)
          .SetCipher(cipher)
          .SetSubjectName(ssl_info.cert->subject().common_name)
          .SetSanList(std::move(san_list))
          .SetIssuer(ssl_info.cert->issuer().common_name)
          .SetValidFrom(ssl_info.cert->valid_start().InSecondsFSinceUnixEpoch())
          .SetValidTo(ssl_info.cert->valid_expiry().InSecondsFSinceUnixEpoch())
          .SetCertificateId(0)  // Keep this in protocol for compatibility.
          .SetSignedCertificateTimestampList(
              std::move(signed_certificate_timestamp_list))
          .SetCertificateTransparencyCompliance(
              SerializeCTPolicyCompliance(ssl_info.ct_policy_compliance))
          .SetEncryptedClientHello(ssl_info.encrypted_client_hello)
          .Build();

  if (ssl_info.key_exchange_group != 0) {
    const char* key_exchange_group =
        SSL_get_curve_name(ssl_info.key_exchange_group);
    if (key_exchange_group) {
      security_details->SetKeyExchangeGroup(key_exchange_group);
    }
  }
  if (mac) {
    security_details->SetMac(mac);
  }
  if (ssl_info.peer_signature_algorithm != 0) {
    security_details->SetServerSignatureAlgorithm(
        ssl_info.peer_signature_algorithm);
  }

  return security_details;
}

std::unique_ptr<base::DictValue> BuildResponseHeaders(
    const net::HttpResponseHeaders* headers) {
  auto headers_dict = std::make_unique<base::DictValue>();
  if (!headers) {
    return headers_dict;
  }
  size_t iterator = 0;
  std::string name;
  std::string value;
  while (headers->EnumerateHeaderLines(&iterator, &name, &value)) {
    base::Value* header_value = headers_dict->Find(name);
    if (header_value) {
      *header_value = base::Value(header_value->GetString() + '\n' + value);
    } else {
      headers_dict->Set(name, value);
    }
  }
  return headers_dict;
}

std::unique_ptr<base::DictValue> BuildRequestHeaders(
    const net::HttpRequestHeaders& headers,
    const GURL& referrer) {
  auto headers_dict = std::make_unique<base::DictValue>();
  for (net::HttpRequestHeaders::Iterator it(headers); it.GetNext();) {
    // Skip case variants of the referer header if it's added below.
    if (!referrer.is_empty() &&
        base::EqualsCaseInsensitiveASCII(it.name(),
                                         net::HttpRequestHeaders::kReferer)) {
      continue;
    }
    headers_dict->Set(it.name(), it.value());
  }

  // This is normally added down the stack, so we have to fake it here.
  if (!referrer.is_empty()) {
    headers_dict->Set(net::HttpRequestHeaders::kReferer, referrer.spec());
  }

  return headers_dict;
}

String BuildServiceWorkerResponseSource(
    const network::mojom::URLResponseHeadDevToolsInfo& info) {
  switch (info.service_worker_response_source) {
    case network::mojom::FetchResponseSource::kCacheStorage:
      return protocol::Network::ServiceWorkerResponseSourceEnum::CacheStorage;
    case network::mojom::FetchResponseSource::kHttpCache:
      return protocol::Network::ServiceWorkerResponseSourceEnum::HttpCache;
    case network::mojom::FetchResponseSource::kNetwork:
      return protocol::Network::ServiceWorkerResponseSourceEnum::Network;
    case network::mojom::FetchResponseSource::kUnspecified:
      return protocol::Network::ServiceWorkerResponseSourceEnum::FallbackCode;
  }
}

String BuildServiceWorkerRouterSourceType(
    const network::mojom::ServiceWorkerRouterSourceType& type) {
  switch (type) {
    case network::mojom::ServiceWorkerRouterSourceType::kNetwork:
      return protocol::Network::ServiceWorkerRouterSourceEnum::Network;
    case network::mojom::ServiceWorkerRouterSourceType::
        kRaceNetworkAndFetchEvent:
      return protocol::Network::ServiceWorkerRouterSourceEnum::
          RaceNetworkAndFetchHandler;
    case network::mojom::ServiceWorkerRouterSourceType::kFetchEvent:
      return protocol::Network::ServiceWorkerRouterSourceEnum::FetchEvent;
    case network::mojom::ServiceWorkerRouterSourceType::kCache:
      return protocol::Network::ServiceWorkerRouterSourceEnum::Cache;
    case network::mojom::ServiceWorkerRouterSourceType::kRaceNetworkAndCache:
      return protocol::Network::ServiceWorkerRouterSourceEnum::
          RaceNetworkAndCache;
  }
}

String AlternateProtocolUsageToString(
    net::AlternateProtocolUsage alternate_protocol_usage) {
  switch (alternate_protocol_usage) {
    case net::AlternateProtocolUsage::ALTERNATE_PROTOCOL_USAGE_NO_RACE:
      return protocol::Network::AlternateProtocolUsageEnum::
          AlternativeJobWonWithoutRace;
    case net::AlternateProtocolUsage::ALTERNATE_PROTOCOL_USAGE_WON_RACE:
      return protocol::Network::AlternateProtocolUsageEnum::
          AlternativeJobWonRace;
    case net::AlternateProtocolUsage::
        ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE:
      return protocol::Network::AlternateProtocolUsageEnum::MainJobWonRace;
    case net::AlternateProtocolUsage::ALTERNATE_PROTOCOL_USAGE_MAPPING_MISSING:
      return protocol::Network::AlternateProtocolUsageEnum::MappingMissing;
    case net::AlternateProtocolUsage::ALTERNATE_PROTOCOL_USAGE_BROKEN:
      return protocol::Network::AlternateProtocolUsageEnum::Broken;
    case net::AlternateProtocolUsage::
        ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_WITHOUT_RACE:
      return protocol::Network::AlternateProtocolUsageEnum::
          DnsAlpnH3JobWonWithoutRace;
    case net::AlternateProtocolUsage::
        ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE:
      return protocol::Network::AlternateProtocolUsageEnum::DnsAlpnH3JobWonRace;
    case net::AlternateProtocolUsage::
        ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON:
      return protocol::Network::AlternateProtocolUsageEnum::UnspecifiedReason;
    case net::AlternateProtocolUsage::ALTERNATE_PROTOCOL_USAGE_MAX:
      return protocol::Network::AlternateProtocolUsageEnum::UnspecifiedReason;
  }
  return protocol::Network::AlternateProtocolUsageEnum::UnspecifiedReason;
}

std::unique_ptr<Network::Response> BuildResponse(
    const GURL& url,
    const network::mojom::URLResponseHeadDevToolsInfo& info) {
  int status = 0;
  std::string status_text;
  if (info.headers) {
    status = info.headers->response_code();
    status_text = info.headers->GetStatusText();
  } else if (url.SchemeIs(url::kDataScheme)) {
    status = net::HTTP_OK;
    status_text = "OK";
  }

  const bool was_cached =
      !info.load_timing.request_start_time.is_null() &&
      info.response_time < info.load_timing.request_start_time;

  std::string url_fragment;
  auto response =
      Network::Response::Create()
          .SetUrl(NetworkHandler::ExtractFragment(url, &url_fragment))
          .SetStatus(status)
          .SetStatusText(status_text)
          .SetHeaders(BuildResponseHeaders(info.headers.get()))
          .SetMimeType(info.mime_type)
          .SetCharset(info.charset)
          .SetConnectionReused(info.load_timing.socket_reused)
          .SetConnectionId(info.load_timing.socket_log_id)
          .SetSecurityState(securityState(url, info.cert_status))
          .SetEncodedDataLength(info.encoded_data_length)
          .SetTiming(GetTiming(info.load_timing))
          .SetFromDiskCache(was_cached)
          .Build();
  response->SetFromServiceWorker(info.was_fetched_via_service_worker);
  if (info.was_fetched_via_service_worker) {
    response->SetServiceWorkerResponseSource(
        BuildServiceWorkerResponseSource(info));
  }
  response->SetFromPrefetchCache(info.was_in_prefetch_cache);
  if (!info.response_time.is_null()) {
    response->SetResponseTime(
        info.response_time.InMillisecondsFSinceUnixEpochIgnoringNull());
  }
  if (!info.cache_storage_cache_name.empty()) {
    response->SetCacheStorageCacheName(info.cache_storage_cache_name);
  }
  if (!info.service_worker_router_info.is_null()) {
    auto service_worker_router_info =
        protocol::Network::ServiceWorkerRouterInfo::Create().Build();
    if (info.service_worker_router_info->rule_id_matched) {
      service_worker_router_info->SetRuleIdMatched(
          *info.service_worker_router_info->rule_id_matched);
    }

    if (info.service_worker_router_info->matched_source_type) {
      service_worker_router_info->SetMatchedSourceType(
          BuildServiceWorkerRouterSourceType(
              *info.service_worker_router_info->matched_source_type));
    }

    if (info.service_worker_router_info->actual_source_type) {
      service_worker_router_info->SetActualSourceType(
          BuildServiceWorkerRouterSourceType(
              *info.service_worker_router_info->actual_source_type));
    }

    response->SetServiceWorkerRouterInfo(std::move(service_worker_router_info));
  }

  response->SetProtocol(GetProtocol(url, info));
  if (info.alternate_protocol_usage !=
      net::AlternateProtocolUsage::
          ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON) {
    response->SetAlternateProtocolUsage(
        AlternateProtocolUsageToString(info.alternate_protocol_usage));
  }
  response->SetRemoteIPAddress(
      net::HostPortPair::FromIPEndPoint(info.remote_endpoint).HostForURL());
  response->SetRemotePort(info.remote_endpoint.port());
  if (info.ssl_info.has_value()) {
    response->SetSecurityDetails(BuildSecurityDetails(*info.ssl_info));
  }

  return response;
}

std::unique_ptr<Network::Response> BuildRedirectResponse(
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info,
    bool& redirect_emitted_extra_info) {
  std::unique_ptr<Network::Response> redirect_response;
  if (redirect_info) {
    const auto& [previous_url, head] = *redirect_info;
    redirect_emitted_extra_info = head.emitted_extra_info;
    redirect_response = BuildResponse(previous_url, head);
  }
  return redirect_response;
}

String blockedReason(blink::ResourceRequestBlockedReason reason) {
  switch (reason) {
    case blink::ResourceRequestBlockedReason::kCSP:
      return protocol::Network::BlockedReasonEnum::Csp;
    case blink::ResourceRequestBlockedReason::kMixedContent:
      return protocol::Network::BlockedReasonEnum::MixedContent;
    case blink::ResourceRequestBlockedReason::kOrigin:
      return protocol::Network::BlockedReasonEnum::Origin;
    case blink::ResourceRequestBlockedReason::kInspector:
      return protocol::Network::BlockedReasonEnum::Inspector;
    case blink::ResourceRequestBlockedReason::kIntegrity:
      return protocol::Network::BlockedReasonEnum::Integrity;
    case blink::ResourceRequestBlockedReason::kSubresourceFilter:
      return protocol::Network::BlockedReasonEnum::SubresourceFilter;
    case blink::ResourceRequestBlockedReason::kContentType:
      return protocol::Network::BlockedReasonEnum::ContentType;
    case blink::ResourceRequestBlockedReason::kOther:
      return protocol::Network::BlockedReasonEnum::Other;
    case blink::ResourceRequestBlockedReason::kCoepFrameResourceNeedsCoepHeader:
      return protocol::Network::BlockedReasonEnum::
          CoepFrameResourceNeedsCoepHeader;
    case blink::ResourceRequestBlockedReason::
        kCoopSandboxedIFrameCannotNavigateToCoopPage:
      return protocol::Network::BlockedReasonEnum::
          CoopSandboxedIframeCannotNavigateToCoopPage;
    case blink::ResourceRequestBlockedReason::kCorpNotSameOrigin:
      return protocol::Network::BlockedReasonEnum::CorpNotSameOrigin;
    case blink::ResourceRequestBlockedReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByCoep:
      return protocol::Network::BlockedReasonEnum::
          CorpNotSameOriginAfterDefaultedToSameOriginByCoep;
    case blink::ResourceRequestBlockedReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByDip:
      return protocol::Network::BlockedReasonEnum::
          CorpNotSameOriginAfterDefaultedToSameOriginByDip;
    case blink::ResourceRequestBlockedReason::
        kCorpNotSameOriginAfterDefaultedToSameOriginByCoepAndDip:
      return protocol::Network::BlockedReasonEnum::
          CorpNotSameOriginAfterDefaultedToSameOriginByCoep;
    case blink::ResourceRequestBlockedReason::kCorpNotSameSite:
      return protocol::Network::BlockedReasonEnum::CorpNotSameSite;
    case blink::ResourceRequestBlockedReason::kSRIMessageSignatureMismatch:
      return protocol::Network::BlockedReasonEnum::SriMessageSignatureMismatch;
    case blink::ResourceRequestBlockedReason::kConversionRequest:
      // This is actually never reached, as the conversion request
      // is marked as successful and no blocking reason is reported.
      NOTREACHED();
  }
  NOTREACHED();
}

std::optional<String> GetBlockedReasonFor(
    const network::URLLoaderCompletionStatus& status) {
  if (status.blocked_by_response_reason) {
    switch (*status.blocked_by_response_reason) {
      case network::mojom::BlockedByResponseReason::
          kCoepFrameResourceNeedsCoepHeader:
        return {protocol::Network::BlockedReasonEnum::
                    CoepFrameResourceNeedsCoepHeader};
      case network::mojom::BlockedByResponseReason::
          kCoopSandboxedIFrameCannotNavigateToCoopPage:
        return {protocol::Network::BlockedReasonEnum::
                    CoopSandboxedIframeCannotNavigateToCoopPage};
      case network::mojom::BlockedByResponseReason::
          kCorpNotSameOriginAfterDefaultedToSameOriginByCoep:
        return {protocol::Network::BlockedReasonEnum::
                    CorpNotSameOriginAfterDefaultedToSameOriginByCoep};
      case network::mojom::BlockedByResponseReason::
          kCorpNotSameOriginAfterDefaultedToSameOriginByDip:
        return {protocol::Network::BlockedReasonEnum::
                    CorpNotSameOriginAfterDefaultedToSameOriginByDip};
      case network::mojom::BlockedByResponseReason::
          kCorpNotSameOriginAfterDefaultedToSameOriginByCoepAndDip:
        return {protocol::Network::BlockedReasonEnum::
                    CorpNotSameOriginAfterDefaultedToSameOriginByCoepAndDip};
      case network::mojom::BlockedByResponseReason::kCorpNotSameOrigin:
        return {protocol::Network::BlockedReasonEnum::CorpNotSameOrigin};
      case network::mojom::BlockedByResponseReason::kCorpNotSameSite:
        return {protocol::Network::BlockedReasonEnum::CorpNotSameSite};
      case network::mojom::BlockedByResponseReason::
          kSRIMessageSignatureMismatch:
        return {
            protocol::Network::BlockedReasonEnum::SriMessageSignatureMismatch};
    }
    NOTREACHED();
  }
  if (status.error_code != net::ERR_BLOCKED_BY_CLIENT &&
      status.error_code != net::ERR_BLOCKED_BY_RESPONSE) {
    return std::nullopt;
  }

  if (status.extended_error_code <=
      static_cast<int>(blink::ResourceRequestBlockedReason::kMax)) {
    return blockedReason(static_cast<blink::ResourceRequestBlockedReason>(
        status.extended_error_code));
  }

  // TODO(karandeepb): Embedder would know how to interpret the
  // `status.extended_error_code` in this case. For now just return Other.
  return {protocol::Network::BlockedReasonEnum::Other};
}

String GetTrustTokenOperationType(
    network::mojom::TrustTokenOperationType operation) {
  switch (operation) {
    case network::mojom::TrustTokenOperationType::kIssuance:
      return protocol::Network::TrustTokenOperationTypeEnum::Issuance;
    case network::mojom::TrustTokenOperationType::kRedemption:
      return protocol::Network::TrustTokenOperationTypeEnum::Redemption;
    case network::mojom::TrustTokenOperationType::kSigning:
      return protocol::Network::TrustTokenOperationTypeEnum::Signing;
  }
}

String GetTrustTokenRefreshPolicy(
    network::mojom::TrustTokenRefreshPolicy policy) {
  switch (policy) {
    case network::mojom::TrustTokenRefreshPolicy::kUseCached:
      return protocol::Network::TrustTokenParams::RefreshPolicyEnum::UseCached;
    case network::mojom::TrustTokenRefreshPolicy::kRefresh:
      return protocol::Network::TrustTokenParams::RefreshPolicyEnum::Refresh;
  }
}

std::unique_ptr<protocol::Network::TrustTokenParams> BuildTrustTokenParams(
    const network::mojom::TrustTokenParams& params) {
  auto protocol_params =
      protocol::Network::TrustTokenParams::Create()
          .SetOperation(GetTrustTokenOperationType(params.operation))
          .SetRefreshPolicy(GetTrustTokenRefreshPolicy(params.refresh_policy))
          .Build();

  if (!params.issuers.empty()) {
    auto issuers = std::make_unique<protocol::Array<protocol::String>>();
    for (const auto& issuer : params.issuers) {
      issuers->push_back(issuer.Serialize());
    }
    protocol_params->SetIssuers(std::move(issuers));
  }

  return protocol_params;
}

}  // namespace

void NetworkHandler::PrefetchRequestWillBeSent(
    const std::string& request_id,
    const network::ResourceRequest& request,
    const GURL& initiator_url,
    std::optional<std::string> frame_token,
    base::TimeTicks timestamp,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info) {
  RequestWillBeSent(request_id, request_id, request, initiator_url,
                    Network::Initiator::TypeEnum::Script,
                    Network::ResourceTypeEnum::Prefetch, std::move(frame_token),
                    timestamp, std::move(redirect_info));
}

void NetworkHandler::NavigationRequestWillBeSent(
    const NavigationRequest& nav_request,
    base::TimeTicks timestamp) {
  if (!enabled_) {
    return;
  }

  const blink::mojom::CommonNavigationParams& common_params =
      nav_request.common_params();
  net::HttpRequestHeaders headers;
  headers.AddHeadersFromString(nav_request.begin_params().headers);

  std::unique_ptr<Network::Response> redirect_response;
  const blink::mojom::CommitNavigationParams& commit_params =
      nav_request.commit_params();
  bool redirect_emitted_extra_info = false;
  if (!commit_params.redirect_params.empty()) {
    const network::mojom::URLResponseHead& head =
        *commit_params.redirect_params.back()->response_head;
    network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
        network::ExtractDevToolsInfo(head);
    redirect_response =
        BuildResponse(commit_params.redirects.back(), *head_info);
    redirect_emitted_extra_info = head_info->emitted_extra_info;
  }
  std::string url_fragment;
  std::string url_without_fragment =
      ExtractFragment(common_params.url, &url_fragment);
  auto request =
      Network::Request::Create()
          .SetUrl(url_without_fragment)
          .SetMethod(common_params.method)
          .SetHeaders(BuildRequestHeaders(headers, common_params.referrer->url))
          .SetInitialPriority(resourcePriority(net::HIGHEST))
          .SetReferrerPolicy(referrerPolicy(common_params.referrer->policy))
          .Build();

  if (!url_fragment.empty()) {
    request->SetUrlFragment(url_fragment);
  }

  if (common_params.post_data) {
    request->SetHasPostData(true);
    std::string post_data;
    auto data_entries =
        std::make_unique<protocol::Array<protocol::Network::PostDataEntry>>();
    if (GetPostData(*common_params.post_data, data_entries.get(), &post_data)) {
      if (!post_data.empty()) {
        request->SetPostData(post_data);
      }
      if (data_entries->size()) {
        request->SetPostDataEntries(std::move(data_entries));
      }
    }
  }
  // TODO(caseq): report potentially blockable types
  request->SetMixedContentType(Security::MixedContentTypeEnum::None);

  std::unique_ptr<Network::Initiator> initiator;
  const std::optional<base::DictValue>& initiator_optional =
      nav_request.begin_params().devtools_initiator;
  if (initiator_optional.has_value()) {
    crdtp::ConvertProtocolValue(initiator_optional.value(), &initiator);
  }
  if (!initiator) {
    initiator = Network::Initiator::Create()
                    .SetType(Network::Initiator::TypeEnum::Other)
                    .Build();
  }
  std::string id = nav_request.devtools_navigation_token().ToString();
  double current_ticks = timestamp.since_origin().InSecondsF();
  double current_wall_time = base::Time::Now().InSecondsFSinceUnixEpoch();
  std::string frame_token = nav_request.frame_tree_node()
                                ->current_frame_host()
                                ->devtools_frame_token()
                                .ToString();

  const blink::mojom::BeginNavigationParams& begin_params =
      nav_request.begin_params();
  if (begin_params.trust_token_params) {
    request->SetTrustTokenParams(
        BuildTrustTokenParams(*begin_params.trust_token_params));
  }

  if (host_) {
    if (nav_request.frame_tree_node()->IsOutermostMainFrame()) {
      request->SetIsSameSite(true);
    } else {
      request->SetIsSameSite(
          host_->ComputeSiteForCookies().IsFirstParty(common_params.url));
    }
  }

  if (nav_request.is_ad_tagged()) {
    request->SetIsAdRelated(true);
  }

  frontend_->RequestWillBeSent(
      id, id, url_without_fragment, std::move(request), current_ticks,
      current_wall_time, std::move(initiator), redirect_emitted_extra_info,
      std::move(redirect_response),
      std::string(Network::ResourceTypeEnum::Document), std::move(frame_token),
      common_params.has_possibly_filtered_user_gesture);
}

void NetworkHandler::FencedFrameReportRequestSent(
    const std::string& request_id,
    const network::ResourceRequest& request,
    const std::string& event_data,
    base::TimeTicks timestamp) {
  std::vector<base::expected<std::vector<uint8_t>, std::string>>
      request_body_bytes;
  if (!event_data.empty()) {
    request_body_bytes.emplace_back(
        std::vector<uint8_t>(event_data.begin(), event_data.end()));
  }
  RequestWillBeSent(request_id, request_id, request, /*initiator_url=*/GURL(),
                    Network::Initiator::TypeEnum::Other,
                    Network::ResourceTypeEnum::Other,
                    /*frame_token=*/std::nullopt, timestamp,
                    /*redirect_info=*/std::nullopt,
                    /*initiator_devtools_request_id=*/request_id,
                    std::move(request_body_bytes));
}

void NetworkHandler::RequestSent(
    const std::string& request_id,
    const std::string& loader_id,
    const net::HttpRequestHeaders& request_headers,
    const network::mojom::URLRequestDevToolsInfo& request_info,
    const char* initiator_type,
    const std::optional<GURL>& initiator_url,
    const std::string& initiator_devtools_request_id,
    std::optional<base::UnguessableToken> frame_token,
    base::TimeTicks timestamp) {
  if (!enabled_) {
    return;
  }
  std::unique_ptr<Network::Initiator> initiator =
      Network::Initiator::Create().SetType(initiator_type).Build();
  if (initiator_url) {
    initiator->SetUrl(initiator_url->spec());
  }
  if (initiator_devtools_request_id.size()) {
    initiator->SetRequestId(initiator_devtools_request_id);
  }
  std::string url_fragment;
  std::string url_without_fragment =
      ExtractFragment(request_info.url, &url_fragment);
  auto request_object =
      Network::Request::Create()
          .SetUrl(url_without_fragment)
          .SetMethod(request_info.method)
          .SetHeaders(BuildRequestHeaders(request_headers, GURL()))
          .SetInitialPriority(resourcePriority(request_info.priority))
          .SetReferrerPolicy(referrerPolicy(request_info.referrer_policy))
          .Build();
  if (!url_fragment.empty()) {
    request_object->SetUrlFragment(url_fragment);
  }
  if (request_info.trust_token_params) {
    request_object->SetTrustTokenParams(
        BuildTrustTokenParams(*request_info.trust_token_params));
  }

  if (request_info.is_ad_related) {
    request_object->SetIsAdRelated(true);
  }

  std::string resource_type = Network::ResourceTypeEnum::Other;
  if (request_info.resource_type ==
          static_cast<int>(blink::mojom::ResourceType::kWorker) ||
      request_info.resource_type ==
          static_cast<int>(blink::mojom::ResourceType::kSharedWorker) ||
      request_info.resource_type ==
          static_cast<int>(blink::mojom::ResourceType::kServiceWorker)) {
    resource_type = Network::ResourceTypeEnum::Script;
  }

  std::optional<std::string> frame_id;
  if (frame_token.has_value()) {
    frame_id = frame_token->ToString();
  }

  // TODO(crbug.com/40798984): Populate redirectHasExtraInfo instead of
  // just returning false.
  frontend_->RequestWillBeSent(
      request_id, loader_id, url_without_fragment, std::move(request_object),
      timestamp.since_origin().InSecondsF(),
      base::Time::Now().InSecondsFSinceUnixEpoch(), std::move(initiator),
      /*redirectHasExtraInfo=*/false, std::unique_ptr<Network::Response>(),
      resource_type, std::move(frame_id), request_info.has_user_gesture);
}

namespace {
String BuildCorsError(network::mojom::CorsError cors_error) {
  switch (cors_error) {
    case network::mojom::CorsError::kDisallowedByMode:
      return protocol::Network::CorsErrorEnum::DisallowedByMode;

    case network::mojom::CorsError::kInvalidResponse:
      return protocol::Network::CorsErrorEnum::InvalidResponse;

    case network::mojom::CorsError::kWildcardOriginNotAllowed:
      return protocol::Network::CorsErrorEnum::WildcardOriginNotAllowed;

    case network::mojom::CorsError::kMissingAllowOriginHeader:
      return protocol::Network::CorsErrorEnum::MissingAllowOriginHeader;

    case network::mojom::CorsError::kMultipleAllowOriginValues:
      return protocol::Network::CorsErrorEnum::MultipleAllowOriginValues;

    case network::mojom::CorsError::kInvalidAllowOriginValue:
      return protocol::Network::CorsErrorEnum::InvalidAllowOriginValue;

    case network::mojom::CorsError::kAllowOriginMismatch:
      return protocol::Network::CorsErrorEnum::AllowOriginMismatch;

    case network::mojom::CorsError::kInvalidAllowCredentials:
      return protocol::Network::CorsErrorEnum::InvalidAllowCredentials;

    case network::mojom::CorsError::kCorsDisabledScheme:
      return protocol::Network::CorsErrorEnum::CorsDisabledScheme;

    case network::mojom::CorsError::kPreflightInvalidStatus:
      return protocol::Network::CorsErrorEnum::PreflightInvalidStatus;

    case network::mojom::CorsError::kPreflightDisallowedRedirect:
      return protocol::Network::CorsErrorEnum::PreflightDisallowedRedirect;

    case network::mojom::CorsError::kPreflightWildcardOriginNotAllowed:
      return protocol::Network::CorsErrorEnum::
          PreflightWildcardOriginNotAllowed;

    case network::mojom::CorsError::kPreflightMissingAllowOriginHeader:
      return protocol::Network::CorsErrorEnum::
          PreflightMissingAllowOriginHeader;

    case network::mojom::CorsError::kPreflightMultipleAllowOriginValues:
      return protocol::Network::CorsErrorEnum::
          PreflightMultipleAllowOriginValues;

    case network::mojom::CorsError::kPreflightInvalidAllowOriginValue:
      return protocol::Network::CorsErrorEnum::PreflightInvalidAllowOriginValue;

    case network::mojom::CorsError::kPreflightAllowOriginMismatch:
      return protocol::Network::CorsErrorEnum::PreflightAllowOriginMismatch;

    case network::mojom::CorsError::kPreflightInvalidAllowCredentials:
      return protocol::Network::CorsErrorEnum::PreflightInvalidAllowCredentials;

    case network::mojom::CorsError::kInvalidAllowMethodsPreflightResponse:
      return protocol::Network::CorsErrorEnum::
          InvalidAllowMethodsPreflightResponse;

    case network::mojom::CorsError::kInvalidAllowHeadersPreflightResponse:
      return protocol::Network::CorsErrorEnum::
          InvalidAllowHeadersPreflightResponse;

    case network::mojom::CorsError::kMethodDisallowedByPreflightResponse:
      return protocol::Network::CorsErrorEnum::
          MethodDisallowedByPreflightResponse;

    case network::mojom::CorsError::kHeaderDisallowedByPreflightResponse:
      return protocol::Network::CorsErrorEnum::
          HeaderDisallowedByPreflightResponse;

    case network::mojom::CorsError::kRedirectContainsCredentials:
      return protocol::Network::CorsErrorEnum::RedirectContainsCredentials;

    case network::mojom::CorsError::kInsecureLocalNetwork:
      return protocol::Network::CorsErrorEnum::InsecureLocalNetwork;

    case network::mojom::CorsError::kInvalidLocalNetworkAccess:
      return protocol::Network::CorsErrorEnum::InvalidLocalNetworkAccess;

    case network::mojom::CorsError::kLocalNetworkAccessPermissionDenied:
      return protocol::Network::CorsErrorEnum::
          LocalNetworkAccessPermissionDenied;
  }
}
}  // namespace

void NetworkHandler::ResponseReceived(
    const std::string& request_id,
    const std::string& loader_id,
    const GURL& url,
    const char* resource_type,
    const network::mojom::URLResponseHeadDevToolsInfo& head,
    std::optional<std::string> frame_id) {
  if (!enabled_) {
    return;
  }
  std::unique_ptr<Network::Response> response(BuildResponse(url, head));
  frontend_->ResponseReceived(
      request_id, loader_id,
      base::TimeTicks::Now().ToInternalValue() /
          static_cast<double>(base::Time::kMicrosecondsPerSecond),
      resource_type, std::move(response), head.emitted_extra_info,
      std::move(frame_id));
}

void NetworkHandler::LoadingComplete(
    const std::string& request_id,
    const char* resource_type,
    const network::URLLoaderCompletionStatus& status) {
  if (!enabled_) {
    return;
  }
  if (status.error_code != net::OK) {
    frontend_->LoadingFailed(
        request_id,
        base::TimeTicks::Now().ToInternalValue() /
            static_cast<double>(base::Time::kMicrosecondsPerSecond),
        resource_type, net::ErrorToString(status.error_code),
        status.error_code == net::Error::ERR_ABORTED,
        GetBlockedReasonFor(status),
        status.cors_error_status
            ? BuildCorsErrorStatus(*status.cors_error_status)
            : nullptr);
    return;
  }
  frontend_->LoadingFinished(
      request_id,
      status.completion_time.ToInternalValue() /
          static_cast<double>(base::Time::kMicrosecondsPerSecond),
      status.encoded_data_length.InBytesF());
}

void NetworkHandler::FetchKeepAliveRequestWillBeSent(
    const std::string& request_id,
    const network::ResourceRequest& request,
    const GURL& initiator_url,
    std::optional<std::string> frame_token,
    base::TimeTicks timestamp,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info) {
  RequestWillBeSent(request_id, request_id, request, initiator_url,
                    Network::Initiator::TypeEnum::Script,
                    Network::ResourceTypeEnum::Fetch, std::move(frame_token),
                    timestamp, std::move(redirect_info),
                    /*initiator_devtools_request_id=*/"", /*request_bodies=*/{},
                    Security::MixedContentTypeEnum::Blockable);
}

void NetworkHandler::PrefetchActivationBeaconWillBeSent(
    const std::string& request_id,
    const network::ResourceRequest& request,
    const GURL& initiator_url,
    std::optional<std::string> frame_token,
    base::TimeTicks timestamp,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info) {
  RequestWillBeSent(request_id, request_id, request, initiator_url,
                    Network::Initiator::TypeEnum::Other,
                    Network::ResourceTypeEnum::Ping, std::move(frame_token),
                    timestamp, std::move(redirect_info));
}

void NetworkHandler::RequestWillBeSent(
    const std::string& request_id,
    const std::string& loader_id,
    const network::ResourceRequest& request,
    const GURL& initiator_url,
    const std::string& initiator_type,
    const std::string& resource_type,
    std::optional<std::string> frame_token,
    base::TimeTicks timestamp,
    std::optional<std::pair<const GURL&,
                            const network::mojom::URLResponseHeadDevToolsInfo&>>
        redirect_info,
    const std::string& initiator_devtools_request_id,
    std::vector<base::expected<std::vector<uint8_t>, std::string>>
        request_bodies,
    std::optional<std::string> mixed_content_type) {
  if (!enabled_) {
    return;
  }

  double current_ticks = timestamp.since_origin().InSecondsF();
  double current_wall_time = base::Time::Now().InSecondsFSinceUnixEpoch();
  auto initiator = Network::Initiator::Create().SetType(initiator_type).Build();
  if (initiator_url.is_valid()) {
    initiator->SetUrl(initiator_url.spec());
  }
  if (!initiator_devtools_request_id.empty()) {
    initiator->SetRequestId(initiator_devtools_request_id);
  }

  bool redirect_emitted_extra_info = false;
  std::unique_ptr<Network::Response> redirect_response =
      BuildRedirectResponse(redirect_info, redirect_emitted_extra_info);

  auto request_info =
      CreateRequestFromResourceRequest(request, "", std::move(request_bodies));

  if (request.request_body && !request_info->GetHasPostData()) {
    request_info->SetHasPostData(true);
    std::string post_data;
    auto data_entries =
        std::make_unique<protocol::Array<protocol::Network::PostDataEntry>>();
    if (GetPostData(*request.request_body, data_entries.get(), &post_data)) {
      if (!post_data.empty()) {
        request_info->SetPostData(post_data);
      }
      if (data_entries->size()) {
        request_info->SetPostDataEntries(std::move(data_entries));
      }
    }
  }

  if (mixed_content_type.has_value()) {
    request_info->SetMixedContentType(*mixed_content_type);
  }

  std::string url = request_info->GetUrl();

  frontend_->RequestWillBeSent(
      request_id, loader_id, url, std::move(request_info), current_ticks,
      current_wall_time, std::move(initiator), redirect_emitted_extra_info,
      std::move(redirect_response), resource_type, std::move(frame_token),
      request.has_user_gesture);
}

void NetworkHandler::OnSignedExchangeReceived(
    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) {
  if (!enabled_) {
    return;
  }
  network::mojom::URLResponseHeadDevToolsInfoPtr head_info =
      network::ExtractDevToolsInfo(outer_response);
  std::unique_ptr<Network::SignedExchangeInfo> signed_exchange_info =
      Network::SignedExchangeInfo::Create()
          .SetOuterResponse(BuildResponse(outer_request_url, *head_info))
          .SetHasExtraInfo(outer_response.emitted_extra_info)
          .Build();

  if (envelope) {
    auto headers_dict = std::make_unique<base::DictValue>();
    for (const auto& it : envelope->response_headers()) {
      headers_dict->Set(it.first, it.second);
    }

    const SignedExchangeSignatureHeaderField::Signature& sig =
        envelope->signature();
    auto signatures =
        std::make_unique<protocol::Array<Network::SignedExchangeSignature>>();
    std::unique_ptr<Network::SignedExchangeSignature> signature =
        Network::SignedExchangeSignature::Create()
            .SetLabel(sig.label)
            .SetSignature(base::HexEncode(sig.sig))
            .SetIntegrity(sig.integrity)
            .SetCertUrl(sig.cert_url.spec())
            .SetValidityUrl(sig.validity_url.url.spec())
            .SetDate(sig.date)
            .SetExpires(sig.expires)
            .Build();
    if (sig.cert_sha256) {
      signature->SetCertSha256(base::HexEncode(*sig.cert_sha256));
    }
    if (certificate) {
      auto encoded_certificates = std::make_unique<protocol::Array<String>>();
      for (const auto& cert : certificate->cert_buffers()) {
        encoded_certificates->emplace_back(base::Base64Encode(
            net::x509_util::CryptoBufferAsStringPiece(cert.get())));
      }
      signature->SetCertificates(std::move(encoded_certificates));
    }
    signatures->emplace_back(std::move(signature));

    signed_exchange_info->SetHeader(
        Network::SignedExchangeHeader::Create()
            .SetRequestUrl(envelope->request_url().url.spec())
            .SetResponseCode(envelope->response_code())
            .SetResponseHeaders(std::move(headers_dict))
            .SetSignatures(std::move(signatures))
            .SetHeaderIntegrity(
                signed_exchange_utils::CreateHeaderIntegrityHashString(
                    envelope->ComputeHeaderIntegrity()))
            .Build());
  }
  if (ssl_info) {
    signed_exchange_info->SetSecurityDetails(BuildSecurityDetails(*ssl_info));
  }
  if (errors.size()) {
    signed_exchange_info->SetErrors(BuildSignedExchangeErrors(errors));
  }

  frontend_->SignedExchangeReceived(
      devtools_navigation_token ? devtools_navigation_token->ToString() : "",
      std::move(signed_exchange_info));
}


void NetworkHandler::BodyDataReceived(const String& request_id,
                                      const String& body,
                                      bool is_base64_encoded) {
  network::mojom::DurableMessageCollector* collector =
      root_session_->MaybeGetDurableMessageCollector();
  if (collector) {
    // When Durable Message is enabled, we don't need to store the body data
    // in the NetworkHandler, to avoid doubling the memory usage.
    return;
  }
  received_body_data_[request_id] = {body, is_base64_encoded};
}

void NetworkHandler::FedCmRequestWillBeSent(
    const std::string& request_id,
    const std::string& loader_id,
    const network::ResourceRequest& request,
    const std::optional<std::string>& request_body,
    const GURL& initiator_url,
    const std::optional<base::UnguessableToken>& frame_token,
    base::TimeTicks timestamp) {
  std::vector<base::expected<std::vector<uint8_t>, std::string>>
      request_body_bytes;
  if (request_body.has_value() && !request_body->empty()) {
    request_body_bytes.emplace_back(
        std::vector<uint8_t>(request_body->begin(), request_body->end()));
  }
  std::optional<std::string> frame_token_str =
      frame_token.has_value() ? std::make_optional(frame_token->ToString())
                              : std::nullopt;
  RequestWillBeSent(
      request_id, loader_id, request, initiator_url,
      Network::Initiator::TypeEnum::FedCM, Network::ResourceTypeEnum::FedCM,
      std::move(frame_token_str), timestamp, /*redirect_info=*/std::nullopt,
      /*initiator_devtools_request_id=*/"", std::move(request_body_bytes));
}

void NetworkHandler::ProcessDurableMessageOrGetLocalData(
    const String& request_id,
    std::unique_ptr<GetResponseBodyCallback> callback,
    std::optional<mojo_base::BigBuffer> durable_message) {
  if (durable_message.has_value()) {
    std::string_view data_view =
        base::as_string_view(base::span(*durable_message));
    if (base::IsStringUTF8(data_view)) {
      callback->sendSuccess(std::string(data_view), false);
    } else {
      callback->sendSuccess(base::Base64Encode(data_view), true);
    }
    return;
  }

  auto it = received_body_data_.find(request_id);
  if (it != received_body_data_.end()) {
    callback->sendSuccess(it->second.first, it->second.second);
  } else {
    callback->fallThrough();
  }
}

void NetworkHandler::GetResponseBody(
    const String& request_id,
    std::unique_ptr<GetResponseBodyCallback> callback) {
  CHECK(storage_partition_);
  CHECK(devtools_token_);
  network::mojom::DurableMessageCollector* collector =
      root_session_->MaybeGetDurableMessageCollector();
  if (collector) {
    collector->Retrieve(
        request_id,
        base::BindOnce(&NetworkHandler::ProcessDurableMessageOrGetLocalData,
                       weak_factory_.GetWeakPtr(), request_id,
                       std::move(callback)));
    return;
  }
  ProcessDurableMessageOrGetLocalData(request_id, std::move(callback),
                                      std::nullopt);
}


// static
std::string NetworkHandler::ExtractFragment(const GURL& url,
                                            std::string* fragment) {
  if (!url.has_ref()) {
    *fragment = std::string();
    return url.spec();
  }
  *fragment = "#" + url.GetRef();
  GURL::Replacements replacements;
  replacements.ClearRef();
  return url.ReplaceComponents(replacements).spec();
}

// static
std::unique_ptr<Network::Request>
NetworkHandler::CreateRequestFromResourceRequest(
    const network::ResourceRequest& request,
    const std::string& cookie_line,
    std::vector<base::expected<std::vector<uint8_t>, std::string>>
        request_bodies) {
  std::unique_ptr<base::DictValue> headers_dict =
      BuildRequestHeaders(request.headers, request.referrer);
  if (!cookie_line.empty()) {
    headers_dict->Set(net::HttpRequestHeaders::kCookie, cookie_line);
  }

  std::string url_fragment;
  std::unique_ptr<protocol::Network::Request> request_object =
      Network::Request::Create()
          .SetUrl(ExtractFragment(request.url, &url_fragment))
          .SetMethod(request.method)
          .SetHeaders(std::move(headers_dict))
          .SetInitialPriority(resourcePriority(request.priority))
          .SetReferrerPolicy(referrerPolicy(request.referrer_policy))
          .Build();
  if (!url_fragment.empty()) {
    request_object->SetUrlFragment(url_fragment);
  }
  if (!request_bodies.empty()) {
    std::string post_data;
    auto data_entries =
        std::make_unique<protocol::Array<protocol::Network::PostDataEntry>>();

    for (auto& body : request_bodies) {
      // TODO(caseq): post_data is deprecated, remove.
      auto entry = protocol::Network::PostDataEntry::Create().Build();
      if (body.has_value()) {
        post_data.append(reinterpret_cast<const char*>(body->data()),
                         body->size());
        entry->SetBytes(protocol::Binary::fromVector(*std::move(body)));
      }
      data_entries->push_back(std::move(entry));
    }
    if (!post_data.empty()) {
      request_object->SetPostData(std::move(post_data));
    }
    request_object->SetPostDataEntries(std::move(data_entries));
    request_object->SetHasPostData(true);
  }
  if (request.is_ad_tagged) {
    request_object->SetIsAdRelated(true);
  }
  return request_object;
}

void NetworkHandler::ApplyOverrides(net::HttpRequestHeaders* headers,
                                    bool* skip_service_worker,
                                    bool* disable_cache,
                                    GURL* referrer_override) {
  for (auto& entry : extra_headers_) {
    if (referrer_override &&
        base::EqualsCaseInsensitiveASCII(entry.first,
                                         net::HttpRequestHeaders::kReferer)) {
      GURL referrer_override_(entry.second);
      if (referrer_override_.is_valid()) {
        // If extra header `Referer` is set and is a valid URL, use it as the
        // referrer.
        *referrer_override = referrer_override_;
      }
    }
    headers->SetHeader(entry.first, entry.second);
  }
  *skip_service_worker |= bypass_service_worker_;
  *disable_cache |= cache_disabled_;
}

void NetworkHandler::ApplyCookieControlsOverrides(
    net::CookieSettingOverrides& overrides) {
  if (enable_third_party_cookie_restriction_) {
    overrides.Put(net::CookieSettingOverride::kForceDisableThirdPartyCookies);
  }
}

void NetworkHandler::SetNetworkConditions(
    std::vector<network::mojom::MatchedNetworkConditionsPtr> matched_conditions,
    bool offline) {
  if (!storage_partition_) {
    return;
  }
  network_conditions_configured_ = !matched_conditions.empty() || offline;
  network::mojom::NetworkContext* context =
      storage_partition_->GetNetworkContext();

  if (!devtools_token_.is_empty()) {
    context->SetNetworkConditions(devtools_token_, throttling_client_id_,
                                  std::move(matched_conditions));
  }

  if (offline == !!background_sync_restorer_) {
    return;
  }
  background_sync_restorer_.reset(
      offline ? new BackgroundSyncRestorer(host_id_, storage_partition_)
              : nullptr);
}

namespace {
protocol::Network::CrossOriginOpenerPolicyValue
makeCrossOriginOpenerPolicyValue(
    network::mojom::CrossOriginOpenerPolicyValue value) {
  switch (value) {
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOrigin:
      return protocol::Network::CrossOriginOpenerPolicyValueEnum::SameOrigin;
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOriginAllowPopups:
      return protocol::Network::CrossOriginOpenerPolicyValueEnum::
          SameOriginAllowPopups;
    case network::mojom::CrossOriginOpenerPolicyValue::kUnsafeNone:
      return protocol::Network::CrossOriginOpenerPolicyValueEnum::UnsafeNone;
    case network::mojom::CrossOriginOpenerPolicyValue::kSameOriginPlusCoep:
      return protocol::Network::CrossOriginOpenerPolicyValueEnum::
          SameOriginPlusCoep;
    case network::mojom::CrossOriginOpenerPolicyValue::kNoopenerAllowPopups:
      return protocol::Network::CrossOriginOpenerPolicyValueEnum::
          NoopenerAllowPopups;
  }
}
protocol::Network::CrossOriginEmbedderPolicyValue
makeCrossOriginEmbedderPolicyValue(
    network::mojom::CrossOriginEmbedderPolicyValue value) {
  switch (value) {
    case network::mojom::CrossOriginEmbedderPolicyValue::kNone:
      return protocol::Network::CrossOriginEmbedderPolicyValueEnum::None;
    case network::mojom::CrossOriginEmbedderPolicyValue::kCredentialless:
      return protocol::Network::CrossOriginEmbedderPolicyValueEnum::
          Credentialless;
    case network::mojom::CrossOriginEmbedderPolicyValue::kRequireCorp:
      return protocol::Network::CrossOriginEmbedderPolicyValueEnum::RequireCorp;
  }
}
protocol::Network::ContentSecurityPolicySource makeContentSecurityPolicySource(
    network::mojom::ContentSecurityPolicySource source) {
  switch (source) {
    case network::mojom::ContentSecurityPolicySource::kHTTP:
      return protocol::Network::ContentSecurityPolicySourceEnum::HTTP;
    case network::mojom::ContentSecurityPolicySource::kMeta:
      return protocol::Network::ContentSecurityPolicySourceEnum::Meta;
  }
}
std::unique_ptr<protocol::Network::CrossOriginOpenerPolicyStatus>
makeCrossOriginOpenerPolicyStatus(
    const network::CrossOriginOpenerPolicy& coop) {
  auto protocol_coop =
      protocol::Network::CrossOriginOpenerPolicyStatus::Create()
          .SetValue(makeCrossOriginOpenerPolicyValue(coop.value))
          .SetReportOnlyValue(
              makeCrossOriginOpenerPolicyValue(coop.report_only_value))
          .Build();

  if (coop.reporting_endpoint) {
    protocol_coop->SetReportingEndpoint(*coop.reporting_endpoint);
  }
  if (coop.report_only_reporting_endpoint) {
    protocol_coop->SetReportOnlyReportingEndpoint(
        *coop.report_only_reporting_endpoint);
  }
  return protocol_coop;
}
std::unique_ptr<protocol::Network::CrossOriginEmbedderPolicyStatus>
makeCrossOriginEmbedderPolicyStatus(
    const network::CrossOriginEmbedderPolicy& coep) {
  auto protocol_coep =
      protocol::Network::CrossOriginEmbedderPolicyStatus::Create()
          .SetValue(makeCrossOriginEmbedderPolicyValue(coep.value))
          .SetReportOnlyValue(
              makeCrossOriginEmbedderPolicyValue(coep.report_only_value))
          .Build();

  if (coep.reporting_endpoint) {
    protocol_coep->SetReportingEndpoint(*coep.reporting_endpoint);
  }
  if (coep.report_only_reporting_endpoint) {
    protocol_coep->SetReportOnlyReportingEndpoint(
        *coep.report_only_reporting_endpoint);
  }
  return protocol_coep;
}
std::unique_ptr<protocol::Array<protocol::Network::ContentSecurityPolicyStatus>>
makeContentSecurityPolicyStatus(
    const std::vector<network::mojom::ContentSecurityPolicyHeader>&
        csp_headers) {
  auto csp_status = std::make_unique<
      protocol::Array<protocol::Network::ContentSecurityPolicyStatus>>();
  for (const auto& csp_header : csp_headers) {
    auto csp_status_component =
        protocol::Network::ContentSecurityPolicyStatus::Create()
            .SetEffectiveDirectives(csp_header.header_value)
            .SetIsEnforced(csp_header.type ==
                           network::mojom::ContentSecurityPolicyType::kEnforce)
            .SetSource(makeContentSecurityPolicySource(csp_header.source))
            .Build();
    csp_status->emplace_back(std::move(csp_status_component));
  }
  return csp_status;
}
}  // namespace

DispatchResponse NetworkHandler::GetSecurityIsolationStatus(
    std::optional<String> frame_id,
    std::unique_ptr<protocol::Network::SecurityIsolationStatus>* out_info) {
  scoped_refptr<DevToolsAgentHostImpl> host =
      DevToolsAgentHostImpl::GetForId(host_id_);
  std::string id = frame_id.value_or("");
  auto maybe_coep = host->cross_origin_embedder_policy(id);
  auto maybe_coop = host->cross_origin_opener_policy(id);
  auto maybe_csp = host->content_security_policy(id);
  auto status = protocol::Network::SecurityIsolationStatus::Create().Build();
  if (maybe_coep) {
    status->SetCoep(makeCrossOriginEmbedderPolicyStatus(*maybe_coep));
  }
  if (maybe_coop) {
    status->SetCoop(makeCrossOriginOpenerPolicyStatus(*maybe_coop));
  }
  if (maybe_csp) {
    status->SetCsp(makeContentSecurityPolicyStatus(*maybe_csp));
  }
  *out_info = std::move(status);
  return Response::Success();
}

void NetworkHandler::OnRequestWillBeSentExtraInfo(
    const std::string& devtools_request_id,
    const net::CookieAccessResultList& request_cookie_list,
    const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers,
    const base::TimeTicks timestamp,
    const std::vector<network::mojom::DeviceBoundSessionWithUsagePtr>&
        device_bound_session_usages,
    const network::mojom::ClientSecurityStatePtr& security_state,
    const network::mojom::OtherPartitionInfoPtr& other_partition_info,
    std::optional<base::UnguessableToken> applied_network_conditions_id) {
  if (!enabled_) {
    return;
  }

  frontend_->RequestWillBeSentExtraInfo(
      devtools_request_id, BuildProtocolAssociatedCookies(request_cookie_list),
      GetRawHeaders(request_headers), GetConnectTiming(timestamp),
      BuildProtocolDeviceBoundSessionUsages(device_bound_session_usages),
      MaybeBuildClientSecurityState(security_state),
      other_partition_info
          ? std::optional<bool>(
                other_partition_info->site_has_cookie_in_other_partition)
          : std::nullopt,
      applied_network_conditions_id.has_value()
          ? std::optional<String>(applied_network_conditions_id->ToString())
          : std::nullopt);
}

void NetworkHandler::OnResponseReceivedExtraInfo(
    const std::string& devtools_request_id,
    const net::CookieAndLineAccessResultList& response_cookie_list,
    const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
    const std::optional<std::string>& response_headers_text,
    network::mojom::IPAddressSpace resource_address_space,
    int32_t http_status_code,
    const std::optional<net::CookiePartitionKey>& cookie_partition_key) {
  if (!enabled_) {
    return;
  }

  std::unique_ptr<Network::CookiePartitionKey> frontend_partition_key;

  if (cookie_partition_key) {
    base::expected<net::CookiePartitionKey::SerializedCookiePartitionKey,
                   std::string>
        serialized_result =
            net::CookiePartitionKey::Serialize(cookie_partition_key);
    if (serialized_result.has_value()) {
      frontend_partition_key =
          BuildCookiePartitionKey(serialized_result->TopLevelSite(),
                                  serialized_result->has_cross_site_ancestor());
    }
  }

  frontend_->ResponseReceivedExtraInfo(
      devtools_request_id, BuildProtocolBlockedSetCookies(response_cookie_list),
      GetRawHeaders(response_headers),
      BuildIpAddressSpace(resource_address_space), http_status_code,
      response_headers_text, std::move(frontend_partition_key),
      cookie_partition_key
          ? std::optional<bool>(!cookie_partition_key->IsSerializeable())
          : std::nullopt,
      BuildProtocolExemptedSetCookies(response_cookie_list));
}

void NetworkHandler::OnResponseReceivedEarlyHints(
    const std::string& devtools_request_id,
    const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers) {
  if (!enabled_) {
    return;
  }

  frontend_->ResponseReceivedEarlyHints(devtools_request_id,
                                        GetRawHeaders(response_headers));
}

void NetworkHandler::OnLoadNetworkResourceFinished(
    DevToolsNetworkResourceLoader* loader,
    const net::HttpResponseHeaders* rh,
    bool success,
    int net_error,
    std::string content) {
  auto it = loaders_.find(loader);
  CHECK(it != loaders_.end());
  auto callback = std::move(it->second);
  auto result = Network::LoadNetworkResourcePageResult::Create()
                    .SetSuccess(success)
                    .Build();

  if (net_error != net::OK) {
    result->SetNetError(net_error);
    result->SetNetErrorName(net::ErrorToString(net_error));
  }

  if (success) {
    bool is_binary = !base::IsStringUTF8(content);
    // TODO(sigurds): Use the data-pipe from the network loader.
    scoped_refptr<DevToolsStreamFile> stream =
        DevToolsStreamFile::Create(io_context_, is_binary);
    stream->Append(std::make_unique<std::string>(std::move(content)));
    result->SetStream(stream->handle());
  }

  if (rh) {
    result->SetHttpStatusCode(rh->response_code());
    result->SetHeaders(BuildResponseHeaders(rh));
  }

  callback->sendSuccess(std::move(result));
  loaders_.erase(it);
}

namespace {

mojo::PendingRemote<network::mojom::URLLoaderFactory>
CreateNetworkFactoryForDevTools(
    std::string_view scheme,
    RenderProcessHost* host,
    int routing_id,
    const url::Origin& origin,
    network::mojom::URLLoaderFactoryParamsPtr params) {
  if (!host || !params) {
    // Return an invalid remote by default.
    return {};
  }

  // Don't allow trust token redemption.
  params->trust_token_redemption_policy =
      network::mojom::TrustTokenOperationPolicyVerdict::kForbid;
  // Don't allow trust token issuance.
  params->trust_token_issuance_policy =
      network::mojom::TrustTokenOperationPolicyVerdict::kForbid;
  // Let DevTools fetch resources without CORS and ORB. Source maps are valid
  // JSON and would otherwise require a CORS fetch + correct response headers.
  // See BUG(chromium:1076435) for more context.
  params->is_orb_enabled = false;

  if (scheme == url::kHttpScheme || scheme == url::kHttpsScheme) {
    return url_loader_factory::CreatePendingRemote(
        ContentBrowserClient::URLLoaderFactoryType::kDevTools,
        url_loader_factory::TerminalParams::ForNetworkContext(
            host->GetStoragePartition()->GetNetworkContext(),
            std::move(params)));
  }

  if (scheme != url::kFileScheme) {
    ContentBrowserClient::NonNetworkURLLoaderFactoryMap factories;
    GetContentClient()
        ->browser()
        ->RegisterNonNetworkSubresourceURLLoaderFactories(
            host->GetDeprecatedID(), routing_id, origin, &factories);
    auto i = factories.find(std::string(scheme));
    if (i == factories.end()) {
      return {};
    }
    return std::move(i->second);
  }
  return {};
}
}  // namespace

void NetworkHandler::LoadNetworkResource(
    std::optional<protocol::String> maybe_frame_id,
    const String& url,
    std::unique_ptr<protocol::Network::LoadNetworkResourceOptions> options,
    std::unique_ptr<LoadNetworkResourceCallback> callback) {
  GURL gurl(url);
  const bool is_gurl_valid = gurl.is_valid();
  if (!is_gurl_valid) {
    callback->sendFailure(Response::InvalidParams("The url must be valid"));
    return;
  }

  if (gurl.SchemeIs(url::kFileScheme) && !client_->MayReadLocalFiles()) {
    callback->sendFailure(Response::InvalidParams("Unsupported URL scheme"));
    return;
  }

  const DevToolsNetworkResourceLoader::Caching caching =
      options->GetDisableCache()
          ? DevToolsNetworkResourceLoader::Caching::kBypass
          : DevToolsNetworkResourceLoader::Caching::kDefault;
  const DevToolsNetworkResourceLoader::Credentials include_credentials =
      options->GetIncludeCredentials()
          ? DevToolsNetworkResourceLoader::Credentials::kInclude
          : DevToolsNetworkResourceLoader::Credentials::kSameSite;
  DevToolsNetworkResourceLoader::CompletionCallback complete_callback =
      base::BindOnce(&NetworkHandler::OnLoadNetworkResourceFinished,
                     base::Unretained(this));

  mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory;
  if (host_) {
    if (!maybe_frame_id.has_value()) {
      callback->sendFailure(Response::InvalidParams(
          "Parameter frameId must be provided for frame targets"));
      return;
    }
    FrameTreeNode* node = FrameTreeNodeFromDevToolsFrameToken(
        host_->frame_tree_node(), maybe_frame_id.value());
    RenderFrameHostImpl* frame = node ? node->current_frame_host() : nullptr;
    if (!frame) {
      callback->sendFailure(Response::InvalidParams("Frame not found"));
      return;
    }
    // Don't allow fetching resources for frames goverened by different
    // DevToolsAgentHosts.
    if (GetFrameTreeNodeAncestor(node) !=
        GetFrameTreeNodeAncestor(host_->frame_tree_node())) {
      callback->sendFailure(
          Response::InvalidParams("Frame not under control of agent host"));
      return;
    }

    RenderFrameHostCSPContext csp_context(frame);

    network::CSPCheckResult result = csp_context.IsAllowedByCsp(
        frame->policy_container_host()->policies().content_security_policies,
        network::mojom::CSPDirectiveName::ConnectSrc, gurl, gurl,
        /*has_followed_redirect=*/false, /*source_location=*/nullptr,
        network::CSPContext::CHECK_ENFORCED_CSP,
        /*is_opaque_fenced_frame=*/false);
    if (!result.IsAllowed()) {
      callback->sendFailure(Response::ServerError("CSP violation"));
      return;
    }

    auto params = URLLoaderFactoryParamsHelper::CreateForFrame(
        frame, frame->GetLastCommittedOrigin(),
        frame->GetIsolationInfoForSubresources(),
        frame->BuildClientSecurityState(),
        /*coep_reporter=*/mojo::NullRemote(),
        /*dip_reporter=*/mojo::NullRemote(), frame->GetProcess(),
        network::mojom::TrustTokenOperationPolicyVerdict::kForbid,
        network::mojom::TrustTokenOperationPolicyVerdict::kForbid,
        frame->GetCookieSettingOverrides(),
        /*network_restrictions_id=*/frame->GetNetworkRestrictionsID(),
        "NetworkHandler::LoadNetworkResource");

    auto factory = CreateNetworkFactoryForDevTools(
        gurl.GetScheme(), frame->GetProcess(), frame->GetRoutingID(),
        frame->GetLastCommittedOrigin(), std::move(params));
    if (!factory.is_valid()) {
      callback->sendFailure(Response::InvalidParams("Unsupported URL scheme"));
      return;
    }

    url_loader_factory.Bind(std::move(factory));
    auto loader = DevToolsNetworkResourceLoader::Create(
        std::move(url_loader_factory), std::move(gurl),
        frame->GetLastCommittedOrigin(), frame->ComputeSiteForCookies(),
        caching, include_credentials, std::move(complete_callback),
        frame->IsOutermostMainFrame());
    loaders_.emplace(std::move(loader), std::move(callback));
    return;
  }
  scoped_refptr<DevToolsAgentHostImpl> host =
      DevToolsAgentHostImpl::GetForId(host_id_);
  if (host) {
    // TODO(sigurds): Support dedicated workers.
    // TODO(mkwst): Check CSP for non-frame targets.
    auto info = host->CreateNetworkFactoryParamsForDevTools();
    auto factory = CreateNetworkFactoryForDevTools(
        gurl.GetScheme(), host->GetProcessHost(), IPC::mojom::kRoutingIdNone,
        info.origin, std::move(info.factory_params));
    if (factory.is_valid()) {
      url_loader_factory.Bind(std::move(factory));
      auto loader = DevToolsNetworkResourceLoader::Create(
          std::move(url_loader_factory), std::move(gurl),
          std::move(info.origin), std::move(info.site_for_cookies), caching,
          include_credentials, std::move(complete_callback));
      loaders_.emplace(std::move(loader), std::move(callback));
      return;
    }
  }
  callback->sendFailure(Response::ServerError("Target not supported"));
}

DispatchResponse NetworkHandler::SetCookieControls(
    bool enable_third_party_cookie_restriction) {
  enable_third_party_cookie_restriction_ =
      enable_third_party_cookie_restriction;

  return Response::Success();
}

namespace {

String GetTrustTokenOperationStatus(
    network::mojom::TrustTokenOperationStatus status) {
  switch (status) {
    case network::mojom::TrustTokenOperationStatus::kOk:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::Ok;
    case network::mojom::TrustTokenOperationStatus::kInvalidArgument:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          InvalidArgument;
    case network::mojom::TrustTokenOperationStatus::kMissingIssuerKeys:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          MissingIssuerKeys;
    case network::mojom::TrustTokenOperationStatus::kFailedPrecondition:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          FailedPrecondition;
    case network::mojom::TrustTokenOperationStatus::kResourceExhausted:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          ResourceExhausted;
    case network::mojom::TrustTokenOperationStatus::kAlreadyExists:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          AlreadyExists;
    case network::mojom::TrustTokenOperationStatus::kResourceLimited:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          ResourceLimited;
    case network::mojom::TrustTokenOperationStatus::kUnauthorized:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          Unauthorized;
    case network::mojom::TrustTokenOperationStatus::kBadResponse:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          BadResponse;
    case network::mojom::TrustTokenOperationStatus::kInternalError:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          InternalError;
    case network::mojom::TrustTokenOperationStatus::kUnknownError:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          UnknownError;
    case network::mojom::TrustTokenOperationStatus::
        kOperationSuccessfullyFulfilledLocally:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          FulfilledLocally;
    case network::mojom::TrustTokenOperationStatus::kSiteIssuerLimit:
      return protocol::Network::TrustTokenOperationDone::StatusEnum::
          SiteIssuerLimit;
  }
}

}  // namespace

void NetworkHandler::OnTrustTokenOperationDone(
    const std::string& devtools_request_id,
    const network::mojom::TrustTokenOperationResult& result) {
  if (!enabled_) {
    return;
  }

  std::optional<String> top_level_origin;
  if (result.top_level_origin) {
    top_level_origin = result.top_level_origin->Serialize();
  }
  std::optional<String> issuer;
  if (result.issuer) {
    issuer = result.issuer->Serialize();
  }

  frontend()->TrustTokenOperationDone(
      GetTrustTokenOperationStatus(result.status),
      GetTrustTokenOperationType(result.operation), devtools_request_id,
      std::move(top_level_origin), std::move(issuer),
      result.issued_token_count);
}

void NetworkHandler::OnPolicyContainerHostUpdated() {
  if (!enabled_) {
    return;
  }
  frontend()->PolicyUpdated();
}

String NetworkHandler::BuildLocalNetworkAccessRequestPolicy(
    network::mojom::LocalNetworkAccessRequestPolicy policy) {
  switch (policy) {
    case network::mojom::LocalNetworkAccessRequestPolicy::kAllow:
      return protocol::Network::LocalNetworkAccessRequestPolicyEnum::Allow;
    case network::mojom::LocalNetworkAccessRequestPolicy::kBlock:
      // TODO(crbug.com/40154414): Fix this.
      return protocol::Network::LocalNetworkAccessRequestPolicyEnum::
          BlockFromInsecureToMorePrivate;
    case network::mojom::LocalNetworkAccessRequestPolicy::kWarn:
      // TODO(crbug.com/40154414): Fix this.
      return protocol::Network::LocalNetworkAccessRequestPolicyEnum::
          WarnFromInsecureToMorePrivate;
    case network::mojom::LocalNetworkAccessRequestPolicy::kPermissionBlock:
      return protocol::Network::LocalNetworkAccessRequestPolicyEnum::
          PermissionBlock;
    case network::mojom::LocalNetworkAccessRequestPolicy::kPermissionWarn:
      return protocol::Network::LocalNetworkAccessRequestPolicyEnum::
          PermissionWarn;
  }
}

String NetworkHandler::BuildIpAddressSpace(
    network::mojom::IPAddressSpace space) {
  switch (space) {
    case network::mojom::IPAddressSpace::kLoopback:
      return protocol::Network::IPAddressSpaceEnum::Loopback;
    case network::mojom::IPAddressSpace::kLocal:
      return protocol::Network::IPAddressSpaceEnum::Local;
    case network::mojom::IPAddressSpace::kPublic:
      return protocol::Network::IPAddressSpaceEnum::Public;
    case network::mojom::IPAddressSpace::kUnknown:
      return protocol::Network::IPAddressSpaceEnum::Unknown;
  }
}

std::unique_ptr<protocol::Network::ClientSecurityState>
NetworkHandler::MaybeBuildClientSecurityState(
    const network::mojom::ClientSecurityStatePtr& state) {
  return state ? protocol::Network::ClientSecurityState::Create()
                     .SetLocalNetworkAccessRequestPolicy(
                         BuildLocalNetworkAccessRequestPolicy(
                             state->local_network_access_request_policy))
                     .SetInitiatorIPAddressSpace(
                         BuildIpAddressSpace(state->ip_address_space))
                     .SetInitiatorIsSecureContext(state->is_web_secure_context)
                     .Build()
               : nullptr;
}

std::unique_ptr<protocol::Network::CorsErrorStatus>
NetworkHandler::BuildCorsErrorStatus(const network::CorsErrorStatus& status) {
  return protocol::Network::CorsErrorStatus::Create()
      .SetCorsError(BuildCorsError(status.cors_error))
      .SetFailedParameter(status.failed_parameter)
      .Build();
}

void NetworkHandler::MaybeEnableDurableMessages(base::OnceClosure callback) {
  if (!enable_durable_messages_ || devtools_token_.is_empty()) {
    std::move(callback).Run();
    return;
  }
  network::mojom::NetworkDurableMessageConfigPtr durable_messages_config;
  durable_messages_config = network::mojom::NetworkDurableMessageConfig::New();
  durable_messages_config->http_storage_max_size =
      durable_message_max_total_size_;
  root_session_->EnableDurableMessageCollector(
      devtools_token_, std::move(durable_messages_config), std::move(callback));
}

void NetworkHandler::DisableDurableMessages(base::OnceClosure callback) {
  enable_durable_messages_ = false;
  root_session_->DisableDurableMessageCollectorForProfile(devtools_token_,
                                                          std::move(callback));
}

void NetworkHandler::ConfigureDurableMessages(
    std::optional<int> max_total_size,
    std::optional<int> max_resource_size,
    std::unique_ptr<ConfigureDurableMessagesCallback> callback) {
  if (!max_total_size.has_value() || max_total_size.value() == 0) {
    DisableDurableMessages(base::BindOnce(
        &ConfigureDurableMessagesCallback::fallThrough, std::move(callback)));
    return;
  }
  durable_message_max_total_size_ = max_total_size.value();
  enable_durable_messages_ = true;
  MaybeEnableDurableMessages(base::BindOnce(
      &ConfigureDurableMessagesCallback::fallThrough, std::move(callback)));
}

}  // namespace protocol
}  // namespace content
