1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/devtools/protocol/page_handler.h"
9 #include "base/base64.h"
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/strings/string16.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/worker_pool.h"
17 #include "content/browser/devtools/protocol/color_picker.h"
18 #include "content/browser/renderer_host/render_widget_host_impl.h"
19 #include "content/browser/renderer_host/render_widget_host_view_base.h"
20 #include "content/browser/web_contents/web_contents_impl.h"
21 #include "content/common/view_messages.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/javascript_dialog_manager.h"
24 #include "content/public/browser/navigation_controller.h"
25 #include "content/public/browser/navigation_entry.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/notification_types.h"
28 #include "content/public/browser/storage_partition.h"
29 #include "content/public/browser/web_contents_delegate.h"
30 #include "content/public/common/referrer.h"
31 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
32 #include "third_party/skia/include/core/SkBitmap.h"
33 #include "ui/base/page_transition_types.h"
34 #include "ui/gfx/codec/jpeg_codec.h"
35 #include "ui/gfx/codec/png_codec.h"
36 #include "ui/gfx/geometry/size_conversions.h"
37 #include "ui/snapshot/snapshot.h"
46 static const char kPng
[] = "png";
47 static const char kJpeg
[] = "jpeg";
48 static int kDefaultScreenshotQuality
= 80;
49 static int kFrameRetryDelayMs
= 100;
50 static int kCaptureRetryLimit
= 2;
51 static int kMaxScreencastFramesInFlight
= 2;
53 std::string
EncodeScreencastFrame(const SkBitmap
& bitmap
,
54 const std::string
& format
,
56 std::vector
<unsigned char> data
;
57 SkAutoLockPixels
lock_image(bitmap
);
60 encoded
= gfx::PNGCodec::Encode(
61 reinterpret_cast<unsigned char*>(bitmap
.getAddr32(0, 0)),
62 gfx::PNGCodec::FORMAT_SkBitmap
,
63 gfx::Size(bitmap
.width(), bitmap
.height()),
64 bitmap
.width() * bitmap
.bytesPerPixel(),
65 false, std::vector
<gfx::PNGCodec::Comment
>(), &data
);
66 } else if (format
== kJpeg
) {
67 encoded
= gfx::JPEGCodec::Encode(
68 reinterpret_cast<unsigned char*>(bitmap
.getAddr32(0, 0)),
69 gfx::JPEGCodec::FORMAT_SkBitmap
,
72 bitmap
.width() * bitmap
.bytesPerPixel(),
81 std::string base_64_data
;
83 base::StringPiece(reinterpret_cast<char*>(&data
[0]), data
.size()),
91 typedef DevToolsProtocolClient::Response Response
;
93 PageHandler::PageHandler()
95 screencast_enabled_(false),
96 screencast_quality_(kDefaultScreenshotQuality
),
97 screencast_max_width_(-1),
98 screencast_max_height_(-1),
99 capture_retry_count_(0),
100 has_compositor_frame_metadata_(false),
101 screencast_frame_sent_(0),
102 screencast_frame_acked_(0),
103 processing_screencast_frame_(false),
104 color_picker_(new ColorPicker(base::Bind(
105 &PageHandler::OnColorPicked
, base::Unretained(this)))),
107 screencast_listener_(nullptr),
108 weak_factory_(this) {
111 PageHandler::~PageHandler() {
114 void PageHandler::SetRenderFrameHost(RenderFrameHostImpl
* host
) {
118 RenderWidgetHostImpl
* widget_host
=
119 host_
? host_
->GetRenderWidgetHost() : nullptr;
123 content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
,
124 content::Source
<RenderWidgetHost
>(widget_host
));
128 widget_host
= host_
? host_
->GetRenderWidgetHost() : nullptr;
129 color_picker_
->SetRenderWidgetHost(widget_host
);
134 content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
,
135 content::Source
<RenderWidgetHost
>(widget_host
));
139 void PageHandler::SetClient(scoped_ptr
<Client
> client
) {
140 client_
.swap(client
);
143 void PageHandler::Detached() {
147 void PageHandler::OnSwapCompositorFrame(
148 const cc::CompositorFrameMetadata
& frame_metadata
) {
149 last_compositor_frame_metadata_
= has_compositor_frame_metadata_
?
150 next_compositor_frame_metadata_
: frame_metadata
;
151 next_compositor_frame_metadata_
= frame_metadata
;
152 has_compositor_frame_metadata_
= true;
154 if (screencast_enabled_
)
155 InnerSwapCompositorFrame();
156 color_picker_
->OnSwapCompositorFrame();
159 void PageHandler::Observe(int type
,
160 const NotificationSource
& source
,
161 const NotificationDetails
& details
) {
162 if (!screencast_enabled_
)
164 DCHECK(type
== content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
);
165 bool visible
= *Details
<bool>(details
).ptr();
166 NotifyScreencastVisibility(visible
);
169 void PageHandler::DidAttachInterstitialPage() {
172 client_
->InterstitialShown(InterstitialShownParams::Create());
175 void PageHandler::DidDetachInterstitialPage() {
178 client_
->InterstitialHidden(InterstitialHiddenParams::Create());
181 void PageHandler::SetScreencastListener(ScreencastListener
* listener
) {
182 screencast_listener_
= listener
;
185 Response
PageHandler::Enable() {
187 return Response::FallThrough();
190 Response
PageHandler::Disable() {
192 screencast_enabled_
= false;
193 color_picker_
->SetEnabled(false);
194 if (screencast_listener_
)
195 screencast_listener_
->ScreencastEnabledChanged();
196 return Response::FallThrough();
199 Response
PageHandler::Reload(const bool* ignoreCache
,
200 const std::string
* script_to_evaluate_on_load
,
201 const std::string
* script_preprocessor
) {
202 WebContentsImpl
* web_contents
= GetWebContents();
204 return Response::InternalError("Could not connect to view");
206 // Handle in browser only if it is crashed.
207 if (!web_contents
->IsCrashed())
208 return Response::FallThrough();
210 web_contents
->GetController().Reload(false);
211 return Response::OK();
214 Response
PageHandler::Navigate(const std::string
& url
,
217 if (!gurl
.is_valid())
218 return Response::InternalError("Cannot navigate to invalid URL");
220 WebContentsImpl
* web_contents
= GetWebContents();
222 return Response::InternalError("Could not connect to view");
224 web_contents
->GetController()
225 .LoadURL(gurl
, Referrer(), ui::PAGE_TRANSITION_TYPED
, std::string());
226 return Response::FallThrough();
229 Response
PageHandler::GetNavigationHistory(int* current_index
,
230 NavigationEntries
* entries
) {
231 WebContentsImpl
* web_contents
= GetWebContents();
233 return Response::InternalError("Could not connect to view");
235 NavigationController
& controller
= web_contents
->GetController();
236 *current_index
= controller
.GetCurrentEntryIndex();
237 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
238 entries
->push_back(NavigationEntry::Create()
239 ->set_id(controller
.GetEntryAtIndex(i
)->GetUniqueID())
240 ->set_url(controller
.GetEntryAtIndex(i
)->GetURL().spec())
242 base::UTF16ToUTF8(controller
.GetEntryAtIndex(i
)->GetTitle())));
244 return Response::OK();
247 Response
PageHandler::NavigateToHistoryEntry(int entry_id
) {
248 WebContentsImpl
* web_contents
= GetWebContents();
250 return Response::InternalError("Could not connect to view");
252 NavigationController
& controller
= web_contents
->GetController();
253 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
254 if (controller
.GetEntryAtIndex(i
)->GetUniqueID() == entry_id
) {
255 controller
.GoToIndex(i
);
256 return Response::OK();
260 return Response::InvalidParams("No entry with passed id");
263 Response
PageHandler::CaptureScreenshot(DevToolsCommandId command_id
) {
264 if (!host_
|| !host_
->GetRenderWidgetHost())
265 return Response::InternalError("Could not connect to view");
267 host_
->GetRenderWidgetHost()->GetSnapshotFromBrowser(
268 base::Bind(&PageHandler::ScreenshotCaptured
,
269 weak_factory_
.GetWeakPtr(), command_id
));
270 return Response::OK();
273 Response
PageHandler::CanScreencast(bool* result
) {
274 #if defined(OS_ANDROID)
278 #endif // defined(OS_ANDROID)
279 return Response::OK();
282 Response
PageHandler::StartScreencast(const std::string
* format
,
284 const int* max_width
,
285 const int* max_height
) {
286 RenderWidgetHostImpl
* widget_host
=
287 host_
? host_
->GetRenderWidgetHost() : nullptr;
289 return Response::InternalError("Could not connect to view");
291 screencast_enabled_
= true;
292 screencast_format_
= format
? *format
: kPng
;
293 screencast_quality_
= quality
? *quality
: kDefaultScreenshotQuality
;
294 if (screencast_quality_
< 0 || screencast_quality_
> 100)
295 screencast_quality_
= kDefaultScreenshotQuality
;
296 screencast_max_width_
= max_width
? *max_width
: -1;
297 screencast_max_height_
= max_height
? *max_height
: -1;
299 bool visible
= !widget_host
->is_hidden();
300 NotifyScreencastVisibility(visible
);
302 if (has_compositor_frame_metadata_
) {
303 InnerSwapCompositorFrame();
306 new ViewMsg_ForceRedraw(widget_host
->GetRoutingID(), 0));
309 if (screencast_listener_
)
310 screencast_listener_
->ScreencastEnabledChanged();
311 return Response::FallThrough();
314 Response
PageHandler::StopScreencast() {
315 screencast_enabled_
= false;
316 if (screencast_listener_
)
317 screencast_listener_
->ScreencastEnabledChanged();
318 return Response::FallThrough();
321 Response
PageHandler::ScreencastFrameAck(int frame_number
) {
322 screencast_frame_acked_
= frame_number
;
323 return Response::OK();
326 Response
PageHandler::HandleJavaScriptDialog(bool accept
,
327 const std::string
* prompt_text
) {
328 base::string16 prompt_override
;
330 prompt_override
= base::UTF8ToUTF16(*prompt_text
);
332 WebContentsImpl
* web_contents
= GetWebContents();
334 return Response::InternalError("Could not connect to view");
336 JavaScriptDialogManager
* manager
=
337 web_contents
->GetDelegate()->GetJavaScriptDialogManager(web_contents
);
338 if (manager
&& manager
->HandleJavaScriptDialog(
339 web_contents
, accept
, prompt_text
? &prompt_override
: nullptr)) {
340 return Response::OK();
343 return Response::InternalError("Could not handle JavaScript dialog");
346 Response
PageHandler::QueryUsageAndQuota(DevToolsCommandId command_id
,
347 const std::string
& security_origin
) {
348 return Response::OK();
351 Response
PageHandler::SetColorPickerEnabled(bool enabled
) {
353 return Response::InternalError("Could not connect to view");
355 color_picker_
->SetEnabled(enabled
);
356 return Response::OK();
359 WebContentsImpl
* PageHandler::GetWebContents() {
361 static_cast<WebContentsImpl
*>(WebContents::FromRenderFrameHost(host_
)) :
365 void PageHandler::NotifyScreencastVisibility(bool visible
) {
367 capture_retry_count_
= kCaptureRetryLimit
;
368 client_
->ScreencastVisibilityChanged(
369 ScreencastVisibilityChangedParams::Create()->set_visible(visible
));
372 void PageHandler::InnerSwapCompositorFrame() {
373 if (screencast_frame_sent_
- screencast_frame_acked_
>
374 kMaxScreencastFramesInFlight
|| processing_screencast_frame_
) {
378 if (!host_
|| !host_
->GetView())
381 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
383 // TODO(vkuzkokov): do not use previous frame metadata.
384 cc::CompositorFrameMetadata
& metadata
= last_compositor_frame_metadata_
;
386 gfx::SizeF viewport_size_dip
= gfx::ScaleSize(
387 metadata
.scrollable_viewport_size
, metadata
.page_scale_factor
);
388 gfx::SizeF screen_size_dip
= gfx::ScaleSize(view
->GetPhysicalBackingSize(),
389 1 / metadata
.device_scale_factor
);
391 blink::WebScreenInfo screen_info
;
392 view
->GetScreenInfo(&screen_info
);
393 double device_scale_factor
= screen_info
.deviceScaleFactor
;
396 if (screencast_max_width_
> 0) {
397 double max_width_dip
= screencast_max_width_
/ device_scale_factor
;
398 scale
= std::min(scale
, max_width_dip
/ screen_size_dip
.width());
400 if (screencast_max_height_
> 0) {
401 double max_height_dip
= screencast_max_height_
/ device_scale_factor
;
402 scale
= std::min(scale
, max_height_dip
/ screen_size_dip
.height());
408 gfx::Size
snapshot_size_dip(gfx::ToRoundedSize(
409 gfx::ScaleSize(viewport_size_dip
, scale
)));
411 if (snapshot_size_dip
.width() > 0 && snapshot_size_dip
.height() > 0) {
412 processing_screencast_frame_
= true;
413 gfx::Rect
viewport_bounds_dip(gfx::ToRoundedSize(viewport_size_dip
));
414 view
->CopyFromCompositingSurface(
417 base::Bind(&PageHandler::ScreencastFrameCaptured
,
418 weak_factory_
.GetWeakPtr(),
419 last_compositor_frame_metadata_
),
424 void PageHandler::ScreencastFrameCaptured(
425 const cc::CompositorFrameMetadata
& metadata
,
426 const SkBitmap
& bitmap
,
427 ReadbackResponse response
) {
428 if (response
!= READBACK_SUCCESS
) {
429 processing_screencast_frame_
= false;
430 if (capture_retry_count_
) {
431 --capture_retry_count_
;
432 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
433 FROM_HERE
, base::Bind(&PageHandler::InnerSwapCompositorFrame
,
434 weak_factory_
.GetWeakPtr()),
435 base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs
));
439 base::PostTaskAndReplyWithResult(
440 base::WorkerPool::GetTaskRunner(true).get(),
442 base::Bind(&EncodeScreencastFrame
,
443 bitmap
, screencast_format_
, screencast_quality_
),
444 base::Bind(&PageHandler::ScreencastFrameEncoded
,
445 weak_factory_
.GetWeakPtr(), metadata
, base::Time::Now()));
448 void PageHandler::ScreencastFrameEncoded(
449 const cc::CompositorFrameMetadata
& metadata
,
450 const base::Time
& timestamp
,
451 const std::string
& data
) {
452 processing_screencast_frame_
= false;
454 // Consider metadata empty in case it has no device scale factor.
455 if (metadata
.device_scale_factor
== 0 || !host_
|| data
.empty())
458 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
463 gfx::SizeF screen_size_dip
= gfx::ScaleSize(
464 view
->GetPhysicalBackingSize(), 1 / metadata
.device_scale_factor
);
465 scoped_refptr
<ScreencastFrameMetadata
> param_metadata
=
466 ScreencastFrameMetadata::Create()
467 ->set_page_scale_factor(metadata
.page_scale_factor
)
468 ->set_offset_top(metadata
.location_bar_content_translation
.y())
469 ->set_device_width(screen_size_dip
.width())
470 ->set_device_height(screen_size_dip
.height())
471 ->set_scroll_offset_x(metadata
.root_scroll_offset
.x())
472 ->set_scroll_offset_y(metadata
.root_scroll_offset
.y())
473 ->set_timestamp(timestamp
.ToDoubleT());
474 client_
->ScreencastFrame(ScreencastFrameParams::Create()
476 ->set_metadata(param_metadata
)
477 ->set_frame_number(++screencast_frame_sent_
));
480 void PageHandler::ScreenshotCaptured(DevToolsCommandId command_id
,
481 const unsigned char* png_data
,
483 if (!png_data
|| !png_size
) {
484 client_
->SendError(command_id
,
485 Response::InternalError("Unable to capture screenshot"));
489 std::string base_64_data
;
491 base::StringPiece(reinterpret_cast<const char*>(png_data
), png_size
),
494 client_
->SendCaptureScreenshotResponse(command_id
,
495 CaptureScreenshotResponse::Create()->set_data(base_64_data
));
498 void PageHandler::OnColorPicked(int r
, int g
, int b
, int a
) {
499 scoped_refptr
<dom::RGBA
> color
=
500 dom::RGBA::Create()->set_r(r
)->set_g(g
)->set_b(b
)->set_a(a
);
501 client_
->ColorPicked(ColorPickedParams::Create()->set_color(color
));
505 } // namespace devtools
506 } // namespace content