// 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/page_handler.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/process_handle.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/back_forward_cache/disabled_reason_id.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/protocol/browser_handler.h"
#include "content/browser/devtools/protocol/devtools_mhtml_helper.h"
#include "content/browser/devtools/protocol/emulation_handler.h"
#include "content/browser/devtools/protocol/handler_helpers.h"
#include "content/browser/devtools/protocol/page.h"
#include "content/browser/manifest/manifest_manager_host.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/renderer_host/back_forward_cache_can_store_document_result.h"
#include "content/browser/renderer_host/back_forward_cache_disable.h"
#include "content/browser/renderer_host/back_forward_cache_metrics.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/web_contents/web_contents_view.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/referrer.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_utils.h"
#include "net/base/filename_util.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/mojom/back_forward_cache_not_restored_reasons.mojom.h"
#include "third_party/blink/public/mojom/content_extraction/ai_page_content.mojom.h"
#include "third_party/blink/public/mojom/frame/back_forward_cache_controller.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/blink/public/mojom/script_source_location.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/page_transition_types.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/codec/webp_codec.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/skbitmap_operations.h"
#include "ui/snapshot/snapshot.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_ANDROID)
#include "content/browser/renderer_host/compositor_impl_android.h"
#endif
#include "arkweb/build/features/features.h"

namespace content {
namespace protocol {

namespace {

constexpr const char* kMhtml = "mhtml";
constexpr int kDefaultScreenshotQuality = 80;
constexpr int kMaxScreencastFramesInFlight = 2;
constexpr char kCommandIsOnlyAvailableAtTopTarget[] =
    "Command can only be executed on top-level targets";
constexpr char kErrorNotAttached[] = "Not attached to a page";
constexpr char kErrorInactivePage[] = "Not attached to an active page";

using BitmapEncoder =
    base::RepeatingCallback<std::optional<std::vector<uint8_t>>(
        const SkBitmap& bitmap)>;

std::optional<std::vector<uint8_t>> EncodeBitmapAsPngSlow(
    const SkBitmap& bitmap) {
  TRACE_EVENT0("devtools", "EncodeBitmapAsPngSlow");
  return gfx::PNGCodec::EncodeBGRASkBitmap(bitmap,
                                           /*discard_transparency=*/false);
}

std::optional<std::vector<uint8_t>> EncodeBitmapAsPngFast(
    const SkBitmap& bitmap) {
  TRACE_EVENT0("devtools", "EncodeBitmapAsPngFast");
  return gfx::PNGCodec::FastEncodeBGRASkBitmap(bitmap,
                                               /*discard_transparency=*/false);
}

std::optional<std::vector<uint8_t>> EncodeBitmapAsJpeg(int quality,
                                                       const SkBitmap& bitmap) {
  TRACE_EVENT0("devtools", "EncodeBitmapAsJpeg");
  return gfx::JPEGCodec::Encode(bitmap, quality);
}

std::optional<std::vector<uint8_t>> EncodeBitmapAsWebp(int quality,
                                                       const SkBitmap& bitmap) {
  TRACE_EVENT0("devtools", "EncodeBitmapAsWebp");
  return gfx::WebpCodec::Encode(bitmap, quality);
}

std::variant<protocol::Response, BitmapEncoder>
GetEncoder(const std::string& format, int quality, bool optimize_for_speed) {
  if (quality < 0 || quality > 100) {
    quality = kDefaultScreenshotQuality;
  }

  if (format == protocol::Page::CaptureScreenshot::FormatEnum::Png) {
    return base::BindRepeating(optimize_for_speed ? EncodeBitmapAsPngFast
                                                  : EncodeBitmapAsPngSlow);
  }
  if (format == protocol::Page::CaptureScreenshot::FormatEnum::Jpeg) {
    return base::BindRepeating(&EncodeBitmapAsJpeg, quality);
  }
  if (format == protocol::Page::CaptureScreenshot::FormatEnum::Webp) {
    return base::BindRepeating(&EncodeBitmapAsWebp, quality);
  }
  return protocol::Response::InvalidParams("Invalid image format");
}

std::unique_ptr<Page::ScreencastFrameMetadata> BuildScreencastFrameMetadata(
    const gfx::Size& surface_size,
    float device_scale_factor,
    float page_scale_factor,
    const gfx::PointF& root_scroll_offset,
    float top_controls_visible_height) {
  if (surface_size.IsEmpty() || device_scale_factor == 0) {
    return nullptr;
  }

  const gfx::SizeF content_size_dip =
      gfx::ScaleSize(gfx::SizeF(surface_size), 1 / device_scale_factor);
  float top_offset_dip = top_controls_visible_height;
  gfx::PointF root_scroll_offset_dip = root_scroll_offset;
  top_offset_dip /= device_scale_factor;
  root_scroll_offset_dip.Scale(1 / device_scale_factor);
  std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata =
      Page::ScreencastFrameMetadata::Create()
          .SetPageScaleFactor(page_scale_factor)
          .SetOffsetTop(top_offset_dip)
          .SetDeviceWidth(content_size_dip.width())
          .SetDeviceHeight(content_size_dip.height())
          .SetScrollOffsetX(root_scroll_offset_dip.x())
          .SetScrollOffsetY(root_scroll_offset_dip.y())
          .SetTimestamp(base::Time::Now().InSecondsFSinceUnixEpoch())
          .Build();
  return page_metadata;
}

// Determines the snapshot size that best-fits the Surface's content to the
// remote's requested image size.
gfx::Size DetermineSnapshotSize(const gfx::Size& surface_size,
                                int screencast_max_width,
                                int screencast_max_height) {
  if (surface_size.IsEmpty()) {
    return gfx::Size();  // Nothing to copy (and avoid divide-by-zero below).
  }

  double scale = 1;
  if (screencast_max_width > 0) {
    scale = std::min(scale, static_cast<double>(screencast_max_width) /
                                surface_size.width());
  }
  if (screencast_max_height > 0) {
    scale = std::min(scale, static_cast<double>(screencast_max_height) /
                                surface_size.height());
  }
  return gfx::ToRoundedSize(gfx::ScaleSize(gfx::SizeF(surface_size), scale));
}

void GetMetadataFromFrame(const media::VideoFrame& frame,
                          double* device_scale_factor,
                          double* page_scale_factor,
                          gfx::PointF* root_scroll_offset,
                          double* top_controls_visible_height) {
  // Get metadata from |frame|. This will CHECK if metadata is missing.
  *device_scale_factor = *frame.metadata().device_scale_factor;
  *page_scale_factor = *frame.metadata().page_scale_factor;
  root_scroll_offset->set_x(*frame.metadata().root_scroll_offset_x);
  root_scroll_offset->set_y(*frame.metadata().root_scroll_offset_y);
  *top_controls_visible_height = *frame.metadata().top_controls_visible_height;
}

template <typename ProtocolCallback>
bool CanExecuteGlobalCommands(
    PageHandler* page_handler,
    const std::unique_ptr<ProtocolCallback>& callback) {
  Response response = page_handler->AssureTopLevelActiveFrame();
  if (!response.IsError()) {
    return true;
  }
  callback->sendFailure(response);
  return false;
}

void GotManifest(std::optional<std::string> manifest_id,
                 std::unique_ptr<PageHandler::GetAppManifestCallback> callback,
                 const GURL& manifest_url,
                 ::blink::mojom::ManifestPtr input_manifest,
                 blink::mojom::ManifestDebugInfoPtr debug_info) {
  if (manifest_id &&
      manifest_id.value() != input_manifest->id.possibly_invalid_spec()) {
    std::move(callback)->sendFailure(protocol::Response::InvalidParams(
        std::string("Page manifest id ") +
        input_manifest->id.possibly_invalid_spec() +
        " does not match the input " + manifest_id.value()));
    return;
  }

  auto errors = std::make_unique<protocol::Array<Page::AppManifestError>>();
  bool failed = true;
  if (debug_info) {
    failed = false;
    for (const auto& error : debug_info->errors) {
      errors->emplace_back(Page::AppManifestError::Create()
                               .SetMessage(error->message)
                               .SetCritical(error->critical)
                               .SetLine(error->line)
                               .SetColumn(error->column)
                               .Build());
      if (error->critical) {
        failed = true;
      }
    }
  }

  auto convert_icon = [](const blink::Manifest::ImageResource& input_icon)
      -> std::unique_ptr<Page::ImageResource> {
    auto icon = Page::ImageResource::Create();
    std::vector<std::string> size_strings;
    std::ranges::transform(input_icon.sizes, std::back_inserter(size_strings),
                           &gfx::Size::ToString);
    icon.SetSizes(base::JoinString(size_strings, " "));
    icon.SetType(base::UTF16ToUTF8(input_icon.type));
    return icon.SetUrl(input_icon.src.possibly_invalid_spec()).Build();
  };

  auto convert_icons =
      [convert_icon](
          const std::vector<blink::Manifest::ImageResource>& input_icons)
      -> std::unique_ptr<protocol::Array<Page::ImageResource>> {
    auto icons = std::make_unique<protocol::Array<Page::ImageResource>>();
    for (const auto& input_icon : input_icons) {
      icons->push_back(convert_icon(input_icon));
    }
    return icons;
  };

  auto manifest = Page::WebAppManifest::Create();
  if (input_manifest->has_background_color) {
    manifest.SetBackgroundColor(color_utils::SkColorToRgbaString(
        static_cast<SkColor>(input_manifest->background_color)));
  }
  if (input_manifest->description) {
    manifest.SetDescription(
        base::UTF16ToUTF8(input_manifest->description.value()));
  }
  // TODO(crbug.com/331214986): Fill the WebAppManifest.dir (direction).
  manifest.SetDisplay(base::ToString(input_manifest->display));
  if (!input_manifest->display_override.empty()) {
    auto display_overrides = std::make_unique<protocol::Array<std::string>>();
    for (const auto& display_override : input_manifest->display_override) {
      display_overrides->push_back(base::ToString(display_override));
    }
    manifest.SetDisplayOverrides(std::move(display_overrides));
  }
  if (!input_manifest->file_handlers.empty()) {
    auto file_handlers = std::make_unique<protocol::Array<Page::FileHandler>>();
    for (const auto& input_file_handler : input_manifest->file_handlers) {
      auto file_handler = Page::FileHandler::Create();
      if (!input_file_handler->icons.empty()) {
        file_handler.SetIcons(convert_icons(input_file_handler->icons));
      }
      if (!input_file_handler->accept.empty()) {
        auto accepts = std::make_unique<protocol::Array<Page::FileFilter>>();
        for (const auto& input_accept : input_file_handler->accept) {
          auto accept = Page::FileFilter::Create();
          accept.SetName(base::UTF16ToUTF8(input_accept.first));
          if (!input_accept.second.empty()) {
            auto accept_strs = std::make_unique<protocol::Array<std::string>>();
            for (const auto& accept_str : input_accept.second) {
              accept_strs->push_back(base::UTF16ToUTF8(accept_str));
            }
            accept.SetAccepts(std::move(accept_strs));
          }
          accepts->push_back(accept.Build());
        }
        file_handler.SetAccepts(std::move(accepts));
      }
      file_handlers->push_back(
          file_handler
              .SetAction(input_file_handler->action.possibly_invalid_spec())
              .SetName(base::UTF16ToUTF8(input_file_handler->name))
              .SetLaunchType(base::ToString(input_file_handler->launch_type))
              .Build());
    }
  }
  if (!input_manifest->icons.empty()) {
    manifest.SetIcons(convert_icons(input_manifest->icons));
  }
  manifest.SetId(input_manifest->id.possibly_invalid_spec());
  // TODO(crbug.com/331214986): Fill the WebAppManifest.lang.
  if (input_manifest->launch_handler) {
    manifest.SetLaunchHandler(
        Page::LaunchHandler::Create()
            .SetClientMode(base::ToString(
                input_manifest->launch_handler.value().parsed_client_mode()))
            .Build());
  }
  if (input_manifest->name) {
    manifest.SetName(base::UTF16ToUTF8(input_manifest->name.value()));
  }
  manifest.SetOrientation(base::ToString(input_manifest->orientation));
  manifest.SetPreferRelatedApplications(
      input_manifest->prefer_related_applications);
  if (!input_manifest->protocol_handlers.empty()) {
    auto protocol_handlers =
        std::make_unique<protocol::Array<Page::ProtocolHandler>>();
    for (const auto& input_protocol_handler :
         input_manifest->protocol_handlers) {
      protocol_handlers->push_back(
          Page::ProtocolHandler::Create()
              .SetProtocol(base::UTF16ToUTF8(input_protocol_handler->protocol))
              .SetUrl(input_protocol_handler->url.possibly_invalid_spec())
              .Build());
    }
    manifest.SetProtocolHandlers(std::move(protocol_handlers));
  }
  if (!input_manifest->scope_extensions.empty()) {
    auto scope_extensions =
        std::make_unique<protocol::Array<Page::ScopeExtension>>();
    for (const auto& input_scope_extension : input_manifest->scope_extensions) {
      scope_extensions->push_back(
          Page::ScopeExtension::Create()
              .SetOrigin(input_scope_extension->origin.Serialize())
              .SetHasOriginWildcard(input_scope_extension->has_origin_wildcard)
              .Build());
    }
    manifest.SetScopeExtensions(std::move(scope_extensions));
  }
  if (!input_manifest->screenshots.empty()) {
    auto screenshots = std::make_unique<protocol::Array<Page::Screenshot>>();
    for (const auto& input_screenshot : input_manifest->screenshots) {
      auto screenshot = Page::Screenshot::Create();
      if (input_screenshot->label) {
        screenshot.SetLabel(base::UTF16ToUTF8(input_screenshot->label.value()));
      }
      screenshots->push_back(
          screenshot.SetImage(convert_icon(input_screenshot->image))
              .SetFormFactor(base::ToString(input_screenshot->form_factor))
              .Build());
    }
    manifest.SetScreenshots(std::move(screenshots));
  }
  if (input_manifest->share_target) {
    const auto& input_share_target = input_manifest->share_target.value();
    auto share_target = Page::ShareTarget::Create();
    if (input_share_target.params.title) {
      share_target.SetTitle(
          base::UTF16ToUTF8(input_share_target.params.title.value()));
    }
    if (input_share_target.params.text) {
      share_target.SetTitle(
          base::UTF16ToUTF8(input_share_target.params.text.value()));
    }
    if (input_share_target.params.url) {
      share_target.SetTitle(
          base::UTF16ToUTF8(input_share_target.params.url.value()));
    }
    manifest.SetShareTarget(
        share_target
            .SetAction(
                input_manifest->share_target->action.possibly_invalid_spec())
            .SetMethod(base::ToString(input_manifest->share_target->method))
            .SetEnctype(base::ToString(input_manifest->share_target->action))
            .Build());
  }
  if (!input_manifest->related_applications.empty()) {
    auto related_apps =
        std::make_unique<protocol::Array<Page::RelatedApplication>>();
    for (const auto& input_related_app : input_manifest->related_applications) {
      auto related_app = Page::RelatedApplication::Create();
      if (input_related_app.id) {
        related_app.SetId(base::UTF16ToUTF8(input_related_app.id.value()));
      }
      related_apps->push_back(
          related_app.SetUrl(input_related_app.url.possibly_invalid_spec())
              .Build());
    }
    manifest.SetRelatedApplications(std::move(related_apps));
  }
  manifest.SetScope(input_manifest->scope.possibly_invalid_spec());
  if (!input_manifest->shortcuts.empty()) {
    auto shortcuts = std::make_unique<protocol::Array<Page::Shortcut>>();
    for (const auto& input_shortcut : input_manifest->shortcuts) {
      shortcuts->push_back(
          Page::Shortcut::Create()
              .SetName(base::UTF16ToUTF8(input_shortcut.name))
              .SetUrl(input_shortcut.url.possibly_invalid_spec())
              .Build());
    }
    manifest.SetShortcuts(std::move(shortcuts));
  }
  manifest.SetStartUrl(input_manifest->start_url.possibly_invalid_spec());
  if (input_manifest->has_theme_color) {
    manifest.SetThemeColor(color_utils::SkColorToRgbaString(
        static_cast<SkColor>(input_manifest->theme_color)));
  }

  std::unique_ptr<Page::AppManifestParsedProperties> parsed;
  if (!blink::IsEmptyManifest(input_manifest)) {
    parsed = Page::AppManifestParsedProperties::Create()
                 .SetScope(input_manifest->scope.possibly_invalid_spec())
                 .Build();
  }

  std::move(callback)->sendSuccess(
      manifest_url.possibly_invalid_spec(), std::move(errors),
      failed ? std::optional<std::string>() : debug_info->raw_manifest,
      std::move(parsed), manifest.Build());
}

std::string GetFrameStartedNavigatingNavigationTypeString(
    const blink::mojom::NavigationType& navigation_type) {
  switch (navigation_type) {
    case blink::mojom::NavigationType::RELOAD:
      return Page::FrameStartedNavigating::NavigationTypeEnum::Reload;
    case blink::mojom::NavigationType::RELOAD_BYPASSING_CACHE:
      return Page::FrameStartedNavigating::NavigationTypeEnum::
          ReloadBypassingCache;
    case blink::mojom::NavigationType::RESTORE:
      return Page::FrameStartedNavigating::NavigationTypeEnum::Restore;
    case blink::mojom::NavigationType::RESTORE_WITH_POST:
      return Page::FrameStartedNavigating::NavigationTypeEnum::RestoreWithPost;
    case blink::mojom::NavigationType::HISTORY_SAME_DOCUMENT:
      return Page::FrameStartedNavigating::NavigationTypeEnum::
          HistorySameDocument;
    case blink::mojom::NavigationType::HISTORY_DIFFERENT_DOCUMENT:
      return Page::FrameStartedNavigating::NavigationTypeEnum::
          HistoryDifferentDocument;
    case blink::mojom::NavigationType::SAME_DOCUMENT:
      return Page::FrameStartedNavigating::NavigationTypeEnum::SameDocument;
    case blink::mojom::NavigationType::DIFFERENT_DOCUMENT:
      return Page::FrameStartedNavigating::NavigationTypeEnum::
          DifferentDocument;
    default:
      NOTREACHED();
  }
}
}  // namespace

struct PageHandler::PendingScreenshotRequest {
  PendingScreenshotRequest(base::ScopedClosureRunner capturer_handle,
                           PageHandler::BitmapEncoder encoder,
                           std::unique_ptr<CaptureScreenshotCallback> callback)
      : capturer_handle(std::move(capturer_handle)),
        encoder(std::move(encoder)),
        callback(std::move(callback)) {}

