1 // Copyright 2018 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.
4 
5 #include "headless/lib/browser/protocol/headless_handler.h"
6 
7 #include "base/base_switches.h"
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "cc/base/switches.h"
11 #include "components/viz/common/frame_sinks/begin_frame_args.h"
12 #include "components/viz/common/switches.h"
13 #include "content/public/common/content_switches.h"
14 #include "headless/lib/browser/headless_browser_impl.h"
15 #include "headless/lib/browser/headless_web_contents_impl.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/gfx/codec/jpeg_codec.h"
19 #include "ui/gfx/codec/png_codec.h"
20 #include "ui/gfx/image/image.h"
21 #include "ui/gfx/image/image_util.h"
22 
23 namespace headless {
24 namespace protocol {
25 
26 using HeadlessExperimental::ScreenshotParams;
27 
28 namespace {
29 
30 
31 enum class ImageEncoding { kPng, kJpeg };
32 constexpr int kDefaultScreenshotQuality = 80;
33 
EncodeBitmap(const SkBitmap & bitmap,ImageEncoding encoding,int quality)34 protocol::Binary EncodeBitmap(const SkBitmap& bitmap,
35                               ImageEncoding encoding,
36                               int quality) {
37   gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap);
38   DCHECK(!image.IsEmpty());
39 
40   scoped_refptr<base::RefCountedMemory> data;
41   if (encoding == ImageEncoding::kPng) {
42     data = image.As1xPNGBytes();
43   } else if (encoding == ImageEncoding::kJpeg) {
44     scoped_refptr<base::RefCountedBytes> bytes(new base::RefCountedBytes());
45     if (gfx::JPEG1xEncodedDataFromImage(image, quality, &bytes->data()))
46       data = bytes;
47   }
48   if (!data || !data->front())
49     return protocol::Binary();
50   return protocol::Binary::fromRefCounted(data);
51 }
52 
OnBeginFrameFinished(std::unique_ptr<HeadlessHandler::BeginFrameCallback> callback,ImageEncoding encoding,int quality,bool has_damage,std::unique_ptr<SkBitmap> bitmap,std::string error_message)53 void OnBeginFrameFinished(
54     std::unique_ptr<HeadlessHandler::BeginFrameCallback> callback,
55     ImageEncoding encoding,
56     int quality,
57     bool has_damage,
58     std::unique_ptr<SkBitmap> bitmap,
59     std::string error_message) {
60   if (!error_message.empty()) {
61     callback->sendFailure(Response::ServerError(std::move(error_message)));
62     return;
63   }
64   if (!bitmap || bitmap->drawsNothing()) {
65     callback->sendSuccess(has_damage, Maybe<protocol::Binary>());
66     return;
67   }
68   callback->sendSuccess(has_damage, EncodeBitmap(*bitmap, encoding, quality));
69 }
70 
71 }  // namespace
72 
HeadlessHandler(HeadlessBrowserImpl * browser,content::WebContents * web_contents)73 HeadlessHandler::HeadlessHandler(HeadlessBrowserImpl* browser,
74                                  content::WebContents* web_contents)
75     : browser_(browser), web_contents_(web_contents) {}
76 
~HeadlessHandler()77 HeadlessHandler::~HeadlessHandler() {}
78 
Wire(UberDispatcher * dispatcher)79 void HeadlessHandler::Wire(UberDispatcher* dispatcher) {
80   frontend_.reset(new HeadlessExperimental::Frontend(dispatcher->channel()));
81   HeadlessExperimental::Dispatcher::wire(dispatcher, this);
82 }
83 
Enable()84 Response HeadlessHandler::Enable() {
85   if (frontend_)
86     frontend_->NeedsBeginFramesChanged(true);
87   return Response::Success();
88 }
89 
Disable()90 Response HeadlessHandler::Disable() {
91   return Response::Success();
92 }
93 
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)94 void HeadlessHandler::BeginFrame(Maybe<double> in_frame_time_ticks,
95                                  Maybe<double> in_interval,
96                                  Maybe<bool> in_no_display_updates,
97                                  Maybe<ScreenshotParams> screenshot,
98                                  std::unique_ptr<BeginFrameCallback> callback) {
99   HeadlessWebContentsImpl* headless_contents =
100       HeadlessWebContentsImpl::From(browser_, web_contents_);
101   if (!headless_contents->begin_frame_control_enabled()) {
102     callback->sendFailure(Response::ServerError(
103         "Command is only supported if BeginFrameControl is enabled."));
104     return;
105   }
106 
107   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
108           ::switches::kRunAllCompositorStagesBeforeDraw)) {
109     callback->sendFailure(
110         Response::ServerError("Command is only supported with "
111                               "--run-all-compositor-stages-before-draw, see "
112                               "https://goo.gl/3zHXhB for more info."));
113     return;
114   }
115 
116   base::TimeTicks frame_time_ticks;
117   base::TimeDelta interval;
118   bool no_display_updates = in_no_display_updates.fromMaybe(false);
119 
120   if (in_frame_time_ticks.isJust()) {
121     frame_time_ticks = base::TimeTicks() + base::TimeDelta::FromMillisecondsD(
122                                                in_frame_time_ticks.fromJust());
123   } else {
124     frame_time_ticks = base::TimeTicks::Now();
125   }
126 
127   if (in_interval.isJust()) {
128     double interval_double = in_interval.fromJust();
129     if (interval_double <= 0) {
130       callback->sendFailure(
131           Response::InvalidParams("interval has to be greater than 0"));
132       return;
133     }
134     interval = base::TimeDelta::FromMillisecondsD(interval_double);
135   } else {
136     interval = viz::BeginFrameArgs::DefaultInterval();
137   }
138 
139   base::TimeTicks deadline = frame_time_ticks + interval;
140 
141   bool capture_screenshot = false;
142   ImageEncoding encoding;
143   int quality;
144 
145   if (screenshot.isJust()) {
146     capture_screenshot = true;
147     const std::string format =
148         screenshot.fromJust()->GetFormat(ScreenshotParams::FormatEnum::Png);
149     if (format != ScreenshotParams::FormatEnum::Png &&
150         format != ScreenshotParams::FormatEnum::Jpeg) {
151       callback->sendFailure(
152           Response::InvalidParams("Invalid screenshot.format"));
153       return;
154     }
155     encoding = format == ScreenshotParams::FormatEnum::Png
156                    ? ImageEncoding::kPng
157                    : ImageEncoding::kJpeg;
158     quality = screenshot.fromJust()->GetQuality(kDefaultScreenshotQuality);
159     if (quality < 0 || quality > 100) {
160       callback->sendFailure(Response::InvalidParams(
161           "screenshot.quality has to be in range 0..100"));
162       return;
163     }
164   }
165 
166   headless_contents->BeginFrame(
167       frame_time_ticks, deadline, interval, no_display_updates,
168       capture_screenshot,
169       base::BindOnce(&OnBeginFrameFinished, std::move(callback), encoding,
170                      quality));
171 }
172 
173 }  // namespace protocol
174 }  // namespace headless
175