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_
= frame_metadata
;
150 has_compositor_frame_metadata_
= true;
152 if (screencast_enabled_
)
153 InnerSwapCompositorFrame();
154 color_picker_
->OnSwapCompositorFrame();
157 void PageHandler::OnSynchronousSwapCompositorFrame(
158 const cc::CompositorFrameMetadata
& frame_metadata
) {
159 last_compositor_frame_metadata_
= has_compositor_frame_metadata_
?
160 next_compositor_frame_metadata_
: frame_metadata
;
161 next_compositor_frame_metadata_
= frame_metadata
;
162 has_compositor_frame_metadata_
= true;
164 if (screencast_enabled_
)
165 InnerSwapCompositorFrame();
166 color_picker_
->OnSwapCompositorFrame();
169 void PageHandler::Observe(int type
,
170 const NotificationSource
& source
,
171 const NotificationDetails
& details
) {
172 if (!screencast_enabled_
)
174 DCHECK(type
== content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED
);
175 bool visible
= *Details
<bool>(details
).ptr();
176 NotifyScreencastVisibility(visible
);
179 void PageHandler::DidAttachInterstitialPage() {
182 client_
->InterstitialShown(InterstitialShownParams::Create());
185 void PageHandler::DidDetachInterstitialPage() {
188 client_
->InterstitialHidden(InterstitialHiddenParams::Create());
191 void PageHandler::SetScreencastListener(ScreencastListener
* listener
) {
192 screencast_listener_
= listener
;
195 Response
PageHandler::Enable() {
197 return Response::FallThrough();
200 Response
PageHandler::Disable() {
202 screencast_enabled_
= false;
203 color_picker_
->SetEnabled(false);
204 if (screencast_listener_
)
205 screencast_listener_
->ScreencastEnabledChanged();
206 return Response::FallThrough();
209 Response
PageHandler::Reload(const bool* ignoreCache
,
210 const std::string
* script_to_evaluate_on_load
,
211 const std::string
* script_preprocessor
) {
212 WebContentsImpl
* web_contents
= GetWebContents();
214 return Response::InternalError("Could not connect to view");
216 if (web_contents
->IsCrashed() ||
217 web_contents
->GetController().GetVisibleEntry()->IsViewSourceMode()) {
218 web_contents
->GetController().Reload(false);
219 return Response::OK();
221 // Handle reload in renderer except for crashed and view source mode.
222 return Response::FallThrough();
226 Response
PageHandler::Navigate(const std::string
& url
,
229 if (!gurl
.is_valid())
230 return Response::InternalError("Cannot navigate to invalid URL");
232 WebContentsImpl
* web_contents
= GetWebContents();
234 return Response::InternalError("Could not connect to view");
236 web_contents
->GetController()
237 .LoadURL(gurl
, Referrer(), ui::PAGE_TRANSITION_TYPED
, std::string());
238 return Response::FallThrough();
241 Response
PageHandler::GetNavigationHistory(int* current_index
,
242 NavigationEntries
* entries
) {
243 WebContentsImpl
* web_contents
= GetWebContents();
245 return Response::InternalError("Could not connect to view");
247 NavigationController
& controller
= web_contents
->GetController();
248 *current_index
= controller
.GetCurrentEntryIndex();
249 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
250 entries
->push_back(NavigationEntry::Create()
251 ->set_id(controller
.GetEntryAtIndex(i
)->GetUniqueID())
252 ->set_url(controller
.GetEntryAtIndex(i
)->GetURL().spec())
254 base::UTF16ToUTF8(controller
.GetEntryAtIndex(i
)->GetTitle())));
256 return Response::OK();
259 Response
PageHandler::NavigateToHistoryEntry(int entry_id
) {
260 WebContentsImpl
* web_contents
= GetWebContents();
262 return Response::InternalError("Could not connect to view");
264 NavigationController
& controller
= web_contents
->GetController();
265 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
266 if (controller
.GetEntryAtIndex(i
)->GetUniqueID() == entry_id
) {
267 controller
.GoToIndex(i
);
268 return Response::OK();
272 return Response::InvalidParams("No entry with passed id");
275 Response
PageHandler::CaptureScreenshot(DevToolsCommandId command_id
) {
276 if (!host_
|| !host_
->GetRenderWidgetHost())
277 return Response::InternalError("Could not connect to view");
279 host_
->GetRenderWidgetHost()->GetSnapshotFromBrowser(
280 base::Bind(&PageHandler::ScreenshotCaptured
,
281 weak_factory_
.GetWeakPtr(), command_id
));
282 return Response::OK();
285 Response
PageHandler::CanScreencast(bool* result
) {
286 #if defined(OS_ANDROID)
290 #endif // defined(OS_ANDROID)
291 return Response::OK();
294 Response
PageHandler::StartScreencast(const std::string
* format
,
296 const int* max_width
,
297 const int* max_height
) {
298 RenderWidgetHostImpl
* widget_host
=
299 host_
? host_
->GetRenderWidgetHost() : nullptr;
301 return Response::InternalError("Could not connect to view");
303 screencast_enabled_
= true;
304 screencast_format_
= format
? *format
: kPng
;
305 screencast_quality_
= quality
? *quality
: kDefaultScreenshotQuality
;
306 if (screencast_quality_
< 0 || screencast_quality_
> 100)
307 screencast_quality_
= kDefaultScreenshotQuality
;
308 screencast_max_width_
= max_width
? *max_width
: -1;
309 screencast_max_height_
= max_height
? *max_height
: -1;
311 bool visible
= !widget_host
->is_hidden();
312 NotifyScreencastVisibility(visible
);
314 if (has_compositor_frame_metadata_
) {
315 InnerSwapCompositorFrame();
318 new ViewMsg_ForceRedraw(widget_host
->GetRoutingID(), 0));
321 if (screencast_listener_
)
322 screencast_listener_
->ScreencastEnabledChanged();
323 return Response::FallThrough();
326 Response
PageHandler::StopScreencast() {
327 screencast_enabled_
= false;
328 if (screencast_listener_
)
329 screencast_listener_
->ScreencastEnabledChanged();
330 return Response::FallThrough();
333 Response
PageHandler::ScreencastFrameAck(int frame_number
) {
334 screencast_frame_acked_
= frame_number
;
335 return Response::OK();
338 Response
PageHandler::HandleJavaScriptDialog(bool accept
,
339 const std::string
* prompt_text
) {
340 base::string16 prompt_override
;
342 prompt_override
= base::UTF8ToUTF16(*prompt_text
);
344 WebContentsImpl
* web_contents
= GetWebContents();
346 return Response::InternalError("Could not connect to view");
348 JavaScriptDialogManager
* manager
=
349 web_contents
->GetDelegate()->GetJavaScriptDialogManager(web_contents
);
350 if (manager
&& manager
->HandleJavaScriptDialog(
351 web_contents
, accept
, prompt_text
? &prompt_override
: nullptr)) {
352 return Response::OK();
355 return Response::InternalError("Could not handle JavaScript dialog");
358 Response
PageHandler::QueryUsageAndQuota(DevToolsCommandId command_id
,
359 const std::string
& security_origin
) {
360 return Response::OK();
363 Response
PageHandler::SetColorPickerEnabled(bool enabled
) {
365 return Response::InternalError("Could not connect to view");
367 color_picker_
->SetEnabled(enabled
);
368 return Response::OK();
371 WebContentsImpl
* PageHandler::GetWebContents() {
373 static_cast<WebContentsImpl
*>(WebContents::FromRenderFrameHost(host_
)) :
377 void PageHandler::NotifyScreencastVisibility(bool visible
) {
379 capture_retry_count_
= kCaptureRetryLimit
;
380 client_
->ScreencastVisibilityChanged(
381 ScreencastVisibilityChangedParams::Create()->set_visible(visible
));
384 void PageHandler::InnerSwapCompositorFrame() {
385 if (screencast_frame_sent_
- screencast_frame_acked_
>
386 kMaxScreencastFramesInFlight
|| processing_screencast_frame_
) {
390 if (!host_
|| !host_
->GetView())
393 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
395 // TODO(vkuzkokov): do not use previous frame metadata.
396 cc::CompositorFrameMetadata
& metadata
= last_compositor_frame_metadata_
;
398 gfx::SizeF viewport_size_dip
= gfx::ScaleSize(
399 metadata
.scrollable_viewport_size
, metadata
.page_scale_factor
);
400 gfx::SizeF screen_size_dip
= gfx::ScaleSize(view
->GetPhysicalBackingSize(),
401 1 / metadata
.device_scale_factor
);
403 blink::WebScreenInfo screen_info
;
404 view
->GetScreenInfo(&screen_info
);
405 double device_scale_factor
= screen_info
.deviceScaleFactor
;
408 if (screencast_max_width_
> 0) {
409 double max_width_dip
= screencast_max_width_
/ device_scale_factor
;
410 scale
= std::min(scale
, max_width_dip
/ screen_size_dip
.width());
412 if (screencast_max_height_
> 0) {
413 double max_height_dip
= screencast_max_height_
/ device_scale_factor
;
414 scale
= std::min(scale
, max_height_dip
/ screen_size_dip
.height());
420 gfx::Size
snapshot_size_dip(gfx::ToRoundedSize(
421 gfx::ScaleSize(viewport_size_dip
, scale
)));
423 if (snapshot_size_dip
.width() > 0 && snapshot_size_dip
.height() > 0) {
424 processing_screencast_frame_
= true;
425 gfx::Rect
viewport_bounds_dip(gfx::ToRoundedSize(viewport_size_dip
));
426 view
->CopyFromCompositingSurface(
429 base::Bind(&PageHandler::ScreencastFrameCaptured
,
430 weak_factory_
.GetWeakPtr(),
431 last_compositor_frame_metadata_
),
436 void PageHandler::ScreencastFrameCaptured(
437 const cc::CompositorFrameMetadata
& metadata
,
438 const SkBitmap
& bitmap
,
439 ReadbackResponse response
) {
440 if (response
!= READBACK_SUCCESS
) {
441 processing_screencast_frame_
= false;
442 if (capture_retry_count_
) {
443 --capture_retry_count_
;
444 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
445 FROM_HERE
, base::Bind(&PageHandler::InnerSwapCompositorFrame
,
446 weak_factory_
.GetWeakPtr()),
447 base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs
));
451 base::PostTaskAndReplyWithResult(
452 base::WorkerPool::GetTaskRunner(true).get(),
454 base::Bind(&EncodeScreencastFrame
,
455 bitmap
, screencast_format_
, screencast_quality_
),
456 base::Bind(&PageHandler::ScreencastFrameEncoded
,
457 weak_factory_
.GetWeakPtr(), metadata
, base::Time::Now()));
460 void PageHandler::ScreencastFrameEncoded(
461 const cc::CompositorFrameMetadata
& metadata
,
462 const base::Time
& timestamp
,
463 const std::string
& data
) {
464 processing_screencast_frame_
= false;
466 // Consider metadata empty in case it has no device scale factor.
467 if (metadata
.device_scale_factor
== 0 || !host_
|| data
.empty())
470 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
475 gfx::SizeF screen_size_dip
= gfx::ScaleSize(
476 view
->GetPhysicalBackingSize(), 1 / metadata
.device_scale_factor
);
477 scoped_refptr
<ScreencastFrameMetadata
> param_metadata
=
478 ScreencastFrameMetadata::Create()
479 ->set_page_scale_factor(metadata
.page_scale_factor
)
480 ->set_offset_top(metadata
.location_bar_content_translation
.y())
481 ->set_device_width(screen_size_dip
.width())
482 ->set_device_height(screen_size_dip
.height())
483 ->set_scroll_offset_x(metadata
.root_scroll_offset
.x())
484 ->set_scroll_offset_y(metadata
.root_scroll_offset
.y())
485 ->set_timestamp(timestamp
.ToDoubleT());
486 client_
->ScreencastFrame(ScreencastFrameParams::Create()
488 ->set_metadata(param_metadata
)
489 ->set_frame_number(++screencast_frame_sent_
));
492 void PageHandler::ScreenshotCaptured(DevToolsCommandId command_id
,
493 const unsigned char* png_data
,
495 if (!png_data
|| !png_size
) {
496 client_
->SendError(command_id
,
497 Response::InternalError("Unable to capture screenshot"));
501 std::string base_64_data
;
503 base::StringPiece(reinterpret_cast<const char*>(png_data
), png_size
),
506 client_
->SendCaptureScreenshotResponse(command_id
,
507 CaptureScreenshotResponse::Create()->set_data(base_64_data
));
510 void PageHandler::OnColorPicked(int r
, int g
, int b
, int a
) {
511 scoped_refptr
<dom::RGBA
> color
=
512 dom::RGBA::Create()->set_r(r
)->set_g(g
)->set_b(b
)->set_a(a
);
513 client_
->ColorPicked(ColorPickedParams::Create()->set_color(color
));
517 } // namespace devtools
518 } // namespace content