  base::ScopedClosureRunner capturer_handle;
  PageHandler::BitmapEncoder encoder;
  std::unique_ptr<CaptureScreenshotCallback> callback;
  blink::DeviceEmulationParams original_emulation_params;
  std::optional<blink::web_pref::WebPreferences> original_web_prefs;
  gfx::Size original_view_size;
  gfx::Size requested_image_size;
};

PageHandler::PageHandler(
    EmulationHandler* emulation_handler,
    BrowserHandler* browser_handler,
    bool allow_unsafe_operations,
    bool is_trusted,
    std::optional<url::Origin> navigation_initiator_origin,
    bool may_read_local_files,
    base::RepeatingCallback<void(std::string)> prepare_for_reload_callback)
    : DevToolsDomainHandler(Page::Metainfo::domainName),
      allow_unsafe_operations_(allow_unsafe_operations),
      is_trusted_(is_trusted),
      navigation_initiator_origin_(navigation_initiator_origin),
      may_read_local_files_(may_read_local_files),
      enabled_(false),
      screencast_max_width_(-1),
      screencast_max_height_(-1),
      capture_every_nth_frame_(1),
      session_id_(0),
      frame_counter_(0),
      frames_in_flight_(0),
      host_(nullptr),
      emulation_handler_(emulation_handler),
      browser_handler_(browser_handler),
      prepare_for_reload_callback_(std::move(prepare_for_reload_callback)) {
#if BUILDFLAG(IS_ANDROID)
  constexpr auto kScreencastPixelFormat = media::PIXEL_FORMAT_I420;
#elif (BUILDFLAG(IS_ARKWEB) && BUILDFLAG(ARKWEB_DEVTOOLS))
  constexpr auto kScreencastPixelFormat = media::PIXEL_FORMAT_I420;
#else
  constexpr auto kScreencastPixelFormat = media::PIXEL_FORMAT_ARGB;
#endif
  video_consumer_ = std::make_unique<DevToolsVideoConsumer>(base::BindRepeating(
      &PageHandler::OnFrameFromVideoConsumer, weak_factory_.GetWeakPtr()));
  video_consumer_->SetFormat(kScreencastPixelFormat);
  DCHECK(emulation_handler_);
}

PageHandler::~PageHandler() = default;

// static
std::vector<PageHandler*> PageHandler::EnabledForWebContents(
    WebContentsImpl* contents) {
  if (!DevToolsAgentHost::HasFor(contents)) {
    return std::vector<PageHandler*>();
  }
  std::vector<PageHandler*> result;
  for (auto* handler :
       PageHandler::ForAgentHost(static_cast<DevToolsAgentHostImpl*>(
           DevToolsAgentHost::GetOrCreateFor(contents).get()))) {
    if (handler->enabled_) {
      result.push_back(handler);
    }
  }
  return result;
}

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

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

  RenderWidgetHostImpl* widget_host =
      host_ ? host_->GetRenderWidgetHost() : nullptr;
  if (widget_host && observation_.IsObservingSource(widget_host)) {
    observation_.Reset();
  }

  host_ = frame_host;
  widget_host = host_ ? host_->GetRenderWidgetHost() : nullptr;

  if (widget_host) {
    observation_.Observe(widget_host);
  }

  if (frame_host) {
    video_consumer_->SetFrameSinkId(
        frame_host->GetRenderWidgetHost()->GetFrameSinkId());
  }
}

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

void PageHandler::RenderWidgetHostVisibilityChanged(
    RenderWidgetHost* widget_host,
    bool became_visible) {
  if (!screencast_encoder_) {
    return;
  }
  NotifyScreencastVisibility(became_visible);
}

void PageHandler::RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) {
  DCHECK(observation_.IsObservingSource(widget_host));
  observation_.Reset();
}

void PageHandler::DidAttachInterstitialPage() {
  if (!enabled_) {
    return;
  }
  frontend_->InterstitialShown();
}

void PageHandler::DidDetachInterstitialPage() {
  if (!enabled_) {
    return;
  }
  frontend_->InterstitialHidden();
}

void PageHandler::DidRunJavaScriptDialog(const GURL& url,
                                         const base::UnguessableToken& frame_id,
                                         const std::u16string& message,
                                         const std::u16string& default_prompt,
                                         JavaScriptDialogType dialog_type,
                                         bool has_non_devtools_handlers,
                                         JavaScriptDialogCallback callback) {
  if (!enabled_) {
    return;
  }
  DCHECK(pending_dialog_.is_null());
  pending_dialog_ = std::move(callback);
  std::string type = Page::DialogTypeEnum::Alert;
  if (dialog_type == JAVASCRIPT_DIALOG_TYPE_CONFIRM) {
    type = Page::DialogTypeEnum::Confirm;
  }
  if (dialog_type == JAVASCRIPT_DIALOG_TYPE_PROMPT) {
    type = Page::DialogTypeEnum::Prompt;
  }
  frontend_->JavascriptDialogOpening(
      url.spec(), frame_id.ToString(), base::UTF16ToUTF8(message), type,
      has_non_devtools_handlers, base::UTF16ToUTF8(default_prompt));
}

void PageHandler::DidRunBeforeUnloadConfirm(
    const GURL& url,
    const base::UnguessableToken& frame_id,
    bool has_non_devtools_handlers,
    JavaScriptDialogCallback callback) {
  if (!enabled_) {
    return;
  }
  DCHECK(pending_dialog_.is_null());
  pending_dialog_ = std::move(callback);
  frontend_->JavascriptDialogOpening(url.spec(), frame_id.ToString(),
                                     std::string(),
                                     Page::DialogTypeEnum::Beforeunload,
                                     has_non_devtools_handlers, std::string());
}

void PageHandler::DidCloseJavaScriptDialog(
    const base::UnguessableToken& frame_id,
    bool success,
    const std::u16string& user_input) {
  if (!enabled_) {
    return;
  }
  pending_dialog_.Reset();
  frontend_->JavascriptDialogClosed(frame_id.ToString(), success,
                                    base::UTF16ToUTF8(user_input));
}

