blob: 4abf251565f46a70ed1ee7f067f76cb9aa25326b [file] [log] [blame]
// Copyright 2018 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 "headless/lib/browser/protocol/headless_handler.h"
#include "base/base64.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "cc/base/switches.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/switches.h"
#include "content/public/common/content_switches.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_util.h"
namespace headless {
namespace protocol {
using HeadlessExperimental::ScreenshotParams;
namespace {
base::LazyInstance<std::set<HeadlessHandler*>>::Leaky g_instances =
LAZY_INSTANCE_INITIALIZER;
enum class ImageEncoding { kPng, kJpeg };
constexpr int kDefaultScreenshotQuality = 80;
std::string EncodeBitmap(const SkBitmap& bitmap,
ImageEncoding encoding,
int quality) {
gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap);
DCHECK(!image.IsEmpty());
scoped_refptr<base::RefCountedMemory> data;
if (encoding == ImageEncoding::kPng) {
data = image.As1xPNGBytes();
} else if (encoding == ImageEncoding::kJpeg) {
scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes());
if (gfx::JPEG1xEncodedDataFromImage(image, quality, &bytes->data()))
data = bytes;
}
if (!data || !data->front())
return std::string();
std::string base_64_data;
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(data->front()),
data->size()),
&base_64_data);
return base_64_data;
}
void OnBeginFrameFinished(
std::unique_ptr<HeadlessHandler::BeginFrameCallback> callback,
ImageEncoding encoding,
int quality,
bool has_damage,
std::unique_ptr<SkBitmap> bitmap) {
if (!bitmap || bitmap->drawsNothing()) {
callback->sendSuccess(has_damage, Maybe<String>());
return;
}
std::string data = EncodeBitmap(*bitmap, encoding, quality);
callback->sendSuccess(has_damage, std::move(data));
}
} // namespace
// static
void HeadlessHandler::OnNeedsBeginFrames(
HeadlessWebContentsImpl* headless_contents,
bool needs_begin_frames) {
if (!g_instances.IsCreated())
return;
for (const HeadlessHandler* handler : g_instances.Get()) {
if (handler->enabled_ && handler->frontend_)
handler->frontend_->NeedsBeginFramesChanged(needs_begin_frames);
}
}
HeadlessHandler::HeadlessHandler(base::WeakPtr<HeadlessBrowserImpl> browser,
content::WebContents* web_contents)
: DomainHandler(HeadlessExperimental::Metainfo::domainName, browser),
web_contents_(web_contents) {}
HeadlessHandler::~HeadlessHandler() {
DCHECK(g_instances.Get().find(this) == g_instances.Get().end());
}
void HeadlessHandler::Wire(UberDispatcher* dispatcher) {
frontend_.reset(new HeadlessExperimental::Frontend(dispatcher->channel()));
HeadlessExperimental::Dispatcher::wire(dispatcher, this);
}
Response HeadlessHandler::Enable() {
g_instances.Get().insert(this);
HeadlessWebContentsImpl* headless_contents =
HeadlessWebContentsImpl::From(browser().get(), web_contents_);
enabled_ = true;
if (headless_contents->needs_external_begin_frames() && frontend_)
frontend_->NeedsBeginFramesChanged(true);
return Response::OK();
}
Response HeadlessHandler::Disable() {
enabled_ = false;
g_instances.Get().erase(this);
return Response::OK();
}
void HeadlessHandler::BeginFrame(Maybe<double> in_frame_time_ticks,
Maybe<double> in_interval,
Maybe<bool> in_no_display_updates,
Maybe<ScreenshotParams> screenshot,
std::unique_ptr<BeginFrameCallback> callback) {
HeadlessWebContentsImpl* headless_contents =
HeadlessWebContentsImpl::From(browser().get(), web_contents_);
if (!headless_contents->begin_frame_control_enabled()) {
callback->sendFailure(Response::Error(
"Command is only supported if BeginFrameControl is enabled."));
return;
}
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kRunAllCompositorStagesBeforeDraw)) {
LOG(WARNING) << "BeginFrameControl commands are designed to be used with "
"--run-all-compositor-stages-before-draw, see "
"https://goo.gl/3zHXhB for more info.";
}
base::TimeTicks frame_time_ticks;
base::TimeDelta interval;
bool no_display_updates = in_no_display_updates.fromMaybe(false);
if (in_frame_time_ticks.isJust()) {
frame_time_ticks = base::TimeTicks() + base::TimeDelta::FromMillisecondsD(
in_frame_time_ticks.fromJust());
} else {
frame_time_ticks = base::TimeTicks::Now();
}
if (in_interval.isJust()) {
double interval_double = in_interval.fromJust();
if (interval_double <= 0) {
callback->sendFailure(
Response::InvalidParams("interval has to be greater than 0"));
return;
}
interval = base::TimeDelta::FromMillisecondsD(interval_double);
} else {
interval = viz::BeginFrameArgs::DefaultInterval();
}
base::TimeTicks deadline = frame_time_ticks + interval;
bool capture_screenshot = false;
ImageEncoding encoding;
int quality;
if (screenshot.isJust()) {
capture_screenshot = true;
const std::string format =
screenshot.fromJust()->GetFormat(ScreenshotParams::FormatEnum::Png);
if (format != ScreenshotParams::FormatEnum::Png &&
format != ScreenshotParams::FormatEnum::Jpeg) {
callback->sendFailure(
Response::InvalidParams("Invalid screenshot.format"));
return;
}
encoding = format == ScreenshotParams::FormatEnum::Png
? ImageEncoding::kPng
: ImageEncoding::kJpeg;
quality = screenshot.fromJust()->GetQuality(kDefaultScreenshotQuality);
if (quality < 0 || quality > 100) {
callback->sendFailure(Response::InvalidParams(
"screenshot.quality has to be in range 0..100"));
return;
}
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kRunAllCompositorStagesBeforeDraw) &&
headless_contents->HasPendingFrame()) {
LOG(WARNING) << "A BeginFrame is already in flight. In "
"--run-all-compositor-stages-before-draw mode, only a "
"single BeginFrame should be active at the same time.";
}
headless_contents->BeginFrame(
frame_time_ticks, deadline, interval, no_display_updates,
capture_screenshot,
base::BindOnce(&OnBeginFrameFinished, std::move(callback), encoding,
quality));
}
} // namespace protocol
} // namespace headless