| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // 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 <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/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/single_thread_task_runner.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.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/frame_host/navigation_request.h" |
| #include "content/browser/frame_host/navigator.h" |
| #include "content/browser/manifest/manifest_manager_host.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/common/widget_messages.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_delegate.h" |
| #include "content/public/common/referrer.h" |
| #include "content/public/common/result_codes.h" |
| #include "content/public/common/use_zoom_for_dsf_policy.h" |
| #include "net/base/filename_util.h" |
| #include "third_party/skia/include/core/SkBitmap.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/geometry/size_conversions.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_util.h" |
| #include "ui/gfx/skbitmap_operations.h" |
| #include "ui/snapshot/snapshot.h" |
| |
| #ifdef OS_ANDROID |
| #include "content/browser/renderer_host/compositor_impl_android.h" |
| #endif |
| |
| namespace content { |
| namespace protocol { |
| |
| namespace { |
| |
| constexpr const char* kMhtml = "mhtml"; |
| constexpr const char* kPng = "png"; |
| constexpr const char* kJpeg = "jpeg"; |
| constexpr int kDefaultScreenshotQuality = 80; |
| constexpr int kFrameRetryDelayMs = 100; |
| constexpr int kCaptureRetryLimit = 2; |
| constexpr int kMaxScreencastFramesInFlight = 2; |
| |
| Binary EncodeImage(const gfx::Image& image, |
| const std::string& format, |
| int quality) { |
| DCHECK(!image.IsEmpty()); |
| |
| scoped_refptr<base::RefCountedMemory> data; |
| if (format == kPng) { |
| data = image.As1xPNGBytes(); |
| } else if (format == kJpeg) { |
| scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes()); |
| if (gfx::JPEG1xEncodedDataFromImage(image, quality, &bytes->data())) |
| data = bytes; |
| } |
| |
| if (!data || !data->front()) |
| return protocol::Binary(); |
| |
| return Binary::fromRefCounted(data); |
| } |
| |
| Binary EncodeSkBitmap(const SkBitmap& image, |
| const std::string& format, |
| int quality) { |
| return EncodeImage(gfx::Image::CreateFrom1xBitmap(image), format, quality); |
| } |
| |
| std::unique_ptr<Page::ScreencastFrameMetadata> BuildScreencastFrameMetadata( |
| const gfx::Size& surface_size, |
| float device_scale_factor, |
| float page_scale_factor, |
| const gfx::Vector2dF& 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::Vector2dF root_scroll_offset_dip = root_scroll_offset; |
| if (IsUseZoomForDSFEnabled()) { |
| 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().ToDoubleT()) |
| .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::Vector2dF* 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; |
| } |
| |
| } // namespace |
| |
| PageHandler::PageHandler(EmulationHandler* emulation_handler, |
| BrowserHandler* browser_handler, |
| bool allow_file_access) |
| : DevToolsDomainHandler(Page::Metainfo::domainName), |
| enabled_(false), |
| screencast_enabled_(false), |
| screencast_quality_(kDefaultScreenshotQuality), |
| screencast_max_width_(-1), |
| screencast_max_height_(-1), |
| capture_every_nth_frame_(1), |
| capture_retry_count_(0), |
| session_id_(0), |
| frame_counter_(0), |
| frames_in_flight_(0), |
| video_consumer_(nullptr), |
| last_surface_size_(gfx::Size()), |
| host_(nullptr), |
| emulation_handler_(emulation_handler), |
| browser_handler_(browser_handler) { |
| bool create_video_consumer = true; |
| #ifdef OS_ANDROID |
| // Video capture doesn't work on Android WebView. Use CopyFromSurface instead. |
| if (!CompositorImpl::IsInitialized()) |
| create_video_consumer = false; |
| #endif |
| if (create_video_consumer) { |
| video_consumer_ = std::make_unique<DevToolsVideoConsumer>( |
| base::BindRepeating(&PageHandler::OnFrameFromVideoConsumer, |
| weak_factory_.GetWeakPtr())); |
| } |
| 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 && observer_.IsObserving(widget_host)) |
| observer_.Remove(widget_host); |
| |
| host_ = frame_host; |
| widget_host = host_ ? host_->GetRenderWidgetHost() : nullptr; |
| |
| if (widget_host) |
| observer_.Add(widget_host); |
| |
| if (video_consumer_ && frame_host) { |
| video_consumer_->SetFrameSinkId( |
| frame_host->GetRenderWidgetHost()->GetFrameSinkId()); |
| } |
| } |
| |
| void PageHandler::Wire(UberDispatcher* dispatcher) { |
| frontend_.reset(new Page::Frontend(dispatcher->channel())); |
| Page::Dispatcher::wire(dispatcher, this); |
| } |
| |
| void PageHandler::OnSynchronousSwapCompositorFrame( |
| const cc::RenderFrameMetadata& frame_metadata) { |
| // Cache |frame_metadata_| as InnerSwapCompositorFrame may also be called on |
| // screencast start. |
| frame_metadata_ = frame_metadata; |
| if (screencast_enabled_) |
| InnerSwapCompositorFrame(); |
| } |
| |
| void PageHandler::RenderWidgetHostVisibilityChanged( |
| RenderWidgetHost* widget_host, |
| bool became_visible) { |
| if (!screencast_enabled_) |
| return; |
| NotifyScreencastVisibility(became_visible); |
| } |
| |
| void PageHandler::RenderWidgetHostDestroyed(RenderWidgetHost* widget_host) { |
| observer_.Remove(widget_host); |
| } |
| |
| void PageHandler::DidAttachInterstitialPage() { |
| if (!enabled_) |
| return; |
| frontend_->InterstitialShown(); |
| } |
| |
| void PageHandler::DidDetachInterstitialPage() { |
| if (!enabled_) |
| return; |
| frontend_->InterstitialHidden(); |
| } |
| |
| void PageHandler::DidRunJavaScriptDialog(const GURL& url, |
| const base::string16& message, |
| const base::string16& 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(), base::UTF16ToUTF8(message), |
| type, has_non_devtools_handlers, |
| base::UTF16ToUTF8(default_prompt)); |
| } |
| |
| void PageHandler::DidRunBeforeUnloadConfirm(const GURL& url, |
| bool has_non_devtools_handlers, |
| JavaScriptDialogCallback callback) { |
| if (!enabled_) |
| return; |
| DCHECK(pending_dialog_.is_null()); |
| pending_dialog_ = std::move(callback); |
| frontend_->JavascriptDialogOpening(url.spec(), std::string(), |
| Page::DialogTypeEnum::Beforeunload, |
| has_non_devtools_handlers, std::string()); |
| } |
| |
| void PageHandler::DidCloseJavaScriptDialog(bool success, |
| const base::string16& user_input) { |
| if (!enabled_) |
| return; |
| pending_dialog_.Reset(); |
| frontend_->JavascriptDialogClosed(success, base::UTF16ToUTF8(user_input)); |
| } |
| |
| Response PageHandler::Enable() { |
| enabled_ = true; |
| return Response::FallThrough(); |
| } |
| |
| Response PageHandler::Disable() { |
| enabled_ = false; |
| screencast_enabled_ = false; |
| |
| if (video_consumer_) |
| video_consumer_->StopCapture(); |
| |
| if (!pending_dialog_.is_null()) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| // 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, base::string16()); |
| pending_dialog_.Reset(); |
| } |
| |
| for (auto* item : pending_downloads_) |
| item->RemoveObserver(this); |
| navigate_callbacks_.clear(); |
| return Response::FallThrough(); |
| } |
| |
| Response PageHandler::Crash() { |
| WebContents* web_contents = WebContents::FromRenderFrameHost(host_); |
| if (!web_contents) |
| return Response::ServerError("Not attached to a page"); |
| 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() { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::ServerError("Not attached to a page"); |
| web_contents->DispatchBeforeUnload(false /* auto_cancel */); |
| return Response::Success(); |
| } |
| |
| void PageHandler::Reload(Maybe<bool> bypassCache, |
| Maybe<std::string> script_to_evaluate_on_load, |
| std::unique_ptr<ReloadCallback> callback) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) { |
| callback->sendFailure(Response::InternalError()); |
| 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. |
| if (web_contents->GetOuterWebContents()) |
| web_contents = web_contents->GetOuterWebContents(); |
| |
| // It is important to fallback before triggering reload, so that |
| // renderer could prepare beforehand. |
| callback->fallThrough(); |
| web_contents->GetController().Reload(bypassCache.fromMaybe(false) |
| ? ReloadType::BYPASSING_CACHE |
| : ReloadType::NORMAL, |
| false); |
| } |
| |
| static 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; |
| |
| DCHECK(policy.empty()); |
| return network::mojom::ReferrerPolicy::kDefault; |
| } |
| |
| void PageHandler::Navigate(const std::string& url, |
| Maybe<std::string> referrer, |
| Maybe<std::string> maybe_transition_type, |
| Maybe<std::string> frame_id, |
| Maybe<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 (!host_) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| |
| ui::PageTransition type; |
| std::string transition_type = |
| maybe_transition_type.fromMaybe(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; |
| |
| FrameTreeNode* frame_tree_node = nullptr; |
| std::string out_frame_id = frame_id.fromMaybe( |
| host_->frame_tree_node()->devtools_frame_token().ToString()); |
| FrameTreeNode* root = host_->frame_tree_node(); |
| if (root->devtools_frame_token().ToString() == out_frame_id) { |
| frame_tree_node = root; |
| } else { |
| for (FrameTreeNode* node : root->frame_tree()->SubtreeNodes(root)) { |
| if (node->devtools_frame_token().ToString() == out_frame_id) { |
| frame_tree_node = node; |
| break; |
| } |
| } |
| } |
| |
| if (!frame_tree_node) { |
| callback->sendFailure( |
| Response::ServerError("No frame with given id found")); |
| return; |
| } |
| |
| NavigationController::LoadURLParams params(gurl); |
| network::mojom::ReferrerPolicy policy = |
| ParsePolicyFromString(referrer_policy.fromMaybe("")); |
| params.referrer = Referrer(GURL(referrer.fromMaybe("")), policy); |
| params.transition_type = type; |
| params.frame_tree_node_id = frame_tree_node->frame_tree_node_id(); |
| frame_tree_node->navigator().GetController()->LoadURLWithParams(params); |
| |
| base::UnguessableToken frame_token = frame_tree_node->devtools_frame_token(); |
| auto navigate_callback = navigate_callbacks_.find(frame_token); |
| if (navigate_callback != navigate_callbacks_.end()) { |
| std::string error_string = net::ErrorToString(net::ERR_ABORTED); |
| navigate_callback->second->sendSuccess(out_frame_id, Maybe<std::string>(), |
| Maybe<std::string>(error_string)); |
| } |
| if (frame_tree_node->navigation_request()) { |
| navigate_callbacks_[frame_token] = std::move(callback); |
| } else { |
| callback->sendSuccess(out_frame_id, Maybe<std::string>(), |
| Maybe<std::string>()); |
| } |
| } |
| |
| void PageHandler::NavigationReset(NavigationRequest* navigation_request) { |
| auto navigate_callback = navigate_callbacks_.find( |
| navigation_request->frame_tree_node()->devtools_frame_token()); |
| if (navigate_callback == navigate_callbacks_.end()) |
| return; |
| std::string frame_id = |
| navigation_request->frame_tree_node()->devtools_frame_token().ToString(); |
| bool success = navigation_request->GetNetErrorCode() == net::OK; |
| std::string error_string = |
| net::ErrorToString(navigation_request->GetNetErrorCode()); |
| navigate_callback->second->sendSuccess( |
| frame_id, |
| Maybe<std::string>( |
| navigation_request->devtools_navigation_token().ToString()), |
| success ? Maybe<std::string>() : Maybe<std::string>(error_string)); |
| navigate_callbacks_.erase(navigate_callback); |
| } |
| |
| 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 base::string16 likely_filename = net::GetSuggestedFilename( |
| item->GetURL(), item->GetContentDisposition(), std::string(), |
| item->GetSuggestedFilename(), item->GetMimeType(), "download"); |
| |
| frontend_->DownloadWillBegin(ftn->devtools_frame_token().ToString(), |
| item->GetGuid(), item->GetURL().spec(), |
| base::UTF16ToUTF8(likely_filename)); |
| |
| item->AddObserver(this); |
| pending_downloads_.insert(item); |
| } |
| |
| void PageHandler::OnDownloadDestroyed(download::DownloadItem* item) { |
| pending_downloads_.erase(item); |
| } |
| |
| void PageHandler::OnDownloadUpdated(download::DownloadItem* item) { |
| if (!enabled_) |
| return; |
| std::string state = Page::DownloadProgress::StateEnum::InProgress; |
| if (item->GetState() == download::DownloadItem::COMPLETE) |
| state = Page::DownloadProgress::StateEnum::Completed; |
| else if (item->GetState() == download::DownloadItem::CANCELLED) |
| state = Page::DownloadProgress::StateEnum::Canceled; |
| 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) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| |
| NavigationController& controller = web_contents->GetController(); |
| *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) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| |
| NavigationController& controller = web_contents->GetController(); |
| 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() { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| |
| NavigationController& controller = web_contents->GetController(); |
| controller.DeleteNavigationEntries(base::BindRepeating(&ReturnTrue)); |
| return Response::Success(); |
| } |
| |
| void PageHandler::CaptureSnapshot( |
| Maybe<std::string> format, |
| std::unique_ptr<CaptureSnapshotCallback> callback) { |
| std::string snapshot_format = format.fromMaybe(kMhtml); |
| if (snapshot_format != kMhtml) { |
| callback->sendFailure(Response::ServerError("Unsupported snapshot format")); |
| return; |
| } |
| DevToolsMHTMLHelper::Capture(weak_factory_.GetWeakPtr(), std::move(callback)); |
| } |
| |
| void PageHandler::CaptureScreenshot( |
| Maybe<std::string> format, |
| Maybe<int> quality, |
| Maybe<Page::Viewport> clip, |
| Maybe<bool> from_surface, |
| std::unique_ptr<CaptureScreenshotCallback> callback) { |
| if (!host_ || !host_->GetRenderWidgetHost() || |
| !host_->GetRenderWidgetHost()->GetView()) { |
| callback->sendFailure(Response::InternalError()); |
| return; |
| } |
| if (clip.isJust()) { |
| if (clip.fromJust()->GetWidth() == 0) { |
| callback->sendFailure( |
| Response::ServerError("Cannot take screenshot with 0 width.")); |
| return; |
| } |
| if (clip.fromJust()->GetHeight() == 0) { |
| callback->sendFailure( |
| Response::ServerError("Cannot take screenshot with 0 height.")); |
| return; |
| } |
| } |
| |
| RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost(); |
| std::string screenshot_format = format.fromMaybe(kPng); |
| int screenshot_quality = quality.fromMaybe(kDefaultScreenshotQuality); |
| |
| // We don't support clip/emulation when capturing from window, bail out. |
| if (!from_surface.fromMaybe(true)) { |
| widget_host->GetSnapshotFromBrowser( |
| base::BindOnce(&PageHandler::ScreenshotCaptured, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| screenshot_format, screenshot_quality, gfx::Size(), |
| gfx::Size(), blink::WebDeviceEmulationParams()), |
| false); |
| return; |
| } |
| |
| // Welcome to the neural net of capturing screenshot while emulating device |
| // metrics! |
| bool emulation_enabled = emulation_handler_->device_emulation_enabled(); |
| blink::WebDeviceEmulationParams original_params = |
| emulation_handler_->GetDeviceEmulationParams(); |
| blink::WebDeviceEmulationParams modified_params = original_params; |
| |
| // Capture original view size if we know we are going to destroy it. We use |
| // it in ScreenshotCaptured to restore. |
| gfx::Size original_view_size = |
| emulation_enabled || clip.isJust() |
| ? widget_host->GetView()->GetViewBounds().size() |
| : gfx::Size(); |
| gfx::Size emulated_view_size = modified_params.view_size; |
| |
| double dpfactor = 1; |
| ScreenInfo screen_info; |
| widget_host->GetScreenInfo(&screen_info); |
| 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 / |
| screen_info.device_scale_factor |
| : 1; |
| // When clip is specified, we scale viewport via clip, otherwise we use |
| // scale. |
| modified_params.scale = clip.isJust() ? 1 : dpfactor; |
| modified_params.view_size.width = emulated_view_size.width(); |
| modified_params.view_size.height = emulated_view_size.height(); |
| } else if (clip.isJust()) { |
| // When not emulating, still need to emulate the page size. |
| modified_params.view_size.width = original_view_size.width(); |
| modified_params.view_size.height = original_view_size.height(); |
| modified_params.screen_size.width = 0; |
| modified_params.screen_size.height = 0; |
| modified_params.device_scale_factor = 0; |
| modified_params.scale = 1; |
| } |
| |
| // Set up viewport in renderer. |
| if (clip.isJust()) { |
| modified_params.viewport_offset.SetPoint(clip.fromJust()->GetX(), |
| clip.fromJust()->GetY()); |
| modified_params.viewport_scale = clip.fromJust()->GetScale() * dpfactor; |
| if (IsUseZoomForDSFEnabled()) { |
| modified_params.viewport_offset.Scale(screen_info.device_scale_factor); |
| } |
| } |
| |
| // We use WebDeviceEmulationParams to either emulate, set viewport or both. |
| emulation_handler_->SetDeviceEmulationParams(modified_params); |
| |
| // Set view size for the screenshot right after emulating. |
| if (clip.isJust()) { |
| double scale = dpfactor * clip.fromJust()->GetScale(); |
| widget_host->GetView()->SetSize( |
| gfx::Size(base::Round(clip.fromJust()->GetWidth() * scale), |
| base::Round(clip.fromJust()->GetHeight() * scale))); |
| } else if (emulation_enabled) { |
| widget_host->GetView()->SetSize( |
| gfx::ScaleToFlooredSize(emulated_view_size, dpfactor)); |
| } |
| gfx::Size requested_image_size = gfx::Size(); |
| if (emulation_enabled || clip.isJust()) { |
| if (clip.isJust()) { |
| requested_image_size = |
| gfx::Size(clip.fromJust()->GetWidth(), clip.fromJust()->GetHeight()); |
| } else { |
| requested_image_size = emulated_view_size; |
| } |
| double scale = emulation_enabled ? original_params.device_scale_factor |
| : screen_info.device_scale_factor; |
| if (clip.isJust()) |
| scale *= clip.fromJust()->GetScale(); |
| requested_image_size = gfx::ScaleToRoundedSize(requested_image_size, scale); |
| } |
| |
| widget_host->GetSnapshotFromBrowser( |
| base::BindOnce(&PageHandler::ScreenshotCaptured, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| screenshot_format, screenshot_quality, original_view_size, |
| requested_image_size, original_params), |
| true); |
| } |
| |
| void PageHandler::PrintToPDF(Maybe<bool> landscape, |
| Maybe<bool> display_header_footer, |
| Maybe<bool> print_background, |
| Maybe<double> scale, |
| Maybe<double> paper_width, |
| Maybe<double> paper_height, |
| Maybe<double> margin_top, |
| Maybe<double> margin_bottom, |
| Maybe<double> margin_left, |
| Maybe<double> margin_right, |
| Maybe<String> page_ranges, |
| Maybe<bool> ignore_invalid_page_ranges, |
| Maybe<String> header_template, |
| Maybe<String> footer_template, |
| Maybe<bool> prefer_css_page_size, |
| Maybe<String> transfer_mode, |
| std::unique_ptr<PrintToPDFCallback> callback) { |
| callback->sendFailure(Response::ServerError("PrintToPDF is not implemented")); |
| return; |
| } |
| |
| Response PageHandler::StartScreencast(Maybe<std::string> format, |
| Maybe<int> quality, |
| Maybe<int> max_width, |
| Maybe<int> max_height, |
| Maybe<int> every_nth_frame) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| RenderWidgetHostImpl* widget_host = |
| host_ ? host_->GetRenderWidgetHost() : nullptr; |
| if (!widget_host) |
| return Response::InternalError(); |
| |
| screencast_enabled_ = true; |
| screencast_format_ = format.fromMaybe(kPng); |
| screencast_quality_ = quality.fromMaybe(kDefaultScreenshotQuality); |
| if (screencast_quality_ < 0 || screencast_quality_ > 100) |
| screencast_quality_ = kDefaultScreenshotQuality; |
| screencast_max_width_ = max_width.fromMaybe(-1); |
| screencast_max_height_ = max_height.fromMaybe(-1); |
| ++session_id_; |
| frame_counter_ = 0; |
| frames_in_flight_ = 0; |
| capture_every_nth_frame_ = every_nth_frame.fromMaybe(1); |
| bool visible = !widget_host->is_hidden(); |
| NotifyScreencastVisibility(visible); |
| |
| if (video_consumer_) { |
| 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(); |
| } |
| |
| if (!visible) |
| return Response::FallThrough(); |
| |
| if (frame_metadata_) { |
| InnerSwapCompositorFrame(); |
| } else { |
| widget_host->RequestForceRedraw(0); |
| } |
| return Response::FallThrough(); |
| } |
| |
| Response PageHandler::StopScreencast() { |
| screencast_enabled_ = false; |
| 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, |
| Maybe<std::string> prompt_text) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| |
| if (pending_dialog_.is_null()) |
| return Response::InvalidParams("No dialog is showing"); |
| |
| base::string16 prompt_override; |
| if (prompt_text.isJust()) |
| prompt_override = base::UTF8ToUTF16(prompt_text.fromJust()); |
| std::move(pending_dialog_).Run(accept, prompt_override); |
| |
| // Clean up the dialog UI if any. |
| if (web_contents->GetDelegate()) { |
| JavaScriptDialogManager* manager = |
| web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents); |
| if (manager) { |
| manager->HandleJavaScriptDialog( |
| web_contents, accept, |
| prompt_text.isJust() ? &prompt_override : nullptr); |
| } |
| } |
| |
| return Response::Success(); |
| } |
| |
| Response PageHandler::BringToFront() { |
| WebContentsImpl* wc = GetWebContents(); |
| if (wc) { |
| wc->Activate(); |
| wc->Focus(); |
| return Response::Success(); |
| } |
| return Response::InternalError(); |
| } |
| |
| Response PageHandler::SetDownloadBehavior(const std::string& behavior, |
| Maybe<std::string> download_path) { |
| BrowserContext* browser_context = |
| host_ ? host_->GetProcess()->GetBrowserContext() : nullptr; |
| if (!browser_context) |
| return Response::ServerError("Could not fetch browser context"); |
| return browser_handler_->DoSetDownloadBehavior(behavior, browser_context, |
| std::move(download_path)); |
| } |
| |
| void PageHandler::GetAppManifest( |
| std::unique_ptr<GetAppManifestCallback> callback) { |
| if (!host_) { |
| callback->sendFailure(Response::ServerError("Cannot retrieve manifest")); |
| return; |
| } |
| ManifestManagerHost::GetOrCreateForCurrentDocument(host_->GetMainFrame()) |
| ->RequestManifestDebugInfo(base::BindOnce(&PageHandler::GotManifest, |
| weak_factory_.GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| WebContentsImpl* PageHandler::GetWebContents() { |
| return host_ && !host_->frame_tree_node()->parent() |
| ? static_cast<WebContentsImpl*>( |
| WebContents::FromRenderFrameHost(host_)) |
| : nullptr; |
| } |
| |
| void PageHandler::NotifyScreencastVisibility(bool visible) { |
| if (visible) |
| capture_retry_count_ = kCaptureRetryLimit; |
| frontend_->ScreencastVisibilityChanged(visible); |
| } |
| |
| void PageHandler::InnerSwapCompositorFrame() { |
| if (!host_) |
| return; |
| |
| if (frames_in_flight_ > kMaxScreencastFramesInFlight) |
| return; |
| |
| if (++frame_counter_ % capture_every_nth_frame_) |
| return; |
| |
| RenderWidgetHostViewBase* const view = |
| static_cast<RenderWidgetHostViewBase*>(host_->GetView()); |
| if (!view || !view->IsSurfaceAvailableForCopy()) |
| return; |
| |
| const gfx::Size surface_size = view->GetCompositorViewportPixelSize(); |
| if (surface_size.IsEmpty()) |
| return; |
| |
| const gfx::Size snapshot_size = DetermineSnapshotSize( |
| surface_size, screencast_max_width_, screencast_max_height_); |
| if (snapshot_size.IsEmpty()) |
| return; |
| |
| double top_controls_visible_height = |
| frame_metadata_->top_controls_height * |
| frame_metadata_->top_controls_shown_ratio; |
| |
| std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata = |
| BuildScreencastFrameMetadata( |
| surface_size, frame_metadata_->device_scale_factor, |
| frame_metadata_->page_scale_factor, |
| frame_metadata_->root_scroll_offset.value_or(gfx::Vector2dF()), |
| top_controls_visible_height); |
| if (!page_metadata) |
| return; |
| |
| // Request a copy of the surface as a scaled SkBitmap. |
| view->CopyFromSurface( |
| gfx::Rect(), snapshot_size, |
| base::BindOnce(&PageHandler::ScreencastFrameCaptured, |
| weak_factory_.GetWeakPtr(), std::move(page_metadata))); |
| frames_in_flight_++; |
| } |
| |
| void PageHandler::OnFrameFromVideoConsumer( |
| scoped_refptr<media::VideoFrame> frame) { |
| if (!host_) |
| 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::Vector2dF 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; |
| |
| ScreencastFrameCaptured(std::move(page_metadata), |
| DevToolsVideoConsumer::GetSkBitmapFromFrame(frame)); |
| } |
| |
| void PageHandler::ScreencastFrameCaptured( |
| std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata, |
| const SkBitmap& bitmap) { |
| if (bitmap.drawsNothing()) { |
| if (capture_retry_count_) { |
| --capture_retry_count_; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&PageHandler::InnerSwapCompositorFrame, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs)); |
| } |
| --frames_in_flight_; |
| return; |
| } |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&EncodeSkBitmap, bitmap, screencast_format_, |
| screencast_quality_), |
| base::BindOnce(&PageHandler::ScreencastFrameEncoded, |
| weak_factory_.GetWeakPtr(), std::move(page_metadata))); |
| } |
| |
| void PageHandler::ScreencastFrameEncoded( |
| std::unique_ptr<Page::ScreencastFrameMetadata> page_metadata, |
| const protocol::Binary& data) { |
| if (data.size() == 0) { |
| --frames_in_flight_; |
| return; // Encode failed. |
| } |
| |
| frontend_->ScreencastFrame(data, std::move(page_metadata), session_id_); |
| } |
| |
| void PageHandler::ScreenshotCaptured( |
| std::unique_ptr<CaptureScreenshotCallback> callback, |
| const std::string& format, |
| int quality, |
| const gfx::Size& original_view_size, |
| const gfx::Size& requested_image_size, |
| const blink::WebDeviceEmulationParams& original_emulation_params, |
| const gfx::Image& image) { |
| if (original_view_size.width()) { |
| RenderWidgetHostImpl* widget_host = host_->GetRenderWidgetHost(); |
| widget_host->GetView()->SetSize(original_view_size); |
| emulation_handler_->SetDeviceEmulationParams(original_emulation_params); |
| } |
| |
| if (image.IsEmpty()) { |
| callback->sendFailure( |
| Response::ServerError("Unable to capture screenshot")); |
| return; |
| } |
| |
| if (!requested_image_size.IsEmpty() && |
| (image.Width() != requested_image_size.width() || |
| image.Height() != requested_image_size.height())) { |
| const SkBitmap* bitmap = image.ToSkBitmap(); |
| SkBitmap cropped = SkBitmapOperations::CreateTiledBitmap( |
| *bitmap, 0, 0, requested_image_size.width(), |
| requested_image_size.height()); |
| gfx::Image croppedImage = gfx::Image::CreateFrom1xBitmap(cropped); |
| callback->sendSuccess(EncodeImage(croppedImage, format, quality)); |
| } else { |
| callback->sendSuccess(EncodeImage(image, format, quality)); |
| } |
| } |
| |
| void PageHandler::GotManifest(std::unique_ptr<GetAppManifestCallback> callback, |
| const GURL& manifest_url, |
| const ::blink::Manifest& parsed_manifest, |
| blink::mojom::ManifestDebugInfoPtr debug_info) { |
| 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; |
| } |
| } |
| |
| std::unique_ptr<Page::AppManifestParsedProperties> parsed; |
| if (!parsed_manifest.IsEmpty()) { |
| parsed = Page::AppManifestParsedProperties::Create() |
| .SetScope(parsed_manifest.scope.possibly_invalid_spec()) |
| .Build(); |
| } |
| |
| callback->sendSuccess( |
| manifest_url.possibly_invalid_spec(), std::move(errors), |
| failed ? Maybe<std::string>() : debug_info->raw_manifest, |
| std::move(parsed)); |
| } |
| |
| Response PageHandler::StopLoading() { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::InternalError(); |
| web_contents->Stop(); |
| return Response::Success(); |
| } |
| |
| Response PageHandler::SetWebLifecycleState(const std::string& state) { |
| WebContentsImpl* web_contents = GetWebContents(); |
| if (!web_contents) |
| return Response::ServerError("Not attached to a page"); |
| 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(Maybe<Binary>()); |
| } |
| |
| } // namespace protocol |
| } // namespace content |