Response PageHandler::Enable(
    std::optional<bool> enable_file_chooser_opened_event) {
  if (!enabled_ && !host_->GetParentOrOuterDocument() &&
      host_->frame_tree_node() &&
      host_->frame_tree_node()->navigation_request()) {
    // If the Page domain was not enabled, the page is the top level frame, and
    // there is a pending navigation, emit `FrameStartedNavigating` event.
    FrameTreeNode* frame_tree_node = host_->frame_tree_node();
    NavigationRequest* navigation_request =
        host_->frame_tree_node()->navigation_request();
    frontend_->FrameStartedNavigating(
        frame_tree_node->current_frame_host()
            ->devtools_frame_token()
            .ToString(),
        navigation_request->common_params().url.spec(),
        navigation_request->devtools_navigation_token().ToString(),
        GetFrameStartedNavigatingNavigationTypeString(
            navigation_request->common_params().navigation_type));
  }

  enabled_ = true;
  return Response::FallThrough();
}

Response PageHandler::Disable() {
  enabled_ = false;
  bypass_csp_ = false;

  StopScreencast();

  if (!pending_dialog_.is_null()) {
    ResponseOrWebContents result = GetWebContentsForTopLevelActiveFrame();
    // Only a top level frame can have a dialog.
    DCHECK(std::holds_alternative<WebContentsImpl*>(result));
    WebContentsImpl* web_contents = std::get<WebContentsImpl*>(result);
    // Leave dialog hanging if there is a manager that can take care of it,
    // cancel and send ack otherwise.
    bool has_dialog_manager =
        web_contents && web_contents->GetDelegate() &&
        web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents);
    if (!has_dialog_manager) {
      std::move(pending_dialog_).Run(false, std::u16string());
    }
    pending_dialog_.Reset();
  }

  for (download::DownloadItem* item : pending_downloads_) {
    item->RemoveObserver(this);
  }
  pending_downloads_.clear();
  navigate_callbacks_.clear();
  SetPrerenderingAllowed(true);

  return Response::FallThrough();
}

Response PageHandler::Crash() {
  // Can be called in a subframe.
  WebContents* web_contents = WebContents::FromRenderFrameHost(host_);
  if (!web_contents) {
    return Response::ServerError(kErrorNotAttached);
  }
  if (web_contents->IsCrashed()) {
    return Response::ServerError("The target has already crashed");
  }
  if (host_->frame_tree_node()->navigation_request()) {
    return Response::ServerError("Page has pending navigations, not killing");
  }
  return Response::FallThrough();
}

Response PageHandler::Close() {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  host_->DispatchBeforeUnload(RenderFrameHostImpl::BeforeUnloadType::TAB_CLOSE,
                              false);
  return Response::Success();
}

void PageHandler::Reload(std::optional<bool> bypassCache,
                         std::optional<std::string> script_to_evaluate_on_load,
                         std::optional<std::string> loader_id,
                         std::unique_ptr<ReloadCallback> callback) {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    callback->sendFailure(response);
    return;
  }

  // In the case of inspecting a GuestView (e.g. a PDF), we should reload
  // the outer web contents (embedder), since otherwise reloading the guest by
  // itself will fail.
  RenderFrameHostImpl* outermost_main_frame =
      host_->GetOutermostMainFrameOrEmbedder();

  if (loader_id.has_value()) {
    auto navigation_token = outermost_main_frame->GetDevToolsNavigationToken();
    if (!navigation_token.has_value() ||
        *loader_id != navigation_token->ToString()) {
      callback->sendFailure(Response::InvalidParams(
          "Reload was discarded because the page already navigated"));
      return;
    }
  }

  const auto reload_type = bypassCache.value_or(false)
                               ? ReloadType::BYPASSING_CACHE
                               : ReloadType::NORMAL;
  NavigationController& navigation_controller =
      outermost_main_frame->frame_tree()->controller();
  // For RenderDocument, the path is different: we don't fall through to the
  // renderer and process `scriptToEvaluateOnLoad` in the browser instead.
  const bool handle_reload_on_browser_side =
      outermost_main_frame->ShouldChangeRenderFrameHostOnSameSiteNavigation();
  if (handle_reload_on_browser_side) {
    have_pending_reload_ = true;
    pending_script_to_evaluate_on_load_ =
        script_to_evaluate_on_load.value_or("");
    navigation_controller.Reload(reload_type, false);
    callback->sendSuccess();
  } else {
    // It is important to fallback before triggering reload, so that
    // renderer could prepare beforehand.
    callback->fallThrough();
    navigation_controller.Reload(reload_type, false);
  }
}

static std::optional<network::mojom::ReferrerPolicy> ParsePolicyFromString(
    const std::string& policy) {
  if (policy == Page::ReferrerPolicyEnum::NoReferrer) {
    return network::mojom::ReferrerPolicy::kNever;
  }
  if (policy == Page::ReferrerPolicyEnum::NoReferrerWhenDowngrade) {
    return network::mojom::ReferrerPolicy::kNoReferrerWhenDowngrade;
  }
  if (policy == Page::ReferrerPolicyEnum::Origin) {
    return network::mojom::ReferrerPolicy::kOrigin;
  }
  if (policy == Page::ReferrerPolicyEnum::OriginWhenCrossOrigin) {
    return network::mojom::ReferrerPolicy::kOriginWhenCrossOrigin;
  }
  if (policy == Page::ReferrerPolicyEnum::SameOrigin) {
    return network::mojom::ReferrerPolicy::kSameOrigin;
  }
  if (policy == Page::ReferrerPolicyEnum::StrictOrigin) {
    return network::mojom::ReferrerPolicy::kStrictOrigin;
  }
  if (policy == Page::ReferrerPolicyEnum::StrictOriginWhenCrossOrigin) {
    return network::mojom::ReferrerPolicy::kStrictOriginWhenCrossOrigin;
  }
  if (policy == Page::ReferrerPolicyEnum::UnsafeUrl) {
    return network::mojom::ReferrerPolicy::kAlways;
  }
  return std::nullopt;
}

namespace {

void DispatchNavigateCallback(
    NavigationRequest* request,
    std::unique_ptr<PageHandler::NavigateCallback> callback) {
  std::string frame_id = request->frame_tree_node()
                             ->current_frame_host()
                             ->devtools_frame_token()
                             .ToString();
  // A new NavigationRequest may have been created before |request|
  // started, in which case it is not marked as aborted. We report this as an
  // abort to DevTools anyway.
  if (!request->IsNavigationStarted()) {
    callback->sendSuccess(frame_id, std::nullopt,
                          net::ErrorToString(net::ERR_ABORTED),
                          request->IsDownload());
    return;
  }
  std::optional<std::string> opt_error;
  if (request->GetNetErrorCode() != net::OK) {
    opt_error = net::ErrorToString(request->GetNetErrorCode());
  }
  std::optional<std::string> loader_id =
      request->IsSameDocument()
          ? std::optional<std::string>()
          : request->devtools_navigation_token().ToString();
  callback->sendSuccess(frame_id, std::move(loader_id), std::move(opt_error),
                        request->IsDownload());
}

}  // namespace

void PageHandler::Navigate(const std::string& url,
                           std::optional<std::string> referrer,
                           std::optional<std::string> maybe_transition_type,
                           std::optional<std::string> frame_id,
                           std::optional<std::string> referrer_policy,
                           std::unique_ptr<NavigateCallback> callback) {
  GURL gurl(url);
  if (!gurl.is_valid()) {
    callback->sendFailure(
        Response::ServerError("Cannot navigate to invalid URL"));
    return;
  }
  if (gurl.SchemeIsFile() && !may_read_local_files_) {
    callback->sendFailure(
        Response::ServerError("Navigating to local URL is not allowed"));
    return;
  }

  if (!host_) {
    callback->sendFailure(Response::InternalError());
    return;
  }

  // chrome-untrusted:// WebUIs might perform high-priviledged actions on
  // navigation, disallow navigation to them unless the client is trusted.
  if ((gurl.SchemeIs(kChromeUIUntrustedScheme) ||
       gurl.SchemeIs(kChromeDevToolsScheme)) &&
      !is_trusted_) {
    callback->sendFailure(Response::ServerError(
        "Navigating to a URL with a privileged scheme is not allowed"));
    return;
  }

  ui::PageTransition type;
  std::string transition_type =
      maybe_transition_type.value_or(Page::TransitionTypeEnum::Typed);
  if (transition_type == Page::TransitionTypeEnum::Link) {
    type = ui::PAGE_TRANSITION_LINK;
  } else if (transition_type == Page::TransitionTypeEnum::Typed) {
    type = ui::PAGE_TRANSITION_TYPED;
  } else if (transition_type == Page::TransitionTypeEnum::Address_bar) {
    type = ui::PAGE_TRANSITION_FROM_ADDRESS_BAR;
  } else if (transition_type == Page::TransitionTypeEnum::Auto_bookmark) {
    type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
  } else if (transition_type == Page::TransitionTypeEnum::Auto_subframe) {
    type = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
  } else if (transition_type == Page::TransitionTypeEnum::Manual_subframe) {
    type = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
  } else if (transition_type == Page::TransitionTypeEnum::Generated) {
    type = ui::PAGE_TRANSITION_GENERATED;
  } else if (transition_type == Page::TransitionTypeEnum::Auto_toplevel) {
    type = ui::PAGE_TRANSITION_AUTO_TOPLEVEL;
  } else if (transition_type == Page::TransitionTypeEnum::Form_submit) {
    type = ui::PAGE_TRANSITION_FORM_SUBMIT;
  } else if (transition_type == Page::TransitionTypeEnum::Reload) {
    type = ui::PAGE_TRANSITION_RELOAD;
  } else if (transition_type == Page::TransitionTypeEnum::Keyword) {
    type = ui::PAGE_TRANSITION_KEYWORD;
  } else if (transition_type == Page::TransitionTypeEnum::Keyword_generated) {
    type = ui::PAGE_TRANSITION_KEYWORD_GENERATED;
  } else {
    type = ui::PAGE_TRANSITION_TYPED;
  }

  type = ui::PageTransitionFromInt(type | ui::PAGE_TRANSITION_FROM_API);

  std::string out_frame_id =
      frame_id.value_or(host_->devtools_frame_token().ToString());
  FrameTreeNode* frame_tree_node = FrameTreeNodeFromDevToolsFrameToken(
      host_->frame_tree_node(), out_frame_id);

  if (!frame_tree_node) {
    callback->sendFailure(
        Response::ServerError("No frame with given id found"));
    return;
  }

  network::mojom::ReferrerPolicy policy =
      network::mojom::ReferrerPolicy::kDefault;
  if (referrer_policy.has_value()) {
    const auto& parsed_policy = ParsePolicyFromString(referrer_policy.value());
    if (!parsed_policy.has_value()) {
      callback->sendFailure(Response::InvalidParams("Invalid referrerPolicy"));
      return;
    }
    policy = parsed_policy.value();
  }

  NavigationController::LoadURLParams params(gurl);
  params.referrer = Referrer(GURL(referrer.value_or("")), policy);
  params.transition_type = type;
  params.frame_tree_node_id = frame_tree_node->frame_tree_node_id();
  if (navigation_initiator_origin_.has_value()) {
    // When this agent has an initiator origin defined, ensure that its
    // navigations are considered renderer-initiated by that origin, such that
    // URL spoof defenses are in effect. (crbug.com/1192417)
    params.is_renderer_initiated = true;
    params.initiator_origin = *navigation_initiator_origin_;
    params.source_site_instance = SiteInstance::CreateForURL(
        host_->GetBrowserContext(), navigation_initiator_origin_->GetURL());
  }
  // Handler may be destroyed while navigating if the session
  // gets disconnected as a result of access checks.
  base::WeakPtr<PageHandler> weak_self = weak_factory_.GetWeakPtr();
  base::WeakPtr<NavigationHandle> navigation_handle =
      frame_tree_node->navigator().controller().LoadURLWithParams(params);
  // TODO(caseq): should we still dispatch callback here?
  if (!weak_self) {
    return;
  }
  if (!navigation_handle) {
    callback->sendSuccess(out_frame_id, std::nullopt,
                          net::ErrorToString(net::ERR_ABORTED), std::nullopt);
    return;
  }
  auto* navigation_request =
      static_cast<NavigationRequest*>(navigation_handle.get());
  if (frame_tree_node->navigation_request() != navigation_request) {
    // The ownership of the navigation request should have been transferred to
    // RFH at this point, so we won't get `NavigationReset` for it any more --
    // fire the callback now!
    DispatchNavigateCallback(navigation_request, std::move(callback));
    return;
  }
  // At this point, we expect the callback to get dispatched upon
  // `NavigationReset()` is called when `NavigationRequest` is taken from
  // `FrameTreeNode`.
  const base::UnguessableToken& navigation_token =
      navigation_request->devtools_navigation_token();
  navigate_callbacks_[navigation_token] = std::move(callback);
}

namespace optimization_guide::proto {
class AnnotatedPageContent;
}

