Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / browser / devtools / protocol / page_handler.cc
blob5325a66f8fda5917cdb34b9fd98e7e5792c46221
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/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"
38 #include "url/gurl.h"
40 namespace content {
41 namespace devtools {
42 namespace page {
44 namespace {
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,
55 int quality) {
56 std::vector<unsigned char> data;
57 SkAutoLockPixels lock_image(bitmap);
58 bool encoded;
59 if (format == kPng) {
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,
70 bitmap.width(),
71 bitmap.height(),
72 bitmap.width() * bitmap.bytesPerPixel(),
73 quality, &data);
74 } else {
75 encoded = false;
78 if (!encoded)
79 return std::string();
81 std::string base_64_data;
82 base::Base64Encode(
83 base::StringPiece(reinterpret_cast<char*>(&data[0]), data.size()),
84 &base_64_data);
86 return base_64_data;
89 } // namespace
91 typedef DevToolsProtocolClient::Response Response;
93 PageHandler::PageHandler()
94 : enabled_(false),
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)))),
106 host_(nullptr),
107 screencast_listener_(nullptr),
108 weak_factory_(this) {
111 PageHandler::~PageHandler() {
114 void PageHandler::SetRenderFrameHost(RenderFrameHostImpl* host) {
115 if (host_ == host)
116 return;
118 RenderWidgetHostImpl* widget_host =
119 host_ ? host_->GetRenderWidgetHost() : nullptr;
120 if (widget_host) {
121 registrar_.Remove(
122 this,
123 content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
124 content::Source<RenderWidgetHost>(widget_host));
127 host_ = host;
128 widget_host = host_ ? host_->GetRenderWidgetHost() : nullptr;
129 color_picker_->SetRenderWidgetHost(widget_host);
131 if (widget_host) {
132 registrar_.Add(
133 this,
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() {
144 Disable();
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_)
173 return;
174 DCHECK(type == content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED);
175 bool visible = *Details<bool>(details).ptr();
176 NotifyScreencastVisibility(visible);
179 void PageHandler::DidAttachInterstitialPage() {
180 if (!enabled_)
181 return;
182 client_->InterstitialShown(InterstitialShownParams::Create());
185 void PageHandler::DidDetachInterstitialPage() {
186 if (!enabled_)
187 return;
188 client_->InterstitialHidden(InterstitialHiddenParams::Create());
191 void PageHandler::SetScreencastListener(ScreencastListener* listener) {
192 screencast_listener_ = listener;
195 Response PageHandler::Enable() {
196 enabled_ = true;
197 return Response::FallThrough();
200 Response PageHandler::Disable() {
201 enabled_ = false;
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();
213 if (!web_contents)
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();
220 } else {
221 // Handle reload in renderer except for crashed and view source mode.
222 return Response::FallThrough();
226 Response PageHandler::Navigate(const std::string& url,
227 FrameId* frame_id) {
228 GURL gurl(url);
229 if (!gurl.is_valid())
230 return Response::InternalError("Cannot navigate to invalid URL");
232 WebContentsImpl* web_contents = GetWebContents();
233 if (!web_contents)
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();
244 if (!web_contents)
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())
253 ->set_title(
254 base::UTF16ToUTF8(controller.GetEntryAtIndex(i)->GetTitle())));
256 return Response::OK();
259 Response PageHandler::NavigateToHistoryEntry(int entry_id) {
260 WebContentsImpl* web_contents = GetWebContents();
261 if (!web_contents)
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)
287 *result = true;
288 #else
289 *result = false;
290 #endif // defined(OS_ANDROID)
291 return Response::OK();
294 Response PageHandler::StartScreencast(const std::string* format,
295 const int* quality,
296 const int* max_width,
297 const int* max_height) {
298 RenderWidgetHostImpl* widget_host =
299 host_ ? host_->GetRenderWidgetHost() : nullptr;
300 if (!widget_host)
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);
313 if (visible) {
314 if (has_compositor_frame_metadata_) {
315 InnerSwapCompositorFrame();
316 } else {
317 widget_host->Send(
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;
341 if (prompt_text)
342 prompt_override = base::UTF8ToUTF16(*prompt_text);
344 WebContentsImpl* web_contents = GetWebContents();
345 if (!web_contents)
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) {
364 if (!host_)
365 return Response::InternalError("Could not connect to view");
367 color_picker_->SetEnabled(enabled);
368 return Response::OK();
371 WebContentsImpl* PageHandler::GetWebContents() {
372 return host_ ?
373 static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_)) :
374 nullptr;
377 void PageHandler::NotifyScreencastVisibility(bool visible) {
378 if (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_) {
387 return;
390 if (!host_ || !host_->GetView())
391 return;
393 RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
394 host_->GetView());
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;
406 double scale = 1;
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());
417 if (scale <= 0)
418 scale = 0.1;
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(
427 viewport_bounds_dip,
428 snapshot_size_dip,
429 base::Bind(&PageHandler::ScreencastFrameCaptured,
430 weak_factory_.GetWeakPtr(),
431 last_compositor_frame_metadata_),
432 kN32_SkColorType);
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));
449 return;
451 base::PostTaskAndReplyWithResult(
452 base::WorkerPool::GetTaskRunner(true).get(),
453 FROM_HERE,
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())
468 return;
470 RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
471 host_->GetView());
472 if (!view)
473 return;
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()
487 ->set_data(data)
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,
494 size_t png_size) {
495 if (!png_data || !png_size) {
496 client_->SendError(command_id,
497 Response::InternalError("Unable to capture screenshot"));
498 return;
501 std::string base_64_data;
502 base::Base64Encode(
503 base::StringPiece(reinterpret_cast<const char*>(png_data), png_size),
504 &base_64_data);
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));
516 } // namespace page
517 } // namespace devtools
518 } // namespace content