| // 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 |