void PageHandler::GetAnnotatedPageContent(
    std::optional<bool> include_actionable_information,
    std::unique_ptr<GetAnnotatedPageContentCallback> callback) {
  ResponseOrWebContents result = GetWebContentsForTopLevelActiveFrame();
  if (std::holds_alternative<Response>(result)) {
    callback->sendFailure(std::get<Response>(result));
    return;
  }
  WebContentsImpl* web_contents = std::get<WebContentsImpl*>(result);
  if (!web_contents) {
    callback->sendFailure(Response::ServerError("WebContents not found."));
    return;
  }

  if (WebContentsDelegate* delegate = web_contents->GetDelegate()) {
    delegate->GetAIPageContent(
        web_contents, include_actionable_information.value_or(true),
        base::BindOnce(
            [](std::unique_ptr<GetAnnotatedPageContentCallback> callback,
               const std::string& serialized_proto) {
              if (!serialized_proto.empty()) {
                callback->sendSuccess(
                    protocol::Binary::fromString(serialized_proto));
              } else {
                callback->sendFailure(Response::ServerError(
                    "Failed to get annotated page content"));
              }
            },
            std::move(callback)));
  } else {
    callback->sendFailure(Response::ServerError(
        "No WebContentsDelegate available to get annotated page content"));
  }
}

void PageHandler::NavigationReset(NavigationRequest* navigation_request) {
  auto it =
      navigate_callbacks_.find(navigation_request->devtools_navigation_token());
  if (it == navigate_callbacks_.end()) {
    return;
  }
  DispatchNavigateCallback(navigation_request, std::move(it->second));
  navigate_callbacks_.erase(it);
}

void PageHandler::DownloadWillBegin(FrameTreeNode* ftn,
                                    download::DownloadItem* item) {
  if (!enabled_) {
    return;
  }

  // The filename the end user sees may differ. This is an attempt to eagerly
  // determine the filename at the beginning of the download; see
  // DownloadTargetDeterminer:DownloadTargetDeterminer::Result
  // and DownloadTargetDeterminer::GenerateFileName in
  // chrome/browser/download/download_target_determiner.cc
  // for the more comprehensive logic.
  const std::u16string likely_filename = net::GetSuggestedFilename(
      item->GetURL(), item->GetContentDisposition(), std::string(),
      item->GetSuggestedFilename(), item->GetMimeType(), "download");

  frontend_->DownloadWillBegin(
      ftn->current_frame_host()->devtools_frame_token().ToString(),
      item->GetGuid(), item->GetURL().spec(),
      base::UTF16ToUTF8(likely_filename));

  item->AddObserver(this);
  pending_downloads_.insert(item);
}

void PageHandler::DidStartNavigating(
    FrameTreeNode& ftn,
    const GURL& url,
    const base::UnguessableToken& loader_id,
    const blink::mojom::NavigationType& navigation_type) {
  if (!enabled_) {
    return;
  }

  frontend_->FrameStartedNavigating(
      ftn.current_frame_host()->devtools_frame_token().ToString(), url.spec(),
      loader_id.ToString(),
      GetFrameStartedNavigatingNavigationTypeString(navigation_type));
}

void PageHandler::OnFrameDetached(const base::UnguessableToken& frame_id) {
  if (!enabled_) {
    return;
  }
  frontend_->FrameDetached(frame_id.ToString(), "remove");
}

void PageHandler::DidChangeFrameLoadingState(const FrameTreeNode& ftn) {
  if (!enabled_) {
    return;
  }
  const std::string& frame_id =
      ftn.current_frame_host()->devtools_frame_token().ToString();
  if (ftn.IsLoading()) {
    frontend_->FrameStartedLoading(frame_id);
  } else {
    frontend_->FrameStoppedLoading(frame_id);
  }
}

void PageHandler::OnDownloadDestroyed(download::DownloadItem* item) {
  pending_downloads_.erase(item);
}

void PageHandler::OnDownloadUpdated(download::DownloadItem* item) {
  if (!enabled_) {
    return;
  }
  std::string state;
  switch (item->GetState()) {
    case download::DownloadItem::IN_PROGRESS:
      state = Page::DownloadProgress::StateEnum::InProgress;
      break;
    case download::DownloadItem::COMPLETE:
      state = Page::DownloadProgress::StateEnum::Completed;
      break;
    case download::DownloadItem::CANCELLED:
    case download::DownloadItem::INTERRUPTED:
      state = Page::DownloadProgress::StateEnum::Canceled;
      break;
    case download::DownloadItem::MAX_DOWNLOAD_STATE:
      NOTREACHED();
  }
  frontend_->DownloadProgress(item->GetGuid(), item->GetTotalBytes(),
                              item->GetReceivedBytes(), state);
  if (state != Page::DownloadProgress::StateEnum::InProgress) {
    item->RemoveObserver(this);
    pending_downloads_.erase(item);
  }
}

static const char* TransitionTypeName(ui::PageTransition type) {
  int32_t t = type & ~ui::PAGE_TRANSITION_QUALIFIER_MASK;
  switch (t) {
    case ui::PAGE_TRANSITION_LINK:
      return Page::TransitionTypeEnum::Link;
    case ui::PAGE_TRANSITION_TYPED:
      return Page::TransitionTypeEnum::Typed;
    case ui::PAGE_TRANSITION_AUTO_BOOKMARK:
      return Page::TransitionTypeEnum::Auto_bookmark;
    case ui::PAGE_TRANSITION_AUTO_SUBFRAME:
      return Page::TransitionTypeEnum::Auto_subframe;
    case ui::PAGE_TRANSITION_MANUAL_SUBFRAME:
      return Page::TransitionTypeEnum::Manual_subframe;
    case ui::PAGE_TRANSITION_GENERATED:
      return Page::TransitionTypeEnum::Generated;
    case ui::PAGE_TRANSITION_AUTO_TOPLEVEL:
      return Page::TransitionTypeEnum::Auto_toplevel;
    case ui::PAGE_TRANSITION_FORM_SUBMIT:
      return Page::TransitionTypeEnum::Form_submit;
    case ui::PAGE_TRANSITION_RELOAD:
      return Page::TransitionTypeEnum::Reload;
    case ui::PAGE_TRANSITION_KEYWORD:
      return Page::TransitionTypeEnum::Keyword;
    case ui::PAGE_TRANSITION_KEYWORD_GENERATED:
      return Page::TransitionTypeEnum::Keyword_generated;
    default:
      return Page::TransitionTypeEnum::Other;
  }
}

Response PageHandler::GetNavigationHistory(
    int* current_index,
    std::unique_ptr<NavigationEntries>* entries) {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  NavigationController& controller = host_->frame_tree()->controller();
  *current_index = controller.GetCurrentEntryIndex();
  *entries = std::make_unique<NavigationEntries>();
  for (int i = 0; i != controller.GetEntryCount(); ++i) {
    auto* entry = controller.GetEntryAtIndex(i);
    (*entries)->emplace_back(
        Page::NavigationEntry::Create()
            .SetId(entry->GetUniqueID())
            .SetUrl(entry->GetURL().spec())
            .SetUserTypedURL(entry->GetUserTypedURL().spec())
            .SetTitle(base::UTF16ToUTF8(entry->GetTitle()))
            .SetTransitionType(TransitionTypeName(entry->GetTransitionType()))
            .Build());
  }
  return Response::Success();
}

Response PageHandler::NavigateToHistoryEntry(int entry_id) {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  NavigationController& controller = host_->frame_tree()->controller();
  for (int i = 0; i != controller.GetEntryCount(); ++i) {
    if (controller.GetEntryAtIndex(i)->GetUniqueID() == entry_id) {
      controller.GoToIndex(i);
      return Response::Success();
    }
  }

  return Response::InvalidParams("No entry with passed id");
}

static bool ReturnTrue(NavigationEntry* entry) {
  return true;
}

Response PageHandler::ResetNavigationHistory() {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  NavigationController& controller = host_->frame_tree()->controller();
  if (controller.CanPruneAllButLastCommitted()) {
    controller.DeleteNavigationEntries(base::BindRepeating(&ReturnTrue));
    return Response::Success();
  } else {
    return Response::ServerError("History cannot be pruned");
  }
}

void PageHandler::CaptureSnapshot(
    std::optional<std::string> format,
    std::unique_ptr<CaptureSnapshotCallback> callback) {
  if (!CanExecuteGlobalCommands(this, callback)) {
    return;
  }
  std::string snapshot_format = format.value_or(kMhtml);
  if (snapshot_format != kMhtml) {
    callback->sendFailure(Response::ServerError("Unsupported snapshot format"));
    return;
  }

  DCHECK(host_);
  DevToolsMHTMLHelper::Capture(
      base::BindRepeating(&WebContents::FromFrameTreeNodeId,
                          host_->frame_tree_node()->frame_tree_node_id()),
      std::move(callback));
}

// Sets a clip with full page dimensions. Calls CaptureScreenshot with updated
// value to proceed with capturing the full page screenshot.
// TODO(crbug.com/40238745): at the point this method is called, the page could
// have changed its size.
void PageHandler::CaptureFullPageScreenshot(
    std::optional<std::string> format,
    std::optional<int> quality,
    std::optional<bool> optimize_for_speed,
    std::unique_ptr<CaptureScreenshotCallback> callback,
    const gfx::Size& full_page_size) {
  // check width and height for validity
  // max_size is needed to respect the limit of 16K of the headless mode
  const int kMaxDimension = 128 * 1024;
  if (full_page_size.width() >= kMaxDimension ||
      full_page_size.height() >= kMaxDimension) {
    callback->sendFailure(Response::ServerError("Page is too large."));
    return;
  }

  auto clip = Page::Viewport::Create()
                  .SetX(0)
                  .SetY(0)
                  .SetWidth(full_page_size.width())
                  .SetHeight(full_page_size.height())
                  .SetScale(1)
                  .Build();
  CaptureScreenshot(std::move(format), std::move(quality), std::move(clip),
                    /*from_surface=*/true, /*capture_beyond_viewport=*/true,
                    std::move(optimize_for_speed), std::move(callback));
}

