Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(186)

Side by Side Diff: headless/app/headless_shell.cc

Issue 2035733002: headless: Implement screenshot capturing (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Move async test, write screenshot to file. Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <iostream> 5 #include <iostream>
6 #include <memory> 6 #include <memory>
7 #include <string>
7 8
9 #include "base/base64.h"
8 #include "base/bind.h" 10 #include "base/bind.h"
9 #include "base/callback.h" 11 #include "base/callback.h"
10 #include "base/command_line.h" 12 #include "base/command_line.h"
13 #include "base/files/file_path.h"
11 #include "base/json/json_writer.h" 14 #include "base/json/json_writer.h"
12 #include "base/location.h" 15 #include "base/location.h"
13 #include "base/memory/ref_counted.h" 16 #include "base/memory/ref_counted.h"
14 #include "base/numerics/safe_conversions.h" 17 #include "base/numerics/safe_conversions.h"
15 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/string_number_conversions.h"
16 #include "content/public/common/content_switches.h" 19 #include "content/public/common/content_switches.h"
17 #include "headless/app/headless_shell_switches.h" 20 #include "headless/app/headless_shell_switches.h"
18 #include "headless/public/domains/page.h" 21 #include "headless/public/domains/page.h"
19 #include "headless/public/domains/runtime.h" 22 #include "headless/public/domains/runtime.h"
20 #include "headless/public/headless_browser.h" 23 #include "headless/public/headless_browser.h"
21 #include "headless/public/headless_devtools_client.h" 24 #include "headless/public/headless_devtools_client.h"
22 #include "headless/public/headless_devtools_target.h" 25 #include "headless/public/headless_devtools_target.h"
23 #include "headless/public/headless_web_contents.h" 26 #include "headless/public/headless_web_contents.h"
27 #include "net/base/file_stream.h"
28 #include "net/base/io_buffer.h"
24 #include "net/base/ip_address.h" 29 #include "net/base/ip_address.h"
30 #include "net/base/net_errors.h"
25 #include "ui/gfx/geometry/size.h" 31 #include "ui/gfx/geometry/size.h"
26 32
27 using headless::HeadlessBrowser; 33 using headless::HeadlessBrowser;
28 using headless::HeadlessDevToolsClient; 34 using headless::HeadlessDevToolsClient;
29 using headless::HeadlessWebContents; 35 using headless::HeadlessWebContents;
30 namespace page = headless::page; 36 namespace page = headless::page;
31 namespace runtime = headless::runtime; 37 namespace runtime = headless::runtime;
32 38
33 namespace { 39 namespace {
34 // Address where to listen to incoming DevTools connections. 40 // Address where to listen to incoming DevTools connections.
35 const char kDevToolsHttpServerAddress[] = "127.0.0.1"; 41 const char kDevToolsHttpServerAddress[] = "127.0.0.1";
42 // Default file name for screenshot. Can be overriden by "--screenshot" switch.
43 const char kDefaultScreenshotFileName[] = "screenshot.png";
36 } 44 }
37 45
38 // A sample application which demonstrates the use of the headless API. 46 // A sample application which demonstrates the use of the headless API.
39 class HeadlessShell : public HeadlessWebContents::Observer, page::Observer { 47 class HeadlessShell : public HeadlessWebContents::Observer, page::Observer {
40 public: 48 public:
41 HeadlessShell() 49 HeadlessShell()
42 : browser_(nullptr), 50 : browser_(nullptr),
43 devtools_client_(HeadlessDevToolsClient::Create()), 51 devtools_client_(HeadlessDevToolsClient::Create()),
44 processed_page_ready_(false) {} 52 web_contents_(nullptr),
53 processed_page_ready_(false),
54 screenshot_file_stream_(nullptr) {}
Sami 2016/06/03 11:46:24 No need to initialize a unique_ptr. The web_conten
Eric Seckler 2016/06/03 13:40:38 Ack, removed :)
45 ~HeadlessShell() override {} 55 ~HeadlessShell() override {}
46 56
47 void OnStart(HeadlessBrowser* browser) { 57 void OnStart(HeadlessBrowser* browser) {
48 browser_ = browser; 58 browser_ = browser;
49 59
50 base::CommandLine::StringVector args = 60 base::CommandLine::StringVector args =
51 base::CommandLine::ForCurrentProcess()->GetArgs(); 61 base::CommandLine::ForCurrentProcess()->GetArgs();
52 62
53 const char kDefaultUrl[] = "about:blank"; 63 const char kDefaultUrl[] = "about:blank";
54 if (args.empty() || args[0].empty()) { 64 if (args.empty() || args[0].empty()) {
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
128 138
129 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 139 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
130 headless::switches::kDumpDom)) { 140 headless::switches::kDumpDom)) {
131 FetchDom(); 141 FetchDom();
132 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch( 142 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
133 headless::switches::kRepl)) { 143 headless::switches::kRepl)) {
134 std::cout 144 std::cout
135 << "Type a Javascript expression to evaluate or \"quit\" to exit." 145 << "Type a Javascript expression to evaluate or \"quit\" to exit."
136 << std::endl; 146 << std::endl;
137 InputExpression(); 147 InputExpression();
148 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
149 headless::switches::kScreenshot)) {
150 CaptureScreenshot();
138 } else { 151 } else {
139 Shutdown(); 152 Shutdown();
140 } 153 }
141 } 154 }
142 155
143 void FetchDom() { 156 void FetchDom() {
144 devtools_client_->GetRuntime()->Evaluate( 157 devtools_client_->GetRuntime()->Evaluate(
145 "document.body.innerHTML", 158 "document.body.innerHTML",
146 base::Bind(&HeadlessShell::OnDomFetched, base::Unretained(this))); 159 base::Bind(&HeadlessShell::OnDomFetched, base::Unretained(this)));
147 } 160 }
(...skipping 26 matching lines...) Expand all
174 } 187 }
175 188
176 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { 189 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) {
177 std::unique_ptr<base::Value> value = result->Serialize(); 190 std::unique_ptr<base::Value> value = result->Serialize();
178 std::string result_json; 191 std::string result_json;
179 base::JSONWriter::Write(*value, &result_json); 192 base::JSONWriter::Write(*value, &result_json);
180 std::cout << result_json << std::endl; 193 std::cout << result_json << std::endl;
181 InputExpression(); 194 InputExpression();
182 } 195 }
183 196
197 void CaptureScreenshot() {
198 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
199 page::CaptureScreenshotParams::Builder().Build(),
200 base::Bind(&HeadlessShell::OnScreenshotCaptured,
201 base::Unretained(this)));
202 }
203
204 void OnScreenshotCaptured(
205 std::unique_ptr<page::CaptureScreenshotResult> result) {
206 base::FilePath file_name =
207 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
208 headless::switches::kScreenshot);
209 if (file_name.empty()) {
210 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
211 }
212
213 screenshot_file_stream_.reset(
214 new net::FileStream(browser_->BrowserFileThread()));
215 const int open_result = screenshot_file_stream_->Open(
216 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
217 base::File::FLAG_ASYNC,
218 base::Bind(&HeadlessShell::OnScreenshotFileOpened,
219 base::Unretained(this), base::Passed(std::move(result)),
220 file_name));
221 if (open_result != net::ERR_IO_PENDING) {
222 OnScreenshotFileOpened(std::move(result), file_name, open_result);
Sami 2016/06/03 11:46:24 Does this work since you already moved |result| a
Eric Seckler 2016/06/03 13:40:38 Passing a nullptr now. From the comments in FileSt
223 }
224 }
225
226 void OnScreenshotFileOpened(
227 std::unique_ptr<page::CaptureScreenshotResult> result,
228 const base::FilePath file_name,
229 const int open_result) {
230 if (open_result != net::OK) {
231 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
232 << " was unsuccessful, could not open file: "
233 << net::ErrorToString(open_result);
Sami 2016/06/03 11:46:24 nit: you could early-out here to reduce the indent
Eric Seckler 2016/06/03 13:40:38 Done.
234 } else {
235 std::string decoded_png;
236 base::Base64Decode(result->GetData(), &decoded_png);
237 net::IOBufferWithSize* buf =
Sami 2016/06/03 11:46:24 Who owns |buf|?
Eric Seckler 2016/06/03 13:40:38 IOBuffers are refcounted, but passed as plain poin
238 new net::IOBufferWithSize(decoded_png.size());
239 memcpy(buf->data(), decoded_png.data(), decoded_png.size());
240 const int write_result = screenshot_file_stream_->Write(
241 buf, buf->size(), base::Bind(&HeadlessShell::OnScreenshotFileWritten,
242 base::Unretained(this), file_name));
243 if (write_result != net::ERR_IO_PENDING) {
244 OnScreenshotFileWritten(file_name, open_result);
Sami 2016/06/03 11:46:24 write_result?
Eric Seckler 2016/06/03 13:40:38 Good catch, done.
245 }
246 }
247 }
248
249 void OnScreenshotFileWritten(const base::FilePath file_name,
250 const int write_result) {
251 if (write_result < 0) {
252 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
253 << " was unsuccessful: " << net::ErrorToString(write_result);
254 } else {
255 std::cout << "Screenshot written to file " << file_name.value() << "."
256 << std::endl;
257 }
258 int close_result = screenshot_file_stream_->Close(base::Bind(
259 &HeadlessShell::OnScreenshotFileClosed, base::Unretained(this)));
260 if (close_result != net::ERR_IO_PENDING) {
261 OnScreenshotFileClosed(close_result);
262 }
263 }
264
265 void OnScreenshotFileClosed(const int close_result) { Shutdown(); }
266
184 bool RemoteDebuggingEnabled() const { 267 bool RemoteDebuggingEnabled() const {
185 const base::CommandLine& command_line = 268 const base::CommandLine& command_line =
186 *base::CommandLine::ForCurrentProcess(); 269 *base::CommandLine::ForCurrentProcess();
187 return command_line.HasSwitch(switches::kRemoteDebuggingPort); 270 return command_line.HasSwitch(switches::kRemoteDebuggingPort);
188 } 271 }
189 272
190 private: 273 private:
191 GURL url_; 274 GURL url_;
192 HeadlessBrowser* browser_; // Not owned. 275 HeadlessBrowser* browser_; // Not owned.
193 std::unique_ptr<HeadlessDevToolsClient> devtools_client_; 276 std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
194 HeadlessWebContents* web_contents_; 277 HeadlessWebContents* web_contents_;
195 bool processed_page_ready_; 278 bool processed_page_ready_;
279 std::unique_ptr<net::FileStream> screenshot_file_stream_;
196 280
197 DISALLOW_COPY_AND_ASSIGN(HeadlessShell); 281 DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
198 }; 282 };
199 283
200 int main(int argc, const char** argv) { 284 int main(int argc, const char** argv) {
201 HeadlessShell shell; 285 HeadlessShell shell;
202 HeadlessBrowser::Options::Builder builder(argc, argv); 286 HeadlessBrowser::Options::Builder builder(argc, argv);
203 287
204 // Enable devtools if requested. 288 // Enable devtools if requested.
205 base::CommandLine command_line(argc, argv); 289 base::CommandLine command_line(argc, argv);
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 327
244 if (command_line.HasSwitch(switches::kHostResolverRules)) { 328 if (command_line.HasSwitch(switches::kHostResolverRules)) {
245 builder.SetHostResolverRules( 329 builder.SetHostResolverRules(
246 command_line.GetSwitchValueASCII(switches::kHostResolverRules)); 330 command_line.GetSwitchValueASCII(switches::kHostResolverRules));
247 } 331 }
248 332
249 return HeadlessBrowserMain( 333 return HeadlessBrowserMain(
250 builder.Build(), 334 builder.Build(),
251 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); 335 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell)));
252 } 336 }
OLDNEW
« no previous file with comments | « no previous file | headless/app/headless_shell_switches.h » ('j') | headless/test/headless_browser_test.h » ('J')

Powered by Google App Engine
This is Rietveld 408576698