blob: 63550c016f0bd2c3c622178cddb059747c47007d [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/protocol/page_handler.h"
#include <string>
#include "base/base64.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/worker_pool.h"
#include "content/browser/devtools/protocol/color_picker.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/referrer.h"
#include "third_party/WebKit/public/platform/WebScreenInfo.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/page_transition_types.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/snapshot/snapshot.h"
#include "url/gurl.h"
namespace content {
namespace devtools {
namespace page {
namespace {
static const char kPng[] = "png";
static const char kJpeg[] = "jpeg";
static int kDefaultScreenshotQuality = 80;
static int kFrameRetryDelayMs = 100;
static int kCaptureRetryLimit = 2;
static int kMaxScreencastFramesInFlight = 2;
std::string EncodeScreencastFrame(const SkBitmap& bitmap,
const std::string& format,
int quality) {
std::vector<unsigned char> data;
SkAutoLockPixels lock_image(bitmap);
bool encoded;
if (format == kPng) {
encoded = gfx::PNGCodec::Encode(
reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
gfx::PNGCodec::FORMAT_SkBitmap,
gfx::Size(bitmap.width(), bitmap.height()),
bitmap.width() * bitmap.bytesPerPixel(),
false, std::vector<gfx::PNGCodec::Comment>(), &data);
} else if (format == kJpeg) {
encoded = gfx::JPEGCodec::Encode(
reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)),
gfx::JPEGCodec::FORMAT_SkBitmap,
bitmap.width(),
bitmap.height(),
bitmap.width() * bitmap.bytesPerPixel(),
quality, &data);
} else {
encoded = false;
}
if (!encoded)
return std::string();
std::string base_64_data;
base::Base64Encode(
base::StringPiece(reinterpret_cast<char*>(&data[0]), data.size()),
&base_64_data);
return base_64_data;
}
} // namespace
typedef DevToolsProtocolClient::Response Response;
PageHandler::PageHandler()
: enabled_(false),
screencast_enabled_(false),
screencast_quality_(kDefaultScreenshotQuality),
screencast_max_width_(-1),
screencast_max_height_(-1),
capture_retry_count_(0),
has_compositor_frame_metadata_(false),
screencast_frame_sent_(0),
screencast_frame_acked_(0),
processing_screencast_frame_(false),
color_picker_(new ColorPicker(base::Bind(
&PageHandler::OnColorPicked, base::Unretained(this)))),
host_(nullptr),
screencast_listener_(nullptr),
weak_factory_(this) {
}
PageHandler::~PageHandler() {
}
void PageHandler::SetRenderFrameHost(RenderFrameHostImpl* host) {
if (host_ == host)
return;
RenderWidgetHostImpl* widget_host =
host_ ? host_->GetRenderWidgetHost() : nullptr;
if (widget_host) {
registrar_.Remove(
this,
content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
content::Source<RenderWidgetHost>(widget_host));
}
host_ = host;
widget_host = host_ ? host_->GetRenderWidgetHost() : nullptr;
color_picker_->SetRenderWidgetHost(widget_host);
if (widget_host) {
registrar_.Add(
this,
content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
content::Source<RenderWidgetHost>(widget_host));
}
}
void PageHandler::SetClient(scoped_ptr<Client> client) {
client_.swap(client);
}
void PageHandler::Detached() {
Disable();
}
void PageHandler::OnSwapCompositorFrame(
const cc::CompositorFrameMetadata& frame_metadata) {
last_compositor_frame_metadata_ = has_compositor_frame_metadata_ ?
next_compositor_frame_metadata_ : frame_metadata;
next_compositor_frame_metadata_ = frame_metadata;
has_compositor_frame_metadata_ = true;
if (screencast_enabled_)
InnerSwapCompositorFrame();
color_picker_->OnSwapCompositorFrame();
}
void PageHandler::Observe(int type,
const NotificationSource& source,
const NotificationDetails& details) {
if (!screencast_enabled_)
return;
DCHECK(type == content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED);
bool visible = *Details<bool>(details).ptr();
NotifyScreencastVisibility(visible);
}
void PageHandler::DidAttachInterstitialPage() {
if (!enabled_)
return;
client_->InterstitialShown(InterstitialShownParams::Create());
}
void PageHandler::DidDetachInterstitialPage() {
if (!enabled_)
return;
client_->InterstitialHidden(InterstitialHiddenParams::Create());
}
void PageHandler::SetScreencastListener(ScreencastListener* listener) {
screencast_listener_ = listener;
}
Response PageHandler::Enable() {
enabled_ = true;
return Response::FallThrough();
}
Response PageHandler::Disable() {
enabled_ = false;
screencast_enabled_ = false;
color_picker_->SetEnabled(false);
if (screencast_listener_)
screencast_listener_->ScreencastEnabledChanged();
return Response::FallThrough();
}
Response PageHandler::Reload(const bool* ignoreCache,
const std::string* script_to_evaluate_on_load,
const std::string* script_preprocessor) {
WebContentsImpl* web_contents = GetWebContents();
if (!web_contents)
return Response::InternalError("Could not connect to view");
// Handle in browser only if it is crashed.
if (!web_contents->IsCrashed())
return Response::FallThrough();
web_contents->GetController().Reload(false);
return Response::OK();
}
Response PageHandler::Navigate(const std::string& url,
FrameId* frame_id) {
GURL gurl(url);
if (!gurl.is_valid())
return Response::InternalError("Cannot navigate to invalid URL");
WebContentsImpl* web_contents = GetWebContents();
if (!web_contents)
return Response::InternalError("Could not connect to view");
web_contents->GetController()
.LoadURL(gurl, Referrer(), ui::PAGE_TRANSITION_TYPED, std::string());
return Response::FallThrough();
}
Response PageHandler::GetNavigationHistory(int* current_index,
NavigationEntries* entries) {
WebContentsImpl* web_contents = GetWebContents();
if (!web_contents)
return Response::InternalError("Could not connect to view");
NavigationController& controller = web_contents->GetController();
*current_index = controller.GetCurrentEntryIndex();
for (int i = 0; i != controller.GetEntryCount(); ++i) {
entries->push_back(NavigationEntry::Create()
->set_id(controller.GetEntryAtIndex(i)->GetUniqueID())
->set_url(controller.GetEntryAtIndex(i)->GetURL().spec())
->set_title(
base::UTF16ToUTF8(controller.GetEntryAtIndex(i)->GetTitle())));
}
return Response::OK();
}
Response PageHandler::NavigateToHistoryEntry(int entry_id) {
WebContentsImpl* web_contents = GetWebContents();
if (!web_contents)
return Response::InternalError("Could not connect to view");
NavigationController& controller = web_contents->GetController();
for (int i = 0; i != controller.GetEntryCount(); ++i) {
if (controller.GetEntryAtIndex(i)->GetUniqueID() == entry_id) {
controller.GoToIndex(i);
return Response::OK();
}
}
return Response::InvalidParams("No entry with passed id");
}
Response PageHandler::CaptureScreenshot(DevToolsCommandId command_id) {
if (!host_ || !host_->GetRenderWidgetHost())
return Response::InternalError("Could not connect to view");
host_->GetRenderWidgetHost()->GetSnapshotFromBrowser(
base::Bind(&PageHandler::ScreenshotCaptured,
weak_factory_.GetWeakPtr(), command_id));
return Response::OK();
}
Response PageHandler::CanScreencast(bool* result) {
#if defined(OS_ANDROID)
*result = true;
#else
*result = false;
#endif // defined(OS_ANDROID)
return Response::OK();
}
Response PageHandler::StartScreencast(const std::string* format,
const int* quality,
const int* max_width,
const int* max_height) {
RenderWidgetHostImpl* widget_host =
host_ ? host_->GetRenderWidgetHost() : nullptr;
if (!widget_host)
return Response::InternalError("Could not connect to view");
screencast_enabled_ = true;
screencast_format_ = format ? *format : kPng;
screencast_quality_ = quality ? *quality : kDefaultScreenshotQuality;
if (screencast_quality_ < 0 || screencast_quality_ > 100)
screencast_quality_ = kDefaultScreenshotQuality;
screencast_max_width_ = max_width ? *max_width : -1;
screencast_max_height_ = max_height ? *max_height : -1;
bool visible = !widget_host->is_hidden();
NotifyScreencastVisibility(visible);
if (visible) {
if (has_compositor_frame_metadata_) {
InnerSwapCompositorFrame();
} else {
widget_host->Send(
new ViewMsg_ForceRedraw(widget_host->GetRoutingID(), 0));
}
}
if (screencast_listener_)
screencast_listener_->ScreencastEnabledChanged();
return Response::FallThrough();
}
Response PageHandler::StopScreencast() {
screencast_enabled_ = false;
if (screencast_listener_)
screencast_listener_->ScreencastEnabledChanged();
return Response::FallThrough();
}
Response PageHandler::ScreencastFrameAck(int frame_number) {
screencast_frame_acked_ = frame_number;
return Response::OK();
}
Response PageHandler::HandleJavaScriptDialog(bool accept,
const std::string* prompt_text) {
base::string16 prompt_override;
if (prompt_text)
prompt_override = base::UTF8ToUTF16(*prompt_text);
WebContentsImpl* web_contents = GetWebContents();
if (!web_contents)
return Response::InternalError("Could not connect to view");
JavaScriptDialogManager* manager =
web_contents->GetDelegate()->GetJavaScriptDialogManager(web_contents);
if (manager && manager->HandleJavaScriptDialog(
web_contents, accept, prompt_text ? &prompt_override : nullptr)) {
return Response::OK();
}
return Response::InternalError("Could not handle JavaScript dialog");
}
Response PageHandler::QueryUsageAndQuota(DevToolsCommandId command_id,
const std::string& security_origin) {
return Response::OK();
}
Response PageHandler::SetColorPickerEnabled(bool enabled) {
if (!host_)
return Response::InternalError("Could not connect to view");
color_picker_->SetEnabled(enabled);
return Response::OK();
}
WebContentsImpl* PageHandler::GetWebContents() {
return host_ ?
static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(host_)) :
nullptr;
}
void PageHandler::NotifyScreencastVisibility(bool visible) {
if (visible)
capture_retry_count_ = kCaptureRetryLimit;
client_->ScreencastVisibilityChanged(
ScreencastVisibilityChangedParams::Create()->set_visible(visible));
}
void PageHandler::InnerSwapCompositorFrame() {
if (screencast_frame_sent_ - screencast_frame_acked_ >
kMaxScreencastFramesInFlight || processing_screencast_frame_) {
return;
}
if (!host_ || !host_->GetView())
return;
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
host_->GetView());
// TODO(vkuzkokov): do not use previous frame metadata.
cc::CompositorFrameMetadata& metadata = last_compositor_frame_metadata_;
gfx::SizeF viewport_size_dip = gfx::ScaleSize(
metadata.scrollable_viewport_size, metadata.page_scale_factor);
gfx::SizeF screen_size_dip = gfx::ScaleSize(view->GetPhysicalBackingSize(),
1 / metadata.device_scale_factor);
blink::WebScreenInfo screen_info;
view->GetScreenInfo(&screen_info);
double device_scale_factor = screen_info.deviceScaleFactor;
double scale = 1;
if (screencast_max_width_ > 0) {
double max_width_dip = screencast_max_width_ / device_scale_factor;
scale = std::min(scale, max_width_dip / screen_size_dip.width());
}
if (screencast_max_height_ > 0) {
double max_height_dip = screencast_max_height_ / device_scale_factor;
scale = std::min(scale, max_height_dip / screen_size_dip.height());
}
if (scale <= 0)
scale = 0.1;
gfx::Size snapshot_size_dip(gfx::ToRoundedSize(
gfx::ScaleSize(viewport_size_dip, scale)));
if (snapshot_size_dip.width() > 0 && snapshot_size_dip.height() > 0) {
processing_screencast_frame_ = true;
gfx::Rect viewport_bounds_dip(gfx::ToRoundedSize(viewport_size_dip));
view->CopyFromCompositingSurface(
viewport_bounds_dip,
snapshot_size_dip,
base::Bind(&PageHandler::ScreencastFrameCaptured,
weak_factory_.GetWeakPtr(),
last_compositor_frame_metadata_),
kN32_SkColorType);
}
}
void PageHandler::ScreencastFrameCaptured(
const cc::CompositorFrameMetadata& metadata,
const SkBitmap& bitmap,
ReadbackResponse response) {
if (response != READBACK_SUCCESS) {
processing_screencast_frame_ = false;
if (capture_retry_count_) {
--capture_retry_count_;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&PageHandler::InnerSwapCompositorFrame,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kFrameRetryDelayMs));
}
return;
}
base::PostTaskAndReplyWithResult(
base::WorkerPool::GetTaskRunner(true).get(),
FROM_HERE,
base::Bind(&EncodeScreencastFrame,
bitmap, screencast_format_, screencast_quality_),
base::Bind(&PageHandler::ScreencastFrameEncoded,
weak_factory_.GetWeakPtr(), metadata, base::Time::Now()));
}
void PageHandler::ScreencastFrameEncoded(
const cc::CompositorFrameMetadata& metadata,
const base::Time& timestamp,
const std::string& data) {
processing_screencast_frame_ = false;
// Consider metadata empty in case it has no device scale factor.
if (metadata.device_scale_factor == 0 || !host_ || data.empty())
return;
RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
host_->GetView());
if (!view)
return;
gfx::SizeF screen_size_dip = gfx::ScaleSize(
view->GetPhysicalBackingSize(), 1 / metadata.device_scale_factor);
scoped_refptr<ScreencastFrameMetadata> param_metadata =
ScreencastFrameMetadata::Create()
->set_page_scale_factor(metadata.page_scale_factor)
->set_offset_top(metadata.location_bar_content_translation.y())
->set_device_width(screen_size_dip.width())
->set_device_height(screen_size_dip.height())
->set_scroll_offset_x(metadata.root_scroll_offset.x())
->set_scroll_offset_y(metadata.root_scroll_offset.y())
->set_timestamp(timestamp.ToDoubleT());
client_->ScreencastFrame(ScreencastFrameParams::Create()
->set_data(data)
->set_metadata(param_metadata)
->set_frame_number(++screencast_frame_sent_));
}
void PageHandler::ScreenshotCaptured(DevToolsCommandId command_id,
const unsigned char* png_data,
size_t png_size) {
if (!png_data || !png_size) {
client_->SendError(command_id,
Response::InternalError("Unable to capture screenshot"));
return;
}
std::string base_64_data;
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(png_data), png_size),
&base_64_data);
client_->SendCaptureScreenshotResponse(command_id,
CaptureScreenshotResponse::Create()->set_data(base_64_data));
}
void PageHandler::OnColorPicked(int r, int g, int b, int a) {
scoped_refptr<dom::RGBA> color =
dom::RGBA::Create()->set_r(r)->set_g(g)->set_b(b)->set_a(a);
client_->ColorPicked(ColorPickedParams::Create()->set_color(color));
}
} // namespace page
} // namespace devtools
} // namespace content