void PageHandler::CaptureScreenshot(
    std::optional<std::string> format,
    std::optional<int> quality,
    std::unique_ptr<Page::Viewport> clip,
    std::optional<bool> from_surface,
    std::optional<bool> capture_beyond_viewport,
    std::optional<bool> optimize_for_speed,
    std::unique_ptr<CaptureScreenshotCallback> callback) {
  if (!host_ || !host_->GetRenderWidgetHost() ||
      !host_->GetRenderWidgetHost()->GetView()) {
    callback->sendFailure(Response::InternalError());
    return;
  }
  if (!CanExecuteGlobalCommands(this, callback)) {
    return;
  }

  // Check if full page screenshot is expected and get dimensions accordingly.
  if (from_surface.value_or(true) && capture_beyond_viewport.value_or(false) &&
      !clip) {
    blink::mojom::LocalMainFrame* main_frame =
        host_->GetAssociatedLocalMainFrame();
    main_frame->GetFullPageSize(base::BindOnce(
        &PageHandler::CaptureFullPageScreenshot, weak_factory_.GetWeakPtr(),
        std::move(format), std::move(quality), std::move(optimize_for_speed),
        std::move(callback)));
    return;
  }
  if (clip) {
    if (clip->GetWidth() == 0) {
      callback->sendFailure(
          Response::ServerError("Cannot take screenshot with 0 width."));
      return;
    }
    if (clip->GetHeight() == 0) {
      callback->sendFailure(
          Response::ServerError("Cannot take screenshot with 0 height."));
      return;
    }
  }

  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
  auto encoder =
      GetEncoder(format.value_or(Page::CaptureScreenshot::FormatEnum::Png),
                 quality.value_or(kDefaultScreenshotQuality),
                 optimize_for_speed.value_or(false));
  if (std::holds_alternative<Response>(encoder)) {
    callback->sendFailure(std::get<Response>(encoder));
    return;
  }

  base::ScopedClosureRunner capturer_handle;
  if (auto* wc = WebContents::FromRenderFrameHost(host_)) {
    // Tell page it needs to produce frames even if it doesn't want to (e.g. is
    // not currently visible).
    capturer_handle =
        wc->IncrementCapturerCount(gfx::Size(), /*stay_hidden=*/true,
                                   /*stay_awake=*/true, /*is_activity=*/false);
  }

  auto pending_request = std::make_unique<PendingScreenshotRequest>(
      std::move(capturer_handle), std::move(std::get<BitmapEncoder>(encoder)),
      std::move(callback));

  // We don't support clip/emulation when capturing from window, bail out.
  if (!from_surface.value_or(true)) {
    if (!is_trusted_) {
      pending_request->callback->sendFailure(
          Response::ServerError("Only screenshots from surface are allowed."));
      return;
    }
    widget_host->GetSnapshotFromBrowser(
        base::BindOnce(&PageHandler::ScreenshotCaptured,
                       weak_factory_.GetWeakPtr(), std::move(pending_request)),
        false);
    return;
  }

  // Welcome to the neural net of capturing screenshot while emulating device
  // metrics!
  bool emulation_enabled = emulation_handler_->device_emulation_enabled();
  pending_request->original_emulation_params =
      emulation_handler_->GetDeviceEmulationParams();
  const blink::DeviceEmulationParams& original_params =
      pending_request->original_emulation_params;
  blink::DeviceEmulationParams modified_params = original_params;

  // Capture original view size if we know we are going to destroy it. We use
  // it in ScreenshotCaptured to restore.
  const gfx::Size original_view_size =
      emulation_enabled || clip ? widget_host->GetView()->GetViewBounds().size()
                                : gfx::Size();
  pending_request->original_view_size = original_view_size;
  gfx::Size emulated_view_size = modified_params.view_size;

  double dpfactor = 1;
  float widget_host_device_scale_factor = widget_host->GetDeviceScaleFactor();
  if (emulation_enabled) {
    // When emulating, emulate again and scale to make resulting image match
    // physical DP resolution. If view_size is not overriden, use actual view
    // size.
    float original_scale =
        original_params.scale > 0 ? original_params.scale : 1;
    if (!modified_params.view_size.width()) {
      emulated_view_size.set_width(
          ceil(original_view_size.width() / original_scale));
    }
    if (!modified_params.view_size.height()) {
      emulated_view_size.set_height(
          ceil(original_view_size.height() / original_scale));
    }

    dpfactor = modified_params.device_scale_factor
                   ? modified_params.device_scale_factor /
                         widget_host_device_scale_factor
                   : 1;
    // When clip is specified, we scale viewport via clip, otherwise we use
    // scale.
    modified_params.scale = clip ? 1 : dpfactor;
    modified_params.view_size = emulated_view_size;
  } else if (clip) {
    // When not emulating, still need to emulate the page size.
    modified_params.view_size = original_view_size;
    modified_params.screen_size = gfx::Size();
    modified_params.device_scale_factor = 0;
    modified_params.scale = 1;
  }

  // Set up viewport in renderer.
  if (clip) {
    modified_params.viewport_offset.SetPoint(clip->GetX(), clip->GetY());
    modified_params.viewport_scale = clip->GetScale() * dpfactor;
    modified_params.viewport_offset.Scale(widget_host_device_scale_factor);
  }

  if (capture_beyond_viewport.value_or(false)) {
    pending_request->original_web_prefs =
        host_->render_view_host()->GetDelegate()->GetOrCreateWebPreferences(
            host_->render_view_host());
    const blink::web_pref::WebPreferences& original_web_prefs =
        *pending_request->original_web_prefs;
    blink::web_pref::WebPreferences modified_web_prefs = original_web_prefs;

    // Hiding scrollbar is needed to avoid scrollbar artefacts on the
    // screenshot. Details: https://crbug.com/1003629.
    modified_web_prefs.hide_scrollbars = true;
    modified_web_prefs.record_whole_document = true;
    host_->render_view_host()->GetDelegate()->SetWebPreferences(
        modified_web_prefs);

    {
      // TODO(crbug.com/40727379): Remove the bug is fixed.
      // Walkaround for the bug. Emulated `view_size` has to be set twice,
      // otherwise the scrollbar will be on the screenshot present.
      blink::DeviceEmulationParams tmp_params = modified_params;
      tmp_params.view_size = gfx::Size(1, 1);
      emulation_handler_->SetDeviceEmulationParams(tmp_params);
    }
  }

  // We use DeviceEmulationParams to either emulate, set viewport or both.
  emulation_handler_->SetDeviceEmulationParams(modified_params);

  // Set view size for the screenshot right after emulating.
  if (clip) {
    double scale = dpfactor * clip->GetScale();
    widget_host->GetView()->SetSize(
        gfx::Size(base::ClampRound(clip->GetWidth() * scale),
                  base::ClampRound(clip->GetHeight() * scale)));
  } else if (emulation_enabled) {
    widget_host->GetView()->SetSize(
        gfx::ScaleToFlooredSize(emulated_view_size, dpfactor));
  }
  if (emulation_enabled || clip) {
    const gfx::Size requested_image_size =
        clip ? gfx::Size(clip->GetWidth(), clip->GetHeight())
             : emulated_view_size;
    double scale = widget_host_device_scale_factor * dpfactor;
    if (clip) {
      scale *= clip->GetScale();
    }
    pending_request->requested_image_size =
        gfx::ScaleToRoundedSize(requested_image_size, scale);
  }

  // TODO(crbug.com/377715191): this should check RenderWidgetHostViewBase
  // instead, but there is no easy way to do this today. Once that's possible,
  // this check should move inside RenderWidetHostImpl::GetSnapshotFromBrowser.
  if (base::FeatureList::IsEnabled(features::kCDPScreenshotNewSurface)) {
    if (auto* wc = WebContentsImpl::FromRenderFrameHostImpl(host_)) {
      // When view is completely hidden, capturing a surface snapshot
      // will stall because the surface is never presented.
      CHECK(wc->GetPageVisibilityState() != PageVisibilityState::kHidden);
    }
  }

  widget_host->GetSnapshotFromBrowser(
      base::BindOnce(&PageHandler::ScreenshotCaptured,
                     weak_factory_.GetWeakPtr(), std::move(pending_request)),
      true);
}

Response PageHandler::StartScreencast(std::optional<std::string> format,
                                      std::optional<int> quality,
                                      std::optional<int> max_width,
                                      std::optional<int> max_height,
                                      std::optional<int> every_nth_frame) {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }
  RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
  if (!widget_host) {
    return Response::InternalError();
  }

  auto encoder =
      GetEncoder(format.value_or(Page::CaptureScreenshot::FormatEnum::Png),
                 quality.value_or(kDefaultScreenshotQuality),
                 /* optimize_for_speed= */ true);
  if (std::holds_alternative<Response>(encoder)) {
    return std::get<Response>(encoder);
  }

  screencast_encoder_ = std::get<BitmapEncoder>(encoder);

  screencast_max_width_ = max_width.value_or(-1);
  screencast_max_height_ = max_height.value_or(-1);
  ++session_id_;
  frame_counter_ = 0;
  frames_in_flight_ = 0;
  capture_every_nth_frame_ = every_nth_frame.value_or(1);
  bool visible = !widget_host->IsHidden();
  NotifyScreencastVisibility(visible);

  gfx::Size surface_size = gfx::Size();
  RenderWidgetHostViewBase* const view =
      static_cast<RenderWidgetHostViewBase*>(host_->GetView());
  if (view) {
    surface_size = view->GetCompositorViewportPixelSize();
    last_surface_size_ = surface_size;
  }

  gfx::Size snapshot_size = DetermineSnapshotSize(
      surface_size, screencast_max_width_, screencast_max_height_);
  if (!snapshot_size.IsEmpty()) {
    video_consumer_->SetMinAndMaxFrameSize(snapshot_size, snapshot_size);
  }

  video_consumer_->StartCapture();

  return Response::FallThrough();
}

Response PageHandler::StopScreencast() {
  screencast_encoder_.Reset();
  if (video_consumer_) {
    video_consumer_->StopCapture();
  }
  return Response::FallThrough();
}

Response PageHandler::ScreencastFrameAck(int session_id) {
  if (session_id == session_id_) {
    --frames_in_flight_;
  }
  return Response::Success();
}

Response PageHandler::HandleJavaScriptDialog(
    bool accept,
    std::optional<std::string> prompt_text) {
  ResponseOrWebContents result = GetWebContentsForTopLevelActiveFrame();
  if (std::holds_alternative<Response>(result)) {
    return std::get<Response>(result);
  }

  if (pending_dialog_.is_null()) {
    return Response::InvalidParams("No dialog is showing");
  }

  std::u16string prompt_override;
  if (prompt_text.has_value()) {
    prompt_override = base::UTF8ToUTF16(prompt_text.value());
  }
  std::move(pending_dialog_).Run(accept, prompt_override);

  // Clean up the dialog UI if any.
  WebContentsImpl* web_contents = std::get<WebContentsImpl*>(result);
  if (web_contents->GetDelegate()) {
    JavaScriptDialogManager* manager =
        web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents);
    if (manager) {
      manager->HandleJavaScriptDialog(
          web_contents, accept,
          prompt_text.has_value() ? &prompt_override : nullptr);
    }
  }

  return Response::Success();
}

Response PageHandler::BringToFront() {
  // Not using AssureTopLevelActiveFrame here because
  // we allow bringing WebContents to front that might have ongoing
  // lifecycle updates.
  if (!host_) {
    return Response::ServerError(kErrorNotAttached);
  }

  if (host_->GetParentOrOuterDocument()) {
    return Response::ServerError(kCommandIsOnlyAvailableAtTopTarget);
  }

  WebContentsImpl* web_contents =
      static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_));
  web_contents->Activate();
  web_contents->GetOutermostWebContents()->Focus();
  return Response::Success();
}

Response PageHandler::SetDownloadBehavior(
    const std::string& behavior,
    std::optional<std::string> download_path) {
  BrowserContext* browser_context =
      host_ ? host_->GetProcess()->GetBrowserContext() : nullptr;
  if (!browser_context) {
    return Response::ServerError("Could not fetch browser context");
  }

  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }
  if (!browser_handler_) {
    return Response::ServerError("Cannot not access browser-level commands");
  }
  return browser_handler_->DoSetDownloadBehavior(behavior, browser_context,
                                                 std::move(download_path));
}

void PageHandler::GetAppManifest(
    std::optional<std::string> manifest_id,
    std::unique_ptr<GetAppManifestCallback> callback) {
  if (!CanExecuteGlobalCommands(this, callback)) {
    return;
  }
  ManifestManagerHost::GetOrCreateForPage(host_->GetPage())
      ->RequestManifestDebugInfo(base::BindOnce(
          GotManifest, std::move(manifest_id), std::move(callback)));
}

PageHandler::ResponseOrWebContents
PageHandler::GetWebContentsForTopLevelActiveFrame() {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  return static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_));
}

void PageHandler::NotifyScreencastVisibility(bool visible) {
  frontend_->ScreencastVisibilityChanged(visible);
}

bool PageHandler::ShouldCaptureNextScreencastFrame() {
  return frames_in_flight_ <= kMaxScreencastFramesInFlight &&
         !(++frame_counter_ % capture_every_nth_frame_);
}

void PageHandler::OnFrameFromVideoConsumer(
    scoped_refptr<media::VideoFrame> frame) {
  if (!host_) {
    return;
  }

  if (!ShouldCaptureNextScreencastFrame()) {
    return;
  }

  RenderWidgetHostViewBase* const view =
      static_cast<RenderWidgetHostViewBase*>(host_->GetView());
  if (!view) {
    return;
  }

  const gfx::Size surface_size = view->GetCompositorViewportPixelSize();
  if (surface_size.IsEmpty()) {
    return;
  }

  // If window has been resized, set the new dimensions.
  if (surface_size != last_surface_size_) {
    last_surface_size_ = surface_size;
    gfx::Size snapshot_size = DetermineSnapshotSize(
        surface_size, screencast_max_width_, screencast_max_height_);
    if (!snapshot_size.IsEmpty()) {
      video_consumer_->SetMinAndMaxFrameSize(snapshot_size, snapshot_size);
    }
    return;
  }

  double device_scale_factor, page_scale_factor;
  double top_controls_visible_height;
  gfx::PointF root_scroll_offset;
  GetMetadataFromFrame(*frame, &device_scale_factor, &page_scale_factor,
                       &root_scroll_offset, &top_controls_visible_height);
  std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata =
      BuildScreencastFrameMetadata(surface_size, device_scale_factor,
                                   page_scale_factor, root_scroll_offset,
                                   top_controls_visible_height);
  if (!page_metadata) {
    return;
  }

  frames_in_flight_++;
  ScreencastFrameCaptured(std::move(page_metadata),
                          DevToolsVideoConsumer::GetSkBitmapFromFrame(frame));
}

