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/strings/string16.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/threading/worker_pool.h"
14 #include "content/browser/devtools/protocol/color_picker.h"
15 #include "content/browser/devtools/protocol/usage_and_quota_query.h"
16 #include "content/browser/geolocation/geolocation_service_context.h"
17 #include "content/browser/renderer_host/render_view_host_impl.h"
18 #include "content/browser/renderer_host/render_widget_host_view_base.h"
19 #include "content/browser/web_contents/web_contents_impl.h"
20 #include "content/common/view_messages.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/javascript_dialog_manager.h"
23 #include "content/public/browser/navigation_controller.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "content/public/browser/web_contents_delegate.h"
27 #include "content/public/common/referrer.h"
28 #include "content/public/common/url_constants.h"
29 #include "storage/browser/quota/quota_manager.h"
30 #include "third_party/WebKit/public/platform/WebScreenInfo.h"
31 #include "third_party/skia/include/core/SkBitmap.h"
32 #include "ui/base/page_transition_types.h"
33 #include "ui/gfx/codec/jpeg_codec.h"
34 #include "ui/gfx/codec/png_codec.h"
35 #include "ui/gfx/size_conversions.h"
36 #include "ui/snapshot/snapshot.h"
45 static const char kPng
[] = "png";
46 static const char kJpeg
[] = "jpeg";
47 static int kDefaultScreenshotQuality
= 80;
48 static int kFrameRetryDelayMs
= 100;
49 static int kCaptureRetryLimit
= 2;
50 static int kMaxScreencastFramesInFlight
= 2;
52 void QueryUsageAndQuotaCompletedOnIOThread(
53 const UsageAndQuotaQuery::Callback
& callback
,
54 scoped_refptr
<QueryUsageAndQuotaResponse
> response
) {
55 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
56 base::Bind(callback
, response
));
59 void QueryUsageAndQuotaOnIOThread(
60 scoped_refptr
<storage::QuotaManager
> quota_manager
,
61 const GURL
& security_origin
,
62 const UsageAndQuotaQuery::Callback
& callback
) {
63 new UsageAndQuotaQuery(
66 base::Bind(&QueryUsageAndQuotaCompletedOnIOThread
,
70 std::string
EncodeScreencastFrame(const SkBitmap
& bitmap
,
71 const std::string
& format
,
73 std::vector
<unsigned char> data
;
74 SkAutoLockPixels
lock_image(bitmap
);
77 encoded
= gfx::PNGCodec::Encode(
78 reinterpret_cast<unsigned char*>(bitmap
.getAddr32(0, 0)),
79 gfx::PNGCodec::FORMAT_SkBitmap
,
80 gfx::Size(bitmap
.width(), bitmap
.height()),
81 bitmap
.width() * bitmap
.bytesPerPixel(),
82 false, std::vector
<gfx::PNGCodec::Comment
>(), &data
);
83 } else if (format
== kJpeg
) {
84 encoded
= gfx::JPEGCodec::Encode(
85 reinterpret_cast<unsigned char*>(bitmap
.getAddr32(0, 0)),
86 gfx::JPEGCodec::FORMAT_SkBitmap
,
89 bitmap
.width() * bitmap
.bytesPerPixel(),
98 std::string base_64_data
;
100 base::StringPiece(reinterpret_cast<char*>(&data
[0]), data
.size()),
108 typedef DevToolsProtocolClient::Response Response
;
110 PageHandler::PageHandler()
112 touch_emulation_enabled_(false),
113 screencast_enabled_(false),
114 screencast_quality_(kDefaultScreenshotQuality
),
115 screencast_max_width_(-1),
116 screencast_max_height_(-1),
117 capture_retry_count_(0),
118 has_compositor_frame_metadata_(false),
119 screencast_frame_sent_(0),
120 screencast_frame_acked_(0),
121 processing_screencast_frame_(false),
122 color_picker_(new ColorPicker(base::Bind(
123 &PageHandler::OnColorPicked
, base::Unretained(this)))),
125 weak_factory_(this) {
128 PageHandler::~PageHandler() {
131 void PageHandler::SetRenderViewHost(RenderViewHostImpl
* host
) {
135 color_picker_
->SetRenderViewHost(host
);
137 UpdateTouchEventEmulationState();
140 void PageHandler::SetClient(scoped_ptr
<Client
> client
) {
141 client_
.swap(client
);
144 void PageHandler::Detached() {
148 void PageHandler::OnSwapCompositorFrame(
149 const cc::CompositorFrameMetadata
& frame_metadata
) {
150 last_compositor_frame_metadata_
= has_compositor_frame_metadata_
?
151 next_compositor_frame_metadata_
: frame_metadata
;
152 next_compositor_frame_metadata_
= frame_metadata
;
153 has_compositor_frame_metadata_
= true;
155 if (screencast_enabled_
)
156 InnerSwapCompositorFrame();
157 color_picker_
->OnSwapCompositorFrame();
160 void PageHandler::OnVisibilityChanged(bool visible
) {
161 if (!screencast_enabled_
)
163 NotifyScreencastVisibility(visible
);
166 void PageHandler::DidAttachInterstitialPage() {
169 client_
->InterstitialShown(InterstitialShownParams::Create());
172 void PageHandler::DidDetachInterstitialPage() {
175 client_
->InterstitialHidden(InterstitialHiddenParams::Create());
178 Response
PageHandler::Enable() {
180 return Response::FallThrough();
183 Response
PageHandler::Disable() {
185 touch_emulation_enabled_
= false;
186 screencast_enabled_
= false;
187 UpdateTouchEventEmulationState();
188 color_picker_
->SetEnabled(false);
189 return Response::FallThrough();
192 Response
PageHandler::Reload(const bool* ignoreCache
,
193 const std::string
* script_to_evaluate_on_load
,
194 const std::string
* script_preprocessor
) {
196 return Response::InternalError("Could not connect to view");
198 WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
);
200 return Response::InternalError("No WebContents to reload");
202 // Handle in browser only if it is crashed.
203 if (!web_contents
->IsCrashed())
204 return Response::FallThrough();
206 web_contents
->GetController().Reload(false);
207 return Response::OK();
210 Response
PageHandler::Navigate(const std::string
& url
,
213 if (!gurl
.is_valid())
214 return Response::InternalError("Cannot navigate to invalid URL");
217 return Response::InternalError("Could not connect to view");
219 WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
);
221 return Response::InternalError("No WebContents to navigate");
223 web_contents
->GetController()
224 .LoadURL(gurl
, Referrer(), ui::PAGE_TRANSITION_TYPED
, std::string());
225 return Response::FallThrough();
228 Response
PageHandler::GetNavigationHistory(int* current_index
,
229 NavigationEntries
* entries
) {
231 return Response::InternalError("Could not connect to view");
233 WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
);
235 return Response::InternalError("No WebContents to navigate");
237 NavigationController
& controller
= web_contents
->GetController();
238 *current_index
= controller
.GetCurrentEntryIndex();
239 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
240 entries
->push_back(NavigationEntry::Create()
241 ->set_id(controller
.GetEntryAtIndex(i
)->GetUniqueID())
242 ->set_url(controller
.GetEntryAtIndex(i
)->GetURL().spec())
244 base::UTF16ToUTF8(controller
.GetEntryAtIndex(i
)->GetTitle())));
246 return Response::OK();
249 Response
PageHandler::NavigateToHistoryEntry(int entry_id
) {
251 return Response::InternalError("Could not connect to view");
253 WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
);
255 return Response::InternalError("No WebContents to navigate");
257 NavigationController
& controller
= web_contents
->GetController();
258 for (int i
= 0; i
!= controller
.GetEntryCount(); ++i
) {
259 if (controller
.GetEntryAtIndex(i
)->GetUniqueID() == entry_id
) {
260 controller
.GoToIndex(i
);
261 return Response::OK();
265 return Response::InvalidParams("No entry with passed id");
268 Response
PageHandler::SetGeolocationOverride(double* latitude
,
272 return Response::InternalError("Could not connect to view");
274 WebContentsImpl
* web_contents
= static_cast<WebContentsImpl
*>(
275 WebContents::FromRenderViewHost(host_
));
277 return Response::InternalError("No WebContents to override");
279 GeolocationServiceContext
* geolocation_context
=
280 web_contents
->GetGeolocationServiceContext();
281 scoped_ptr
<Geoposition
> geoposition(new Geoposition());
282 if (latitude
&& longitude
&& accuracy
) {
283 geoposition
->latitude
= *latitude
;
284 geoposition
->longitude
= *longitude
;
285 geoposition
->accuracy
= *accuracy
;
286 geoposition
->timestamp
= base::Time::Now();
287 if (!geoposition
->Validate()) {
288 return Response::InternalError("Invalid geolocation");
291 geoposition
->error_code
= Geoposition::ERROR_CODE_POSITION_UNAVAILABLE
;
293 geolocation_context
->SetOverride(geoposition
.Pass());
294 return Response::OK();
297 Response
PageHandler::ClearGeolocationOverride() {
299 return Response::InternalError("Could not connect to view");
301 WebContentsImpl
* web_contents
= static_cast<WebContentsImpl
*>(
302 WebContents::FromRenderViewHost(host_
));
304 return Response::InternalError("No WebContents to override");
306 GeolocationServiceContext
* geolocation_context
=
307 web_contents
->GetGeolocationServiceContext();
308 geolocation_context
->ClearOverride();
309 return Response::OK();
312 Response
PageHandler::SetTouchEmulationEnabled(bool enabled
) {
313 touch_emulation_enabled_
= enabled
;
314 UpdateTouchEventEmulationState();
315 return Response::FallThrough();
318 Response
PageHandler::SetTouchEmulationEnabled(
319 bool enabled
, const std::string
* configuration
) {
320 touch_emulation_enabled_
= enabled
;
321 touch_emulation_configuration_
=
322 configuration
? *configuration
: std::string();
323 UpdateTouchEventEmulationState();
324 return Response::FallThrough();
327 Response
PageHandler::CaptureScreenshot(DevToolsCommandId command_id
) {
328 if (!host_
|| !host_
->GetView())
329 return Response::InternalError("Could not connect to view");
331 host_
->GetSnapshotFromBrowser(
332 base::Bind(&PageHandler::ScreenshotCaptured
,
333 weak_factory_
.GetWeakPtr(), command_id
));
334 return Response::OK();
337 Response
PageHandler::CanScreencast(bool* result
) {
338 #if defined(OS_ANDROID)
342 #endif // defined(OS_ANDROID)
343 return Response::OK();
346 Response
PageHandler::CanEmulate(bool* result
) {
347 #if defined(OS_ANDROID)
351 if (WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
)) {
352 *result
= !web_contents
->GetVisibleURL().SchemeIs(kChromeDevToolsScheme
);
359 #endif // defined(OS_ANDROID)
360 return Response::OK();
363 Response
PageHandler::StartScreencast(const std::string
* format
,
365 const int* max_width
,
366 const int* max_height
) {
368 return Response::InternalError("Could not connect to view");
370 screencast_enabled_
= true;
371 screencast_format_
= format
? *format
: kPng
;
372 screencast_quality_
= quality
? *quality
: kDefaultScreenshotQuality
;
373 if (screencast_quality_
< 0 || screencast_quality_
> 100)
374 screencast_quality_
= kDefaultScreenshotQuality
;
375 screencast_max_width_
= max_width
? *max_width
: -1;
376 screencast_max_height_
= max_height
? *max_height
: -1;
378 UpdateTouchEventEmulationState();
379 bool visible
= !host_
->is_hidden();
380 NotifyScreencastVisibility(visible
);
382 if (has_compositor_frame_metadata_
)
383 InnerSwapCompositorFrame();
385 host_
->Send(new ViewMsg_ForceRedraw(host_
->GetRoutingID(), 0));
387 return Response::FallThrough();
390 Response
PageHandler::StopScreencast() {
391 screencast_enabled_
= false;
392 UpdateTouchEventEmulationState();
393 return Response::FallThrough();
396 Response
PageHandler::ScreencastFrameAck(int frame_number
) {
397 screencast_frame_acked_
= frame_number
;
398 return Response::OK();
401 Response
PageHandler::HandleJavaScriptDialog(bool accept
,
402 const std::string
* prompt_text
) {
403 base::string16 prompt_override
;
405 prompt_override
= base::UTF8ToUTF16(*prompt_text
);
408 return Response::InternalError("Could not connect to view");
410 WebContents
* web_contents
= WebContents::FromRenderViewHost(host_
);
412 return Response::InternalError("No JavaScript dialog to handle");
414 JavaScriptDialogManager
* manager
=
415 web_contents
->GetDelegate()->GetJavaScriptDialogManager(web_contents
);
416 if (manager
&& manager
->HandleJavaScriptDialog(
417 web_contents
, accept
, prompt_text
? &prompt_override
: nullptr)) {
418 return Response::OK();
421 return Response::InternalError("Could not handle JavaScript dialog");
424 Response
PageHandler::QueryUsageAndQuota(DevToolsCommandId command_id
,
425 const std::string
& security_origin
) {
427 return Response::InternalError("Could not connect to view");
429 scoped_refptr
<storage::QuotaManager
> quota_manager
=
430 host_
->GetProcess()->GetStoragePartition()->GetQuotaManager();
432 BrowserThread::PostTask(
435 base::Bind(&QueryUsageAndQuotaOnIOThread
,
437 GURL(security_origin
),
438 base::Bind(&PageHandler::QueryUsageAndQuotaCompleted
,
439 weak_factory_
.GetWeakPtr(),
441 return Response::OK();
444 Response
PageHandler::SetColorPickerEnabled(bool enabled
) {
446 return Response::InternalError("Could not connect to view");
448 color_picker_
->SetEnabled(enabled
);
449 return Response::OK();
452 void PageHandler::UpdateTouchEventEmulationState() {
455 bool enabled
= touch_emulation_enabled_
|| screencast_enabled_
;
456 // TODO(dgozman): pass |touch_emulation_configuration_| once supported.
457 host_
->SetTouchEventEmulationEnabled(enabled
);
458 WebContentsImpl
* web_contents
= static_cast<WebContentsImpl
*>(
459 WebContents::FromRenderViewHost(host_
));
461 web_contents
->SetForceDisableOverscrollContent(enabled
);
464 void PageHandler::NotifyScreencastVisibility(bool visible
) {
466 capture_retry_count_
= kCaptureRetryLimit
;
467 client_
->ScreencastVisibilityChanged(
468 ScreencastVisibilityChangedParams::Create()->set_visible(visible
));
471 void PageHandler::InnerSwapCompositorFrame() {
472 if (screencast_frame_sent_
- screencast_frame_acked_
>
473 kMaxScreencastFramesInFlight
|| processing_screencast_frame_
) {
477 if (!host_
|| !host_
->GetView())
480 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
482 // TODO(vkuzkokov): do not use previous frame metadata.
483 cc::CompositorFrameMetadata
& metadata
= last_compositor_frame_metadata_
;
485 gfx::SizeF viewport_size_dip
= gfx::ScaleSize(
486 metadata
.scrollable_viewport_size
, metadata
.page_scale_factor
);
487 gfx::SizeF screen_size_dip
= gfx::ScaleSize(view
->GetPhysicalBackingSize(),
488 1 / metadata
.device_scale_factor
);
490 blink::WebScreenInfo screen_info
;
491 view
->GetScreenInfo(&screen_info
);
492 double device_scale_factor
= screen_info
.deviceScaleFactor
;
495 if (screencast_max_width_
> 0) {
496 double max_width_dip
= screencast_max_width_
/ device_scale_factor
;
497 scale
= std::min(scale
, max_width_dip
/ screen_size_dip
.width());
499 if (screencast_max_height_
> 0) {
500 double max_height_dip
= screencast_max_height_
/ device_scale_factor
;
501 scale
= std::min(scale
, max_height_dip
/ screen_size_dip
.height());
507 gfx::Size
snapshot_size_dip(gfx::ToRoundedSize(
508 gfx::ScaleSize(viewport_size_dip
, scale
)));
510 if (snapshot_size_dip
.width() > 0 && snapshot_size_dip
.height() > 0) {
511 processing_screencast_frame_
= true;
512 gfx::Rect
viewport_bounds_dip(gfx::ToRoundedSize(viewport_size_dip
));
513 view
->CopyFromCompositingSurface(
516 base::Bind(&PageHandler::ScreencastFrameCaptured
,
517 weak_factory_
.GetWeakPtr(),
518 last_compositor_frame_metadata_
),
523 void PageHandler::ScreencastFrameCaptured(
524 const cc::CompositorFrameMetadata
& metadata
,
525 const SkBitmap
& bitmap
,
526 ReadbackResponse response
) {
527 if (response
!= READBACK_SUCCESS
) {
528 processing_screencast_frame_
= false;
529 if (capture_retry_count_
) {
530 --capture_retry_count_
;
531 base::MessageLoop::current()->PostDelayedTask(
533 base::Bind(&PageHandler::InnerSwapCompositorFrame
,
534 weak_factory_
.GetWeakPtr()),
535 base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs
));
539 base::PostTaskAndReplyWithResult(
540 base::WorkerPool::GetTaskRunner(true).get(),
542 base::Bind(&EncodeScreencastFrame
,
543 bitmap
, screencast_format_
, screencast_quality_
),
544 base::Bind(&PageHandler::ScreencastFrameEncoded
,
545 weak_factory_
.GetWeakPtr(), metadata
, base::Time::Now()));
548 void PageHandler::ScreencastFrameEncoded(
549 const cc::CompositorFrameMetadata
& metadata
,
550 const base::Time
& timestamp
,
551 const std::string
& data
) {
552 processing_screencast_frame_
= false;
554 // Consider metadata empty in case it has no device scale factor.
555 if (metadata
.device_scale_factor
== 0 || !host_
|| data
.empty())
558 RenderWidgetHostViewBase
* view
= static_cast<RenderWidgetHostViewBase
*>(
563 gfx::SizeF screen_size_dip
= gfx::ScaleSize(
564 view
->GetPhysicalBackingSize(), 1 / metadata
.device_scale_factor
);
565 scoped_refptr
<ScreencastFrameMetadata
> param_metadata
=
566 ScreencastFrameMetadata::Create()
567 ->set_page_scale_factor(metadata
.page_scale_factor
)
568 ->set_offset_top(metadata
.location_bar_content_translation
.y())
569 ->set_device_width(screen_size_dip
.width())
570 ->set_device_height(screen_size_dip
.height())
571 ->set_scroll_offset_x(metadata
.root_scroll_offset
.x())
572 ->set_scroll_offset_y(metadata
.root_scroll_offset
.y())
573 ->set_timestamp(timestamp
.ToDoubleT());
574 client_
->ScreencastFrame(ScreencastFrameParams::Create()
576 ->set_metadata(param_metadata
)
577 ->set_frame_number(++screencast_frame_sent_
));
580 void PageHandler::ScreenshotCaptured(DevToolsCommandId command_id
,
581 const unsigned char* png_data
,
583 if (!png_data
|| !png_size
) {
584 client_
->SendError(command_id
,
585 Response::InternalError("Unable to capture screenshot"));
589 std::string base_64_data
;
591 base::StringPiece(reinterpret_cast<const char*>(png_data
), png_size
),
594 client_
->SendCaptureScreenshotResponse(command_id
,
595 CaptureScreenshotResponse::Create()->set_data(base_64_data
));
598 void PageHandler::OnColorPicked(int r
, int g
, int b
, int a
) {
599 scoped_refptr
<dom::RGBA
> color
=
600 dom::RGBA::Create()->set_r(r
)->set_g(g
)->set_b(b
)->set_a(a
);
601 client_
->ColorPicked(ColorPickedParams::Create()->set_color(color
));
604 void PageHandler::QueryUsageAndQuotaCompleted(
605 DevToolsCommandId command_id
,
606 scoped_refptr
<QueryUsageAndQuotaResponse
> response_data
) {
607 client_
->SendQueryUsageAndQuotaResponse(command_id
, response_data
);
611 } // namespace devtools
612 } // namespace content