blob: c0155c1e741d1d091141519376fd861b2da312a7 [file] [log] [blame]
// 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/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
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;
#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) && !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);
}
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->is_hidden();
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;
}
}
using blink::scheduler::WebSchedulerTrackedFeature;
Page::BackForwardCacheNotRestoredReason BlocklistedFeatureToProtocol(
WebSchedulerTrackedFeature feature) {
switch (feature) {
case WebSchedulerTrackedFeature::kWebSocket:
return Page::BackForwardCacheNotRestoredReasonEnum::WebSocket;
case WebSchedulerTrackedFeature::kWebSocketSticky:
return Page::BackForwardCacheNotRestoredReasonEnum::WebSocketSticky;
case WebSchedulerTrackedFeature::kWebTransport:
return Page::BackForwardCacheNotRestoredReasonEnum::WebTransport;
case WebSchedulerTrackedFeature::kWebTransportSticky:
return Page::BackForwardCacheNotRestoredReasonEnum::WebTransportSticky;
case WebSchedulerTrackedFeature::kWebRTC:
return Page::BackForwardCacheNotRestoredReasonEnum::WebRTC;
case WebSchedulerTrackedFeature::kWebRTCSticky:
return Page::BackForwardCacheNotRestoredReasonEnum::WebRTCSticky;
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:
case WebSchedulerTrackedFeature::kWebBluetooth:
// 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;
case WebSchedulerTrackedFeature::kWebAuthentication:
return Page::BackForwardCacheNotRestoredReasonEnum::
ContentWebAuthenticationAPI;
case WebSchedulerTrackedFeature::kSharedWorkerMessage:
return Page::BackForwardCacheNotRestoredReasonEnum::SharedWorkerMessage;
}
}
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:
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:
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:
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:
case WebSchedulerTrackedFeature::kWebBluetooth:
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>>();
CHECK(details.contains(feature));
for (const auto& detail : details.at(feature)) {
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