void PageHandler::ScreencastFrameCaptured(
    std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata,
    const SkBitmap& bitmap) {
  if (bitmap.drawsNothing()) {
    --frames_in_flight_;
    return;
  }
  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(
          [](const SkBitmap& bitmap,
             BitmapEncoder encoder) -> std::optional<std::vector<uint8_t>> {
            return encoder.Run(bitmap);
          },
          bitmap, screencast_encoder_),
      base::BindOnce(&PageHandler::ScreencastFrameEncoded,
                     weak_factory_.GetWeakPtr(), std::move(page_metadata)));
}

void PageHandler::ScreencastFrameEncoded(
    std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata,
    std::optional<std::vector<uint8_t>> data) {
  if (!data) {
    --frames_in_flight_;
    return;  // Encode failed.
  }

  frontend_->ScreencastFrame(Binary::fromVector(std::move(data).value()),
                             std::move(page_metadata), session_id_);
}

void PageHandler::ScreenshotCaptured(
    std::unique_ptr<PendingScreenshotRequest> request,
    const gfx::Image& image) {
  if (request->original_view_size.width()) {
    RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost();
    widget_host->GetView()->SetSize(request->original_view_size);
    emulation_handler_->SetDeviceEmulationParams(
        request->original_emulation_params);
  }

  if (request->original_web_prefs) {
    host_->render_view_host()->GetDelegate()->SetWebPreferences(
        *request->original_web_prefs);
  }

  if (image.IsEmpty()) {
    request->callback->sendFailure(
        Response::ServerError("Unable to capture screenshot"));
    return;
  }

  std::optional<std::vector<uint8_t>> encoded_bitmap;
  const SkBitmap& bitmap = *image.ToSkBitmap();

  if (!request->requested_image_size.IsEmpty() &&
      (image.Width() != request->requested_image_size.width() ||
       image.Height() != request->requested_image_size.height())) {
    SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap(
        bitmap, 0, 0, request->requested_image_size.width(),
        request->requested_image_size.height());
    encoded_bitmap = request->encoder.Run(cropped);
  } else {
    encoded_bitmap = request->encoder.Run(bitmap);
  }

  if (encoded_bitmap) {
    request->callback->sendSuccess(
        Binary::fromVector(std::move(encoded_bitmap).value()));
    return;
  }
  // TODO(caseq): send failure if we fail to encode?
  request->callback->sendSuccess(Binary());
}

Response PageHandler::StopLoading() {
  ResponseOrWebContents result = GetWebContentsForTopLevelActiveFrame();

  if (std::holds_alternative<Response>(result)) {
    return std::get<Response>(result);
  }

  WebContentsImpl* web_contents = std::get<WebContentsImpl*>(result);
  web_contents->Stop();
  return Response::Success();
}

Response PageHandler::SetWebLifecycleState(const std::string& state) {
  // Inactive pages(e.g., a prerendered or back-forward cached page) should not
  // affect the state.
  ResponseOrWebContents result = GetWebContentsForTopLevelActiveFrame();
  if (std::holds_alternative<Response>(result)) {
    return std::get<Response>(result);
  }

  WebContentsImpl* web_contents = std::get<WebContentsImpl*>(result);
  if (state == Page::SetWebLifecycleState::StateEnum::Frozen) {
    // TODO(fmeawad): Instead of forcing a visibility change, only allow
    // freezing a page if it was already hidden.
    web_contents->WasHidden();
    web_contents->SetPageFrozen(true);
    return Response::Success();
  }
  if (state == Page::SetWebLifecycleState::StateEnum::Active) {
    web_contents->SetPageFrozen(false);
    return Response::Success();
  }
  return Response::ServerError("Unidentified lifecycle state");
}

void PageHandler::GetInstallabilityErrors(
    std::unique_ptr<GetInstallabilityErrorsCallback> callback) {
  auto installability_errors =
      std::make_unique<protocol::Array<Page::InstallabilityError>>();
  // TODO: Use InstallableManager once it moves into content/.
  // Until then, this code is only used to return empty array in the tests.
  callback->sendSuccess(std::move(installability_errors));
}

void PageHandler::GetManifestIcons(
    std::unique_ptr<GetManifestIconsCallback> callback) {
  // TODO: Use InstallableManager once it moves into content/.
  // Until then, this code is only used to return no image data in the tests.
  callback->sendSuccess(std::nullopt);
}

void PageHandler::GetAppId(std::unique_ptr<GetAppIdCallback> callback) {
  // TODO: Use InstallableManager once it moves into content/.
  // Until then, this code is only used to return no image data in the tests.
  callback->sendSuccess(std::nullopt, std::nullopt);
}

Response PageHandler::SetBypassCSP(bool enabled) {
  bypass_csp_ = enabled;
  return Response::FallThrough();
}

Page::BackForwardCacheNotRestoredReason NotRestoredReasonToProtocol(
    BackForwardCacheMetrics::NotRestoredReason reason) {
  using Reason = BackForwardCacheMetrics::NotRestoredReason;
  switch (reason) {
    case Reason::kNotPrimaryMainFrame:
      return Page::BackForwardCacheNotRestoredReasonEnum::NotPrimaryMainFrame;
    case Reason::kBackForwardCacheDisabled:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BackForwardCacheDisabled;
    case Reason::kRelatedActiveContentsExist:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RelatedActiveContentsExist;
    case Reason::kHTTPStatusNotOK:
      return Page::BackForwardCacheNotRestoredReasonEnum::HTTPStatusNotOK;
    case Reason::kSchemeNotHTTPOrHTTPS:
      return Page::BackForwardCacheNotRestoredReasonEnum::SchemeNotHTTPOrHTTPS;
    case Reason::kLoading:
      return Page::BackForwardCacheNotRestoredReasonEnum::Loading;
    case Reason::kDisableForRenderFrameHostCalled:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          DisableForRenderFrameHostCalled;
    case Reason::kDomainNotAllowed:
      return Page::BackForwardCacheNotRestoredReasonEnum::DomainNotAllowed;
    case Reason::kHTTPMethodNotGET:
      return Page::BackForwardCacheNotRestoredReasonEnum::HTTPMethodNotGET;
    case Reason::kSubframeIsNavigating:
      return Page::BackForwardCacheNotRestoredReasonEnum::SubframeIsNavigating;
    case Reason::kTimeout:
      return Page::BackForwardCacheNotRestoredReasonEnum::Timeout;
    case Reason::kCacheLimit:
      return Page::BackForwardCacheNotRestoredReasonEnum::CacheLimit;
    case Reason::kJavaScriptExecution:
      return Page::BackForwardCacheNotRestoredReasonEnum::JavaScriptExecution;
    case Reason::kRendererProcessKilled:
      return Page::BackForwardCacheNotRestoredReasonEnum::RendererProcessKilled;
    case Reason::kRendererProcessCrashed:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RendererProcessCrashed;
    case Reason::kConflictingBrowsingInstance:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          ConflictingBrowsingInstance;
    case Reason::kCacheFlushed:
      return Page::BackForwardCacheNotRestoredReasonEnum::CacheFlushed;
    case Reason::kServiceWorkerVersionActivation:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          ServiceWorkerVersionActivation;
    case Reason::kSessionRestored:
      return Page::BackForwardCacheNotRestoredReasonEnum::SessionRestored;
    case Reason::kServiceWorkerPostMessage:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          ServiceWorkerPostMessage;
    case Reason::kEnteredBackForwardCacheBeforeServiceWorkerHostAdded:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          EnteredBackForwardCacheBeforeServiceWorkerHostAdded;
    case Reason::kServiceWorkerClaim:
      return Page::BackForwardCacheNotRestoredReasonEnum::ServiceWorkerClaim;
    case Reason::kIgnoreEventAndEvict:
      return Page::BackForwardCacheNotRestoredReasonEnum::IgnoreEventAndEvict;
    case Reason::kHaveInnerContents:
      return Page::BackForwardCacheNotRestoredReasonEnum::HaveInnerContents;
    case Reason::kTimeoutPuttingInCache:
      return Page::BackForwardCacheNotRestoredReasonEnum::TimeoutPuttingInCache;
    case Reason::kBackForwardCacheDisabledByLowMemory:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BackForwardCacheDisabledByLowMemory;
    case Reason::kBackForwardCacheDisabledByCommandLine:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BackForwardCacheDisabledByCommandLine;
    case Reason::kNetworkRequestDatapipeDrainedAsBytesConsumer:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          NetworkRequestDatapipeDrainedAsBytesConsumer;
    case Reason::kNetworkRequestRedirected:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          NetworkRequestRedirected;
    case Reason::kNetworkRequestTimeout:
      return Page::BackForwardCacheNotRestoredReasonEnum::NetworkRequestTimeout;
    case Reason::kNetworkExceedsBufferLimit:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          NetworkExceedsBufferLimit;
    case Reason::kNavigationCancelledWhileRestoring:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          NavigationCancelledWhileRestoring;
    case Reason::kUserAgentOverrideDiffers:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          UserAgentOverrideDiffers;
    case Reason::kForegroundCacheLimit:
      return Page::BackForwardCacheNotRestoredReasonEnum::ForegroundCacheLimit;
    case Reason::kBrowsingInstanceNotSwapped:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BrowsingInstanceNotSwapped;
    case Reason::kBackForwardCacheDisabledForDelegate:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BackForwardCacheDisabledForDelegate;
    case Reason::kUnloadHandlerExistsInMainFrame:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          UnloadHandlerExistsInMainFrame;
    case Reason::kUnloadHandlerExistsInSubFrame:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          UnloadHandlerExistsInSubFrame;
    case Reason::kServiceWorkerUnregistration:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          ServiceWorkerUnregistration;
    case Reason::kCacheControlNoStore:
      return Page::BackForwardCacheNotRestoredReasonEnum::CacheControlNoStore;
    case Reason::kCacheControlNoStoreCookieModified:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          CacheControlNoStoreCookieModified;
    case Reason::kCacheControlNoStoreHTTPOnlyCookieModified:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          CacheControlNoStoreHTTPOnlyCookieModified;
    case Reason::kErrorDocument:
      return Page::BackForwardCacheNotRestoredReasonEnum::ErrorDocument;
    case Reason::kCookieDisabled:
      return Page::BackForwardCacheNotRestoredReasonEnum::CookieDisabled;
    case Reason::kHTTPAuthRequired:
      return Page::BackForwardCacheNotRestoredReasonEnum::HTTPAuthRequired;
    case Reason::kCookieFlushed:
      return Page::BackForwardCacheNotRestoredReasonEnum::CookieFlushed;
    case Reason::kBroadcastChannelOnMessage:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          BroadcastChannelOnMessage;
    case Reason::kWebViewSettingsChanged:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebViewSettingsChanged;
    case Reason::kWebViewJavaScriptObjectChanged:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebViewJavaScriptObjectChanged;
    case Reason::kWebViewMessageListenerInjected:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebViewMessageListenerInjected;
    case Reason::kWebViewSafeBrowsingAllowlistChanged:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebViewSafeBrowsingAllowlistChanged;
    case Reason::kWebViewDocumentStartJavascriptChanged:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebViewDocumentStartJavascriptChanged;
    case Reason::kCacheLimitPrunedOnModerateMemoryPressure:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          CacheLimitPrunedOnModerateMemoryPressure;
    case Reason::kCacheLimitPrunedOnCriticalMemoryPressure:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          CacheLimitPrunedOnCriticalMemoryPressure;
    case Reason::kBlocklistedFeatures:
      // Blocklisted features should be handled separately and be broken down
      // into sub reasons.
      NOTREACHED();
    case Reason::kUnknown:
      return Page::BackForwardCacheNotRestoredReasonEnum::Unknown;
    case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          CacheControlNoStoreDeviceBoundSessionTerminated;
    case Reason::kSharedWorkerMessage:
      return Page::BackForwardCacheNotRestoredReasonEnum::SharedWorkerMessage;
    case Reason::kSharedWorkerWithNoActiveClient:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          SharedWorkerWithNoActiveClient;
  }
}

