Change next_proto member type.
[chromium-blink-merge.git] / content / browser / devtools / protocol / page_handler.cc
blobba8da7f55edd9a78c77432fe600d6035f6d5690c
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"
7 #include <string>
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"
37 #include "url/gurl.h"
39 namespace content {
40 namespace devtools {
41 namespace page {
43 namespace {
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(
64 quota_manager,
65 security_origin,
66 base::Bind(&QueryUsageAndQuotaCompletedOnIOThread,
67 callback));
70 std::string EncodeScreencastFrame(const SkBitmap& bitmap,
71 const std::string& format,
72 int quality) {
73 std::vector<unsigned char> data;
74 SkAutoLockPixels lock_image(bitmap);
75 bool encoded;
76 if (format == kPng) {
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,
87 bitmap.width(),
88 bitmap.height(),
89 bitmap.width() * bitmap.bytesPerPixel(),
90 quality, &data);
91 } else {
92 encoded = false;
95 if (!encoded)
96 return std::string();
98 std::string base_64_data;
99 base::Base64Encode(
100 base::StringPiece(reinterpret_cast<char*>(&data[0]), data.size()),
101 &base_64_data);
103 return base_64_data;
106 } // namespace
108 typedef DevToolsProtocolClient::Response Response;
110 PageHandler::PageHandler()
111 : enabled_(false),
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)))),
124 host_(nullptr),
125 weak_factory_(this) {
128 PageHandler::~PageHandler() {
131 void PageHandler::SetRenderViewHost(RenderViewHostImpl* host) {
132 if (host_ == host)
133 return;
135 color_picker_->SetRenderViewHost(host);
136 host_ = host;
137 UpdateTouchEventEmulationState();
140 void PageHandler::SetClient(scoped_ptr<Client> client) {
141 client_.swap(client);
144 void PageHandler::Detached() {
145 Disable();
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_)
162 return;
163 NotifyScreencastVisibility(visible);
166 void PageHandler::DidAttachInterstitialPage() {
167 if (!enabled_)
168 return;
169 client_->InterstitialShown(InterstitialShownParams::Create());
172 void PageHandler::DidDetachInterstitialPage() {
173 if (!enabled_)
174 return;
175 client_->InterstitialHidden(InterstitialHiddenParams::Create());
178 Response PageHandler::Enable() {
179 enabled_ = true;
180 return Response::FallThrough();
183 Response PageHandler::Disable() {
184 enabled_ = false;
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) {
195 if (!host_)
196 return Response::InternalError("Could not connect to view");
198 WebContents* web_contents = WebContents::FromRenderViewHost(host_);
199 if (!web_contents)
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,
211 FrameId* frame_id) {
212 GURL gurl(url);
213 if (!gurl.is_valid())
214 return Response::InternalError("Cannot navigate to invalid URL");
216 if (!host_)
217 return Response::InternalError("Could not connect to view");
219 WebContents* web_contents = WebContents::FromRenderViewHost(host_);
220 if (!web_contents)
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) {
230 if (!host_)
231 return Response::InternalError("Could not connect to view");
233 WebContents* web_contents = WebContents::FromRenderViewHost(host_);
234 if (!web_contents)
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())
243 ->set_title(
244 base::UTF16ToUTF8(controller.GetEntryAtIndex(i)->GetTitle())));
246 return Response::OK();
249 Response PageHandler::NavigateToHistoryEntry(int entry_id) {
250 if (!host_)
251 return Response::InternalError("Could not connect to view");
253 WebContents* web_contents = WebContents::FromRenderViewHost(host_);
254 if (!web_contents)
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,
269 double* longitude,
270 double* accuracy) {
271 if (!host_)
272 return Response::InternalError("Could not connect to view");
274 WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
275 WebContents::FromRenderViewHost(host_));
276 if (!web_contents)
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");
290 } else {
291 geoposition->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
293 geolocation_context->SetOverride(geoposition.Pass());
294 return Response::OK();
297 Response PageHandler::ClearGeolocationOverride() {
298 if (!host_)
299 return Response::InternalError("Could not connect to view");
301 WebContentsImpl* web_contents = static_cast<WebContentsImpl*>(
302 WebContents::FromRenderViewHost(host_));
303 if (!web_contents)
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)
339 *result = true;
340 #else
341 *result = false;
342 #endif // defined(OS_ANDROID)
343 return Response::OK();
346 Response PageHandler::CanEmulate(bool* result) {
347 #if defined(OS_ANDROID)
348 *result = false;
349 #else
350 if (host_) {
351 if (WebContents* web_contents = WebContents::FromRenderViewHost(host_)) {
352 *result = !web_contents->GetVisibleURL().SchemeIs(kChromeDevToolsScheme);
353 } else {
354 *result = true;
356 } else {
357 *result = true;
359 #endif // defined(OS_ANDROID)
360 return Response::OK();
363 Response PageHandler::StartScreencast(const std::string* format,
364 const int* quality,
365 const int* max_width,
366 const int* max_height) {
367 if (!host_)
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);
381 if (visible) {
382 if (has_compositor_frame_metadata_)
383 InnerSwapCompositorFrame();
384 else
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;
404 if (prompt_text)
405 prompt_override = base::UTF8ToUTF16(*prompt_text);
407 if (!host_)
408 return Response::InternalError("Could not connect to view");
410 WebContents* web_contents = WebContents::FromRenderViewHost(host_);
411 if (!web_contents)
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) {
426 if (!host_)
427 return Response::InternalError("Could not connect to view");
429 scoped_refptr<storage::QuotaManager> quota_manager =
430 host_->GetProcess()->GetStoragePartition()->GetQuotaManager();
432 BrowserThread::PostTask(
433 BrowserThread::IO,
434 FROM_HERE,
435 base::Bind(&QueryUsageAndQuotaOnIOThread,
436 quota_manager,
437 GURL(security_origin),
438 base::Bind(&PageHandler::QueryUsageAndQuotaCompleted,
439 weak_factory_.GetWeakPtr(),
440 command_id)));
441 return Response::OK();
444 Response PageHandler::SetColorPickerEnabled(bool enabled) {
445 if (!host_)
446 return Response::InternalError("Could not connect to view");
448 color_picker_->SetEnabled(enabled);
449 return Response::OK();
452 void PageHandler::UpdateTouchEventEmulationState() {
453 if (!host_)
454 return;
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_));
460 if (web_contents)
461 web_contents->SetForceDisableOverscrollContent(enabled);
464 void PageHandler::NotifyScreencastVisibility(bool visible) {
465 if (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_) {
474 return;
477 if (!host_ || !host_->GetView())
478 return;
480 RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
481 host_->GetView());
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;
493 double scale = 1;
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());
504 if (scale <= 0)
505 scale = 0.1;
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(
514 viewport_bounds_dip,
515 snapshot_size_dip,
516 base::Bind(&PageHandler::ScreencastFrameCaptured,
517 weak_factory_.GetWeakPtr(),
518 last_compositor_frame_metadata_),
519 kN32_SkColorType);
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(
532 FROM_HERE,
533 base::Bind(&PageHandler::InnerSwapCompositorFrame,
534 weak_factory_.GetWeakPtr()),
535 base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs));
537 return;
539 base::PostTaskAndReplyWithResult(
540 base::WorkerPool::GetTaskRunner(true).get(),
541 FROM_HERE,
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())
556 return;
558 RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
559 host_->GetView());
560 if (!view)
561 return;
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()
575 ->set_data(data)
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,
582 size_t png_size) {
583 if (!png_data || !png_size) {
584 client_->SendError(command_id,
585 Response::InternalError("Unable to capture screenshot"));
586 return;
589 std::string base_64_data;
590 base::Base64Encode(
591 base::StringPiece(reinterpret_cast<const char*>(png_data), png_size),
592 &base_64_data);
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);
610 } // namespace page
611 } // namespace devtools
612 } // namespace content