blob: b37fca1b4bb975aa2d8990613405606ef469442c [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/base_switches.h"
#include "base/bind.h"
#include "base/command_line.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 {
enum class ImageEncoding { kPng, kJpeg };
constexpr int kDefaultScreenshotQuality = 80;
protocol::Binary 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 protocol::Binary();
return protocol::Binary::fromRefCounted(data);
}
void OnBeginFrameFinished(
std::unique_ptr<HeadlessHandler::BeginFrameCallback> callback,
ImageEncoding encoding,
int quality,
bool has_damage,
std::unique_ptr<SkBitmap> bitmap,
std::string error_message) {
if (!error_message.empty()) {
callback->sendFailure(Response::ServerError(std::move(error_message)));
return;
}
if (!bitmap || bitmap->drawsNothing()) {
callback->sendSuccess(has_damage, Maybe<protocol::Binary>());
return;
}
callback->sendSuccess(has_damage, EncodeBitmap(*bitmap, encoding, quality));
}
} // namespace
HeadlessHandler::HeadlessHandler(HeadlessBrowserImpl* browser,
content::WebContents* web_contents)
: browser_(browser), web_contents_(web_contents) {}
HeadlessHandler::~HeadlessHandler() {}
void HeadlessHandler::Wire(UberDispatcher* dispatcher) {
frontend_.reset(new HeadlessExperimental::Frontend(dispatcher->channel()));
HeadlessExperimental::Dispatcher::wire(dispatcher, this);
}
Response HeadlessHandler::Enable() {
if (frontend_)
frontend_->NeedsBeginFramesChanged(true);
return Response::Success();
}
Response HeadlessHandler::Disable() {
return Response::Success();
}
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_, web_contents_);
if (!headless_contents->begin_frame_control_enabled()) {
callback->sendFailure(Response::ServerError(
"Command is only supported if BeginFrameControl is enabled."));
return;
}
if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kRunAllCompositorStagesBeforeDraw)) {
callback->sendFailure(
Response::ServerError("Command is only supported with "
"--run-all-compositor-stages-before-draw, see "
"https://goo.gl/3zHXhB for more info."));
return;
}
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;
}
}
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