using blink::scheduler::WebSchedulerTrackedFeature;
Page::BackForwardCacheNotRestoredReason BlocklistedFeatureToProtocol(
    WebSchedulerTrackedFeature feature) {
  switch (feature) {
    case WebSchedulerTrackedFeature::kWebSocket:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebSocket;
    case WebSchedulerTrackedFeature::kWebSocketSticky:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebSocketUsedWithCCNS;
    case WebSchedulerTrackedFeature::kWebTransport:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebTransport;
    case WebSchedulerTrackedFeature::kWebTransportSticky:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          WebTransportUsedWithCCNS;
    case WebSchedulerTrackedFeature::kWebRTC:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebRTC;
    case WebSchedulerTrackedFeature::kWebRTCSticky:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebRTCUsedWithCCNS;
    case WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoCache:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          MainResourceHasCacheControlNoCache;
    case WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoStore:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          MainResourceHasCacheControlNoStore;
    case WebSchedulerTrackedFeature::kSubresourceHasCacheControlNoCache:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          SubresourceHasCacheControlNoCache;
    case WebSchedulerTrackedFeature::kSubresourceHasCacheControlNoStore:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          SubresourceHasCacheControlNoStore;
    case WebSchedulerTrackedFeature::kContainsPlugins:
      return Page::BackForwardCacheNotRestoredReasonEnum::ContainsPlugins;
    case WebSchedulerTrackedFeature::kDocumentLoaded:
      return Page::BackForwardCacheNotRestoredReasonEnum::DocumentLoaded;
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestOthers:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          OutstandingNetworkRequestOthers;
    case WebSchedulerTrackedFeature::kRequestedMIDIPermission:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedMIDIPermission;
    case WebSchedulerTrackedFeature::kRequestedAudioCapturePermission:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedAudioCapturePermission;
    case WebSchedulerTrackedFeature::kRequestedVideoCapturePermission:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedVideoCapturePermission;
    case WebSchedulerTrackedFeature::kRequestedBackForwardCacheBlockedSensors:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedBackForwardCacheBlockedSensors;
    case WebSchedulerTrackedFeature::kRequestedBackgroundWorkPermission:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedBackgroundWorkPermission;
    case WebSchedulerTrackedFeature::kBroadcastChannel:
      return Page::BackForwardCacheNotRestoredReasonEnum::BroadcastChannel;
    case WebSchedulerTrackedFeature::kWebXR:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebXR;
    case WebSchedulerTrackedFeature::kSharedWorker:
      return Page::BackForwardCacheNotRestoredReasonEnum::SharedWorker;
    case WebSchedulerTrackedFeature::kWebLocks:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebLocks;
    case WebSchedulerTrackedFeature::kWebHID:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebHID;
    case WebSchedulerTrackedFeature::kWebShare:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebShare;
    case WebSchedulerTrackedFeature::kRequestedStorageAccessGrant:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          RequestedStorageAccessGrant;
    case WebSchedulerTrackedFeature::kWebNfc:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebNfc;
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestFetch:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          OutstandingNetworkRequestFetch;
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          OutstandingNetworkRequestXHR;
    case WebSchedulerTrackedFeature::kPrinting:
      return Page::BackForwardCacheNotRestoredReasonEnum::Printing;
    case WebSchedulerTrackedFeature::kPictureInPicture:
      return Page::BackForwardCacheNotRestoredReasonEnum::PictureInPicture;
    case WebSchedulerTrackedFeature::kSpeechRecognizer:
      return Page::BackForwardCacheNotRestoredReasonEnum::SpeechRecognizer;
    case WebSchedulerTrackedFeature::kIdleManager:
      return Page::BackForwardCacheNotRestoredReasonEnum::IdleManager;
    case WebSchedulerTrackedFeature::kPaymentManager:
      return Page::BackForwardCacheNotRestoredReasonEnum::PaymentManager;
    case WebSchedulerTrackedFeature::kKeyboardLock:
      return Page::BackForwardCacheNotRestoredReasonEnum::KeyboardLock;
    case WebSchedulerTrackedFeature::kWebOTPService:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebOTPService;
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestDirectSocket:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          OutstandingNetworkRequestDirectSocket;
    case WebSchedulerTrackedFeature::kInjectedJavascript:
      return Page::BackForwardCacheNotRestoredReasonEnum::InjectedJavascript;
    case WebSchedulerTrackedFeature::kInjectedStyleSheet:
      return Page::BackForwardCacheNotRestoredReasonEnum::InjectedStyleSheet;
    case WebSchedulerTrackedFeature::kKeepaliveRequest:
      return Page::BackForwardCacheNotRestoredReasonEnum::KeepaliveRequest;
    case WebSchedulerTrackedFeature::kIndexedDBEvent:
      return Page::BackForwardCacheNotRestoredReasonEnum::IndexedDBEvent;
    case WebSchedulerTrackedFeature::kDummy:
      // This is a test only reason and should never be called.
      NOTREACHED();
    case WebSchedulerTrackedFeature::
        kJsNetworkRequestReceivedCacheControlNoStoreResource:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          JsNetworkRequestReceivedCacheControlNoStoreResource;
    case WebSchedulerTrackedFeature::kWebSerial:
      // These features only disable aggressive throttling.
      NOTREACHED();
    case WebSchedulerTrackedFeature::kSmartCard:
      return Page::BackForwardCacheNotRestoredReasonEnum::SmartCard;
    case WebSchedulerTrackedFeature::kLiveMediaStreamTrack:
      return Page::BackForwardCacheNotRestoredReasonEnum::LiveMediaStreamTrack;
    case WebSchedulerTrackedFeature::kUnloadHandler:
      return Page::BackForwardCacheNotRestoredReasonEnum::UnloadHandler;
    case WebSchedulerTrackedFeature::kParserAborted:
      return Page::BackForwardCacheNotRestoredReasonEnum::ParserAborted;
#if BUILDFLAG(ARKWEB_BFCACHE)
    case WebSchedulerTrackedFeature::kEnableCacheNativeEmbed:
      return "EnableCacheNativeEmbed";
    case WebSchedulerTrackedFeature::kEnableCacheMediaTakeOver:
      return "EnableCacheMediaTakeOver";
#endif
    case WebSchedulerTrackedFeature::kWebAuthentication:
      return Page::BackForwardCacheNotRestoredReasonEnum::
          ContentWebAuthenticationAPI;
    case WebSchedulerTrackedFeature::kSharedWorkerMessage:
      return Page::BackForwardCacheNotRestoredReasonEnum::SharedWorkerMessage;
    case WebSchedulerTrackedFeature::kWebBluetooth:
      return Page::BackForwardCacheNotRestoredReasonEnum::WebBluetooth;
  }
}

std::unique_ptr<Page::BackForwardCacheBlockingDetails> SourceLocationToProtocol(
    const blink::mojom::ScriptSourceLocationPtr& source) {
  auto blocking_details = Page::BackForwardCacheBlockingDetails::Create();
  if (!source->url.is_empty()) {
    blocking_details.SetUrl(source->url.spec());
  }
  if (!source->function_name.empty()) {
    blocking_details.SetFunction(source->function_name);
  }
  CHECK(source->line_number > 0);
  CHECK(source->column_number > 0);
  return blocking_details.SetLineNumber(source->line_number - 1)
      .SetColumnNumber(source->column_number - 1)
      .Build();
}

Page::BackForwardCacheNotRestoredReason
DisableForRenderFrameHostReasonToProtocol(
    BackForwardCache::DisabledReason reason) {
  switch (reason.source) {
    case BackForwardCache::DisabledSource::kLegacy:
      NOTREACHED();
    case BackForwardCache::DisabledSource::kTesting:
      NOTREACHED();
    case BackForwardCache::DisabledSource::kContent:
      switch (
          static_cast<BackForwardCacheDisable::DisabledReasonId>(reason.id)) {
        case BackForwardCacheDisable::DisabledReasonId::kUnknown:
          return Page::BackForwardCacheNotRestoredReasonEnum::Unknown;
        case BackForwardCacheDisable::DisabledReasonId::kSecurityHandler:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentSecurityHandler;
        case BackForwardCacheDisable::DisabledReasonId::kWebAuthenticationAPI:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentWebAuthenticationAPI;
        case BackForwardCacheDisable::DisabledReasonId::kFileChooser:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentFileChooser;
        case BackForwardCacheDisable::DisabledReasonId::kSerial:
          return Page::BackForwardCacheNotRestoredReasonEnum::ContentSerial;
        case BackForwardCacheDisable::DisabledReasonId::
            kMediaDevicesDispatcherHost:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentMediaDevicesDispatcherHost;
        case BackForwardCacheDisable::DisabledReasonId::kWebBluetooth:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentWebBluetooth;
        case BackForwardCacheDisable::DisabledReasonId::kWebUSB:
          return Page::BackForwardCacheNotRestoredReasonEnum::ContentWebUSB;
        case BackForwardCacheDisable::DisabledReasonId::kMediaSessionService:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentMediaSessionService;
        case BackForwardCacheDisable::DisabledReasonId::kExtendedProperties:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              ContentScreenReader;
        case BackForwardCacheDisable::DisabledReasonId::kDiscarded:
          return Page::BackForwardCacheNotRestoredReasonEnum::ContentDiscarded;
      }
    case BackForwardCache::DisabledSource::kEmbedder:
      switch (static_cast<back_forward_cache::DisabledReasonId>(reason.id)) {
        case back_forward_cache::DisabledReasonId::kUnknown:
          return Page::BackForwardCacheNotRestoredReasonEnum::Unknown;
        case back_forward_cache::DisabledReasonId::kPopupBlockerTabHelper:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderPopupBlockerTabHelper;
        case back_forward_cache::DisabledReasonId::
            kSafeBrowsingTriggeredPopupBlocker:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderSafeBrowsingTriggeredPopupBlocker;
        case back_forward_cache::DisabledReasonId::kSafeBrowsingThreatDetails:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderSafeBrowsingThreatDetails;
        case back_forward_cache::DisabledReasonId::kDomDistillerViewerSource:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderDomDistillerViewerSource;
        case back_forward_cache::DisabledReasonId::
            kDomDistiller_SelfDeletingRequestDelegate:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderDomDistillerSelfDeletingRequestDelegate;
        case back_forward_cache::DisabledReasonId::kOomInterventionTabHelper:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderOomInterventionTabHelper;
        case back_forward_cache::DisabledReasonId::kOfflinePage:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderOfflinePage;
        case back_forward_cache::DisabledReasonId::
            kContentCredentialManager_BindCredentialManager:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderChromePasswordManagerClientBindCredentialManager;
        case back_forward_cache::DisabledReasonId::kPermissionRequestManager:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderPermissionRequestManager;
        case back_forward_cache::DisabledReasonId::kModalDialog:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderModalDialog;
        case back_forward_cache::DisabledReasonId::kExtensionMessaging:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderExtensionMessaging;
        case back_forward_cache::DisabledReasonId::
            kExtensionSentMessageToCachedFrame:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              EmbedderExtensionSentMessageToCachedFrame;
        case back_forward_cache::DisabledReasonId::kRequestedByWebViewClient:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              RequestedByWebViewClient;
        case back_forward_cache::DisabledReasonId::kPostMessageByWebViewClient:
          return Page::BackForwardCacheNotRestoredReasonEnum::
              PostMessageByWebViewClient;
      }
  }
}

Page::BackForwardCacheNotRestoredReasonType MapNotRestoredReasonToType(
    BackForwardCacheMetrics::NotRestoredReason reason) {
  using Reason = BackForwardCacheMetrics::NotRestoredReason;
  switch (reason) {
    case Reason::kNotPrimaryMainFrame:
    case Reason::kBackForwardCacheDisabled:
    case Reason::kRelatedActiveContentsExist:
    case Reason::kHTTPStatusNotOK:
    case Reason::kSchemeNotHTTPOrHTTPS:
    case Reason::kLoading:
    case Reason::kDisableForRenderFrameHostCalled:
    case Reason::kDomainNotAllowed:
    case Reason::kHTTPMethodNotGET:
    case Reason::kSubframeIsNavigating:
    case Reason::kTimeout:
    case Reason::kCacheLimit:
    case Reason::kJavaScriptExecution:
    case Reason::kRendererProcessKilled:
    case Reason::kRendererProcessCrashed:
    case Reason::kConflictingBrowsingInstance:
    case Reason::kCacheFlushed:
    case Reason::kServiceWorkerVersionActivation:
    case Reason::kSessionRestored:
    case Reason::kServiceWorkerPostMessage:
    case Reason::kEnteredBackForwardCacheBeforeServiceWorkerHostAdded:
    case Reason::kServiceWorkerClaim:
    case Reason::kIgnoreEventAndEvict:
    case Reason::kHaveInnerContents:
    case Reason::kTimeoutPuttingInCache:
    case Reason::kBackForwardCacheDisabledByLowMemory:
    case Reason::kBackForwardCacheDisabledByCommandLine:
    case Reason::kNetworkRequestRedirected:
    case Reason::kNetworkRequestTimeout:
    case Reason::kNetworkExceedsBufferLimit:
    case Reason::kNavigationCancelledWhileRestoring:
    case Reason::kForegroundCacheLimit:
    case Reason::kUserAgentOverrideDiffers:
    case Reason::kBrowsingInstanceNotSwapped:
    case Reason::kBackForwardCacheDisabledForDelegate:
    case Reason::kServiceWorkerUnregistration:
    case Reason::kErrorDocument:
    case Reason::kCookieDisabled:
    case Reason::kHTTPAuthRequired:
    case Reason::kCookieFlushed:
    case Reason::kBroadcastChannelOnMessage:
    case Reason::kWebViewSettingsChanged:
    case Reason::kWebViewJavaScriptObjectChanged:
    case Reason::kWebViewMessageListenerInjected:
    case Reason::kWebViewSafeBrowsingAllowlistChanged:
    case Reason::kWebViewDocumentStartJavascriptChanged:
    case Reason::kCacheLimitPrunedOnModerateMemoryPressure:
    case Reason::kCacheLimitPrunedOnCriticalMemoryPressure:
    case Reason::kSharedWorkerMessage:
    case Reason::kSharedWorkerWithNoActiveClient:
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::Circumstantial;
    case Reason::kCacheControlNoStore:
    case Reason::kCacheControlNoStoreCookieModified:
    case Reason::kCacheControlNoStoreHTTPOnlyCookieModified:
    case Reason::kUnloadHandlerExistsInMainFrame:
    case Reason::kUnloadHandlerExistsInSubFrame:
    case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::PageSupportNeeded;
    case Reason::kNetworkRequestDatapipeDrainedAsBytesConsumer:
    case Reason::kUnknown:
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::SupportPending;
    case Reason::kBlocklistedFeatures:
      NOTREACHED();
  }
}

Page::BackForwardCacheNotRestoredReasonType MapBlocklistedFeatureToType(
    WebSchedulerTrackedFeature feature) {
  switch (feature) {
    case WebSchedulerTrackedFeature::kWebRTC:
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestOthers:
    case WebSchedulerTrackedFeature::kBroadcastChannel:
    case WebSchedulerTrackedFeature::kWebXR:
    case WebSchedulerTrackedFeature::kSharedWorker:
    case WebSchedulerTrackedFeature::kSharedWorkerMessage:
    case WebSchedulerTrackedFeature::kWebHID:
    case WebSchedulerTrackedFeature::kWebShare:
    case WebSchedulerTrackedFeature::kPaymentManager:
    case WebSchedulerTrackedFeature::kKeyboardLock:
    case WebSchedulerTrackedFeature::kWebOTPService:
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestDirectSocket:
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestFetch:
    case WebSchedulerTrackedFeature::kOutstandingNetworkRequestXHR:
    case WebSchedulerTrackedFeature::kWebTransport:
    case WebSchedulerTrackedFeature::kIndexedDBEvent:
    case WebSchedulerTrackedFeature::kSmartCard:
    case WebSchedulerTrackedFeature::kLiveMediaStreamTrack:
    case WebSchedulerTrackedFeature::kUnloadHandler:
    case WebSchedulerTrackedFeature::kParserAborted:
#if BUILDFLAG(ARKWEB_BFCACHE)
    case WebSchedulerTrackedFeature::kEnableCacheNativeEmbed:
    case WebSchedulerTrackedFeature::kEnableCacheMediaTakeOver:
#endif
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::PageSupportNeeded;
    case WebSchedulerTrackedFeature::kWebNfc:
    case WebSchedulerTrackedFeature::kRequestedStorageAccessGrant:
    case WebSchedulerTrackedFeature::kRequestedMIDIPermission:
    case WebSchedulerTrackedFeature::kRequestedAudioCapturePermission:
    case WebSchedulerTrackedFeature::kRequestedVideoCapturePermission:
    case WebSchedulerTrackedFeature::kRequestedBackForwardCacheBlockedSensors:
    case WebSchedulerTrackedFeature::kRequestedBackgroundWorkPermission:
    case WebSchedulerTrackedFeature::kContainsPlugins:
    case WebSchedulerTrackedFeature::kIdleManager:
    case WebSchedulerTrackedFeature::kSpeechRecognizer:
    case WebSchedulerTrackedFeature::kPrinting:
    case WebSchedulerTrackedFeature::kPictureInPicture:
    case WebSchedulerTrackedFeature::kWebLocks:
    case WebSchedulerTrackedFeature::kWebSocket:
    case WebSchedulerTrackedFeature::kKeepaliveRequest:
    case WebSchedulerTrackedFeature::kWebAuthentication:
    case WebSchedulerTrackedFeature::kWebBluetooth:
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::SupportPending;
    case WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoStore:
    case WebSchedulerTrackedFeature::kMainResourceHasCacheControlNoCache:
    case WebSchedulerTrackedFeature::kSubresourceHasCacheControlNoCache:
    case WebSchedulerTrackedFeature::kSubresourceHasCacheControlNoStore:
    case WebSchedulerTrackedFeature::kInjectedStyleSheet:
    case WebSchedulerTrackedFeature::kInjectedJavascript:
    case WebSchedulerTrackedFeature::kDocumentLoaded:
    case WebSchedulerTrackedFeature::kDummy:
    case WebSchedulerTrackedFeature::
        kJsNetworkRequestReceivedCacheControlNoStoreResource:
    case WebSchedulerTrackedFeature::kWebRTCSticky:
    case WebSchedulerTrackedFeature::kWebTransportSticky:
    case WebSchedulerTrackedFeature::kWebSocketSticky:
      return Page::BackForwardCacheNotRestoredReasonTypeEnum::Circumstantial;
    case WebSchedulerTrackedFeature::kWebSerial:
      NOTREACHED();
  }
}

Page::BackForwardCacheNotRestoredReasonType
MapDisableForRenderFrameHostReasonToType(
    BackForwardCache::DisabledReason reason) {
  return Page::BackForwardCacheNotRestoredReasonTypeEnum::SupportPending;
}

using BlockingDetailsMap =
    std::map<blink::scheduler::WebSchedulerTrackedFeature,
             std::vector<blink::mojom::BlockingDetailsPtr>>;

std::unique_ptr<protocol::Array<Page::BackForwardCacheNotRestoredExplanation>>
CreateNotRestoredExplanation(
    const BackForwardCacheCanStoreDocumentResult::NotRestoredReasons
        not_restored_reasons,
    const blink::scheduler::WebSchedulerTrackedFeatures blocklisted_features,
    const BackForwardCacheCanStoreDocumentResult::DisabledReasonsMap&
        disabled_reasons,
    const BlockingDetailsMap& details) {
  auto reasons = std::make_unique<
      protocol::Array<Page::BackForwardCacheNotRestoredExplanation>>();

  for (BackForwardCacheMetrics::NotRestoredReason not_restored_reason :
       not_restored_reasons) {
    if (not_restored_reason ==
        BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures) {
      DCHECK(!blocklisted_features.empty());
      for (blink::scheduler::WebSchedulerTrackedFeature feature :
           blocklisted_features) {
        // Details are not always present for blocklisted features, because the
        // number of details reported is limited.
        auto details_list = std::make_unique<
            protocol::Array<Page::BackForwardCacheBlockingDetails>>();
        auto details_it = details.find(feature);
        CHECK(details_it != details.end());
        for (const auto& detail : details_it->second) {
          if (detail->source) {
            details_list->push_back(SourceLocationToProtocol(detail->source));
          }
        }
        auto explanation =
            Page::BackForwardCacheNotRestoredExplanation::Create()
                .SetType(MapBlocklistedFeatureToType(feature))
                .SetReason(BlocklistedFeatureToProtocol(feature))
                .Build();

        if (!details_list->empty()) {
          explanation->SetDetails(std::move(details_list));
        }

        reasons->emplace_back(std::move(explanation));
      }
    } else if (not_restored_reason ==
               BackForwardCacheMetrics::NotRestoredReason::
                   kDisableForRenderFrameHostCalled) {
      for (const auto& [disabled_reason, _] : disabled_reasons) {
        auto reason =
            Page::BackForwardCacheNotRestoredExplanation::Create()
                .SetType(
                    MapDisableForRenderFrameHostReasonToType(disabled_reason))
                .SetReason(
                    DisableForRenderFrameHostReasonToProtocol(disabled_reason))
                .Build();
        if (!disabled_reason.context.empty()) {
          reason->SetContext(disabled_reason.context);
        }
        reasons->emplace_back(std::move(reason));
      }
    } else {
      reasons->emplace_back(
          Page::BackForwardCacheNotRestoredExplanation::Create()
              .SetType(MapNotRestoredReasonToType(not_restored_reason))
              .SetReason(NotRestoredReasonToProtocol(not_restored_reason))
              .Build());
    }
  }
  return reasons;
}

std::unique_ptr<Page::BackForwardCacheNotRestoredExplanationTree>
CreateNotRestoredExplanationTree(
    const BackForwardCacheCanStoreTreeResult& tree_result) {
  auto explanation = CreateNotRestoredExplanation(
      tree_result.GetDocumentResult().not_restored_reasons(),
      tree_result.GetDocumentResult().blocklisted_features(),
      tree_result.GetDocumentResult().disabled_reasons(),
      tree_result.GetDocumentResult().blocking_details_map());

  auto children_array = std::make_unique<
      protocol::Array<Page::BackForwardCacheNotRestoredExplanationTree>>();
  for (auto& child : tree_result.GetChildren()) {
    children_array->emplace_back(
        CreateNotRestoredExplanationTree(*(child.get())));
  }
  return Page::BackForwardCacheNotRestoredExplanationTree::Create()
      .SetUrl(tree_result.GetUrl().spec())
      .SetExplanations(std::move(explanation))
      .SetChildren(std::move(children_array))
      .Build();
}

Response PageHandler::AddCompilationCache(const std::string& url,
                                          const Binary& data) {
  // We're just checking a permission here, the real business happens
  // in the renderer, if we fall through.
  if (allow_unsafe_operations_) {
    return Response::FallThrough();
  }
  return Response::ServerError("Permission denied");
}

void PageHandler::IsPrerenderingAllowed(bool& is_allowed) {
  is_allowed &= is_prerendering_allowed_;
}

void PageHandler::ReadyToCommitNavigation(
    NavigationRequest* navigation_request) {
  if (navigation_request->GetReloadType() == ReloadType::NONE) {
    // Disregard pending reload if the navigation being committed is not a
    // reload.
    have_pending_reload_ = false;
    pending_script_to_evaluate_on_load_.clear();
  } else if (have_pending_reload_) {
    prepare_for_reload_callback_.Run(
        std::move(pending_script_to_evaluate_on_load_));
    have_pending_reload_ = false;
  }
}

Response PageHandler::SetPrerenderingAllowed(bool is_allowed) {
  Response response = AssureTopLevelActiveFrame();
  if (response.IsError()) {
    return response;
  }

  is_prerendering_allowed_ = is_allowed;

  return Response::Success();
}

Response PageHandler::AssureTopLevelActiveFrame() {
  if (!host_) {
    return Response::ServerError(kErrorNotAttached);
  }

  if (host_->GetParentOrOuterDocument()) {
    return Response::ServerError(kCommandIsOnlyAvailableAtTopTarget);
  }

  if (!host_->IsActive()) {
    return Response::ServerError(kErrorInactivePage);
  }

  return Response::Success();
}

void PageHandler::BackForwardCacheNotUsed(
    const NavigationRequest* navigation,
    const BackForwardCacheCanStoreDocumentResult* result,
    const BackForwardCacheCanStoreTreeResult* tree_result) {
  if (!enabled_) {
    return;
  }

  FrameTreeNode* ftn = navigation->frame_tree_node();
  std::string devtools_navigation_token =
      navigation->devtools_navigation_token().ToString();
  std::string frame_id =
      ftn->current_frame_host()->devtools_frame_token().ToString();

  auto explanation = CreateNotRestoredExplanation(
      result->not_restored_reasons(), result->blocklisted_features(),
      result->disabled_reasons(), result->blocking_details_map());

  // TODO(crbug.com/40812472): |tree_result| should not be nullptr when |result|
  // has the reasons.
  std::unique_ptr<Page::BackForwardCacheNotRestoredExplanationTree>
      explanation_tree =
          tree_result ? CreateNotRestoredExplanationTree(*tree_result)
                      : nullptr;
  frontend_->BackForwardCacheNotUsed(devtools_navigation_token, frame_id,
                                     std::move(explanation),
                                     std::move(explanation_tree));
}

bool PageHandler::ShouldBypassCSP() {
  return enabled_ && bypass_csp_;
}

}  // namespace protocol
}  // namespace content