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

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

Issue 2687083002: Headless: make URLRequestDispatcher aware of navigations (Closed)
Patch Set: Pulled ShellNavigationRequest out into a separate file Created 3 years, 10 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 <memory> 5 #include <memory>
6 #include <sstream> 6 #include <sstream>
7 #include <string> 7 #include <string>
8 8
9 #include "base/base64.h" 9 #include "base/base64.h"
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/callback.h" 11 #include "base/callback.h"
12 #include "base/command_line.h" 12 #include "base/command_line.h"
13 #include "base/files/file_path.h" 13 #include "base/files/file_path.h"
14 #include "base/json/json_writer.h" 14 #include "base/json/json_writer.h"
15 #include "base/location.h" 15 #include "base/location.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/memory/weak_ptr.h" 16 #include "base/memory/weak_ptr.h"
19 #include "base/numerics/safe_conversions.h" 17 #include "base/numerics/safe_conversions.h"
20 #include "base/strings/string_number_conversions.h" 18 #include "base/strings/string_number_conversions.h"
19 #include "headless/app/headless_shell.h"
21 #include "headless/app/headless_shell_switches.h" 20 #include "headless/app/headless_shell_switches.h"
22 #include "headless/public/devtools/domains/emulation.h"
23 #include "headless/public/devtools/domains/inspector.h"
24 #include "headless/public/devtools/domains/page.h"
25 #include "headless/public/devtools/domains/runtime.h"
26 #include "headless/public/headless_browser.h"
27 #include "headless/public/headless_devtools_client.h"
28 #include "headless/public/headless_devtools_target.h" 21 #include "headless/public/headless_devtools_target.h"
29 #include "headless/public/headless_web_contents.h"
30 #include "headless/public/util/deterministic_dispatcher.h"
31 #include "headless/public/util/deterministic_http_protocol_handler.h" 22 #include "headless/public/util/deterministic_http_protocol_handler.h"
32 #include "net/base/file_stream.h"
33 #include "net/base/io_buffer.h" 23 #include "net/base/io_buffer.h"
34 #include "net/base/ip_address.h" 24 #include "net/base/ip_address.h"
35 #include "net/base/net_errors.h" 25 #include "net/base/net_errors.h"
36 #include "ui/gfx/geometry/size.h" 26 #include "ui/gfx/geometry/size.h"
37 27
38 namespace headless { 28 namespace headless {
29 class HeadlessShell;
Sami 2017/02/09 15:12:52 Unneeded?
alex clarke (OOO till 29th) 2017/02/09 15:55:45 Done.
30
39 namespace { 31 namespace {
40 // Address where to listen to incoming DevTools connections. 32 // Address where to listen to incoming DevTools connections.
41 const char kDevToolsHttpServerAddress[] = "127.0.0.1"; 33 const char kDevToolsHttpServerAddress[] = "127.0.0.1";
42 // Default file name for screenshot. Can be overriden by "--screenshot" switch. 34 // Default file name for screenshot. Can be overriden by "--screenshot" switch.
43 const char kDefaultScreenshotFileName[] = "screenshot.png"; 35 const char kDefaultScreenshotFileName[] = "screenshot.png";
44 36
45 bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) { 37 bool ParseWindowSize(std::string window_size, gfx::Size* parsed_window_size) {
46 int width, height = 0; 38 int width, height = 0;
47 if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 && 39 if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 &&
48 width >= 0 && height >= 0) { 40 width >= 0 && height >= 0) {
49 parsed_window_size->set_width(width); 41 parsed_window_size->set_width(width);
50 parsed_window_size->set_height(height); 42 parsed_window_size->set_height(height);
51 return true; 43 return true;
52 } 44 }
53 return false; 45 return false;
54 } 46 }
55 } // namespace 47 } // namespace
56 48
57 // An application which implements a simple headless browser. 49 HeadlessShell::HeadlessShell()
58 class HeadlessShell : public HeadlessWebContents::Observer, 50 : browser_(nullptr),
59 emulation::ExperimentalObserver, 51 devtools_client_(HeadlessDevToolsClient::Create()),
60 inspector::ExperimentalObserver, 52 web_contents_(nullptr),
61 page::Observer { 53 processed_page_ready_(false),
62 public: 54 browser_context_(nullptr),
63 HeadlessShell() 55 weak_factory_(this) {}
64 : browser_(nullptr), 56
65 devtools_client_(HeadlessDevToolsClient::Create()), 57 HeadlessShell::~HeadlessShell() {}
66 web_contents_(nullptr), 58
67 processed_page_ready_(false), 59 void HeadlessShell::OnStart(HeadlessBrowser* browser) {
68 browser_context_(nullptr), 60 browser_ = browser;
69 weak_factory_(this) {} 61
70 ~HeadlessShell() override {} 62 HeadlessBrowserContext::Builder context_builder =
71 63 browser_->CreateBrowserContextBuilder();
72 void OnStart(HeadlessBrowser* browser) { 64 // TODO(eseckler): These switches should also affect BrowserContexts that
73 browser_ = browser; 65 // are created via DevTools later.
74 66 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
75 HeadlessBrowserContext::Builder context_builder = 67 switches::kDeterministicFetch)) {
76 browser_->CreateBrowserContextBuilder(); 68 deterministic_dispatcher_.reset(
77 // TODO(eseckler): These switches should also affect BrowserContexts that 69 new DeterministicDispatcher(browser_->BrowserIOThread()));
78 // are created via DevTools later. 70
79 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 71 ProtocolHandlerMap protocol_handlers;
80 switches::kDeterministicFetch)) { 72 protocol_handlers[url::kHttpScheme] =
81 deterministic_dispatcher_.reset( 73 base::MakeUnique<DeterministicHttpProtocolHandler>(
82 new DeterministicDispatcher(browser_->BrowserIOThread())); 74 deterministic_dispatcher_.get(), browser->BrowserIOThread());
83 75 protocol_handlers[url::kHttpsScheme] =
84 ProtocolHandlerMap protocol_handlers; 76 base::MakeUnique<DeterministicHttpProtocolHandler>(
85 protocol_handlers[url::kHttpScheme] = 77 deterministic_dispatcher_.get(), browser->BrowserIOThread());
86 base::MakeUnique<DeterministicHttpProtocolHandler>( 78
87 deterministic_dispatcher_.get(), browser->BrowserIOThread()); 79 context_builder.SetProtocolHandlers(std::move(protocol_handlers));
88 protocol_handlers[url::kHttpsScheme] = 80 }
89 base::MakeUnique<DeterministicHttpProtocolHandler>( 81 browser_context_ = context_builder.Build();
90 deterministic_dispatcher_.get(), browser->BrowserIOThread()); 82 browser_->SetDefaultBrowserContext(browser_context_);
91 83
92 context_builder.SetProtocolHandlers(std::move(protocol_handlers)); 84 HeadlessWebContents::Builder builder(
93 } 85 browser_context_->CreateWebContentsBuilder());
94 browser_context_ = context_builder.Build(); 86 base::CommandLine::StringVector args =
95 browser_->SetDefaultBrowserContext(browser_context_); 87 base::CommandLine::ForCurrentProcess()->GetArgs();
96 88
97 HeadlessWebContents::Builder builder( 89 // TODO(alexclarke): Should we navigate to about:blank first if using
98 browser_context_->CreateWebContentsBuilder()); 90 // virtual time?
99 base::CommandLine::StringVector args = 91 if (args.empty())
100 base::CommandLine::ForCurrentProcess()->GetArgs(); 92 args.push_back("about:blank");
101 93 for (auto it = args.rbegin(); it != args.rend(); ++it) {
102 // TODO(alexclarke): Should we navigate to about:blank first if using 94 GURL url(*it);
103 // virtual time? 95 HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
104 if (args.empty()) 96 if (!web_contents) {
105 args.push_back("about:blank"); 97 LOG(ERROR) << "Navigation to " << url << " failed";
106 for (auto it = args.rbegin(); it != args.rend(); ++it) { 98 browser_->Shutdown();
107 GURL url(*it);
108 HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
109 if (!web_contents) {
110 LOG(ERROR) << "Navigation to " << url << " failed";
111 browser_->Shutdown();
112 return;
113 }
114 if (!web_contents_ && !RemoteDebuggingEnabled()) {
115 // TODO(jzfeng): Support observing multiple targets.
116 url_ = url;
117 web_contents_ = web_contents;
118 web_contents_->AddObserver(this);
119 }
120 }
121 }
122
123 void Shutdown() {
124 if (!web_contents_)
125 return; 99 return;
126 if (!RemoteDebuggingEnabled()) { 100 }
127 devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this); 101 if (!web_contents_ && !RemoteDebuggingEnabled()) {
128 devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this); 102 // TODO(jzfeng): Support observing multiple targets.
129 devtools_client_->GetPage()->RemoveObserver(this); 103 url_ = url;
130 if (web_contents_->GetDevToolsTarget()) { 104 web_contents_ = web_contents;
131 web_contents_->GetDevToolsTarget()->DetachClient( 105 web_contents_->AddObserver(this);
132 devtools_client_.get()); 106 }
133 } 107 }
134 } 108 }
135 web_contents_->RemoveObserver(this); 109
136 web_contents_ = nullptr; 110 void HeadlessShell::Shutdown() {
137 browser_context_->Close(); 111 if (!web_contents_)
138 browser_->Shutdown(); 112 return;
139 } 113 if (!RemoteDebuggingEnabled()) {
140 114 devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
141 // HeadlessWebContents::Observer implementation: 115 devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this);
142 void DevToolsTargetReady() override { 116 devtools_client_->GetPage()->GetExperimental()->RemoveObserver(this);
143 web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get()); 117 if (web_contents_->GetDevToolsTarget()) {
144 devtools_client_->GetInspector()->GetExperimental()->AddObserver(this); 118 web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
145 devtools_client_->GetPage()->AddObserver(this); 119 }
146 devtools_client_->GetPage()->Enable(); 120 }
147 // Check if the document had already finished loading by the time we 121 web_contents_->RemoveObserver(this);
148 // attached. 122 web_contents_ = nullptr;
149 123 browser_context_->Close();
150 devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this); 124 browser_->Shutdown();
151 125 }
152 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 126
153 switches::kVirtualTimeBudget)) { 127 void HeadlessShell::DevToolsTargetReady() {
154 std::string budget_ms_ascii = 128 web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
155 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 129 devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
156 switches::kVirtualTimeBudget); 130 devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
157 int budget_ms; 131 devtools_client_->GetPage()->Enable();
158 CHECK(base::StringToInt(budget_ms_ascii, &budget_ms)) 132 // Check if the document had already finished loading by the time we
159 << "Expected an integer value for --virtual-time-budget="; 133 // attached.
160 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( 134
161 emulation::SetVirtualTimePolicyParams::Builder() 135 devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
162 .SetPolicy(emulation::VirtualTimePolicy:: 136
163 PAUSE_IF_NETWORK_FETCHES_PENDING) 137 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
164 .SetBudget(budget_ms) 138 switches::kDeterministicFetch)) {
165 .Build()); 139 devtools_client_->GetPage()->GetExperimental()->SetControlNavigations(
166 } else { 140 headless::page::SetControlNavigationsParams::Builder()
167 PollReadyState(); 141 .SetEnabled(true)
168 } 142 .Build());
169 143 }
170 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) { 144
171 std::string timeout_ms_ascii = 145 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
172 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 146 switches::kVirtualTimeBudget)) {
173 switches::kTimeout); 147 std::string budget_ms_ascii =
174 int timeout_ms; 148 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
175 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms)) 149 switches::kVirtualTimeBudget);
176 << "Expected an integer value for --timeout="; 150 int budget_ms;
177 browser_->BrowserMainThread()->PostDelayedTask( 151 CHECK(base::StringToInt(budget_ms_ascii, &budget_ms))
178 FROM_HERE, 152 << "Expected an integer value for --virtual-time-budget=";
179 base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()), 153 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
180 base::TimeDelta::FromMilliseconds(timeout_ms)); 154 emulation::SetVirtualTimePolicyParams::Builder()
181 } 155 .SetPolicy(
182 156 emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING)
183 // TODO(skyostil): Implement more features to demonstrate the devtools API. 157 .SetBudget(budget_ms)
184 } 158 .Build());
185 159 } else {
186 void FetchTimeout() { 160 PollReadyState();
187 LOG(INFO) << "Timeout."; 161 }
188 devtools_client_->GetPage()->GetExperimental()->StopLoading( 162
189 page::StopLoadingParams::Builder().Build()); 163 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) {
190 } 164 std::string timeout_ms_ascii =
191 165 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
192 void OnTargetCrashed(const inspector::TargetCrashedParams& params) override { 166 switches::kTimeout);
193 LOG(ERROR) << "Abnormal renderer termination."; 167 int timeout_ms;
194 // NB this never gets called if remote debugging is enabled. 168 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms))
169 << "Expected an integer value for --timeout=";
170 browser_->BrowserMainThread()->PostDelayedTask(
171 FROM_HERE,
172 base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()),
173 base::TimeDelta::FromMilliseconds(timeout_ms));
174 }
175
176 // TODO(skyostil): Implement more features to demonstrate the devtools API.
177 }
178
179 void HeadlessShell::FetchTimeout() {
180 LOG(INFO) << "Timeout.";
181 devtools_client_->GetPage()->GetExperimental()->StopLoading(
182 page::StopLoadingParams::Builder().Build());
183 }
184
185 void HeadlessShell::OnTargetCrashed(
186 const inspector::TargetCrashedParams& params) {
187 LOG(ERROR) << "Abnormal renderer termination.";
188 // NB this never gets called if remote debugging is enabled.
189 Shutdown();
190 }
191
192 void HeadlessShell::PollReadyState() {
193 // We need to check the current location in addition to the ready state to
194 // be sure the expected page is ready.
195 devtools_client_->GetRuntime()->Evaluate(
196 "document.readyState + ' ' + document.location.href",
197 base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr()));
198 }
199
200 void HeadlessShell::OnReadyState(
201 std::unique_ptr<runtime::EvaluateResult> result) {
202 std::string ready_state_and_url;
203 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) {
204 std::stringstream stream(ready_state_and_url);
205 std::string ready_state;
206 std::string url;
207 stream >> ready_state;
208 stream >> url;
209
210 if (ready_state == "complete" &&
211 (url_.spec() == url || url != "about:blank")) {
212 OnPageReady();
213 return;
214 }
215 }
216 }
217
218 // emulation::Observer implementation:
219 void HeadlessShell::OnVirtualTimeBudgetExpired(
220 const emulation::VirtualTimeBudgetExpiredParams& params) {
221 OnPageReady();
222 }
223
224 // page::Observer implementation:
225 void HeadlessShell::OnLoadEventFired(const page::LoadEventFiredParams& params) {
226 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
227 switches::kVirtualTimeBudget)) {
228 return;
229 }
230 OnPageReady();
231 }
232
233 void HeadlessShell::OnNavigationRequested(
234 const headless::page::NavigationRequestedParams& params) {
235 deterministic_dispatcher_->NavigationRequested(
236 base::MakeUnique<ShellNavigationRequest>(weak_factory_.GetWeakPtr(),
237 params));
238 }
239
240 void HeadlessShell::OnPageReady() {
241 if (processed_page_ready_)
242 return;
243 processed_page_ready_ = true;
244
245 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
246 FetchDom();
247 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
248 switches::kRepl)) {
249 LOG(INFO)
250 << "Type a Javascript expression to evaluate or \"quit\" to exit.";
251 InputExpression();
252 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
253 switches::kScreenshot)) {
254 CaptureScreenshot();
255 } else {
195 Shutdown(); 256 Shutdown();
196 } 257 }
197 258 }
198 void PollReadyState() { 259
199 // We need to check the current location in addition to the ready state to 260 void HeadlessShell::FetchDom() {
200 // be sure the expected page is ready. 261 devtools_client_->GetRuntime()->Evaluate(
201 devtools_client_->GetRuntime()->Evaluate( 262 "document.body.innerHTML",
202 "document.readyState + ' ' + document.location.href", 263 base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
203 base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr())); 264 }
204 } 265
205 266 void HeadlessShell::OnDomFetched(
206 void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) { 267 std::unique_ptr<runtime::EvaluateResult> result) {
207 std::string ready_state_and_url; 268 if (result->HasExceptionDetails()) {
208 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { 269 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
209 std::stringstream stream(ready_state_and_url); 270 << result->GetExceptionDetails()->GetText();
210 std::string ready_state; 271 } else {
211 std::string url; 272 std::string dom;
212 stream >> ready_state; 273 if (result->GetResult()->GetValue()->GetAsString(&dom)) {
213 stream >> url; 274 printf("%s\n", dom.c_str());
214 275 }
215 if (ready_state == "complete" && 276 }
216 (url_.spec() == url || url != "about:blank")) { 277 Shutdown();
217 OnPageReady(); 278 }
218 return; 279
219 } 280 void HeadlessShell::InputExpression() {
220 } 281 // Note that a real system should read user input asynchronously, because
221 } 282 // otherwise all other browser activity is suspended (e.g., page loading).
222 283 printf(">>> ");
223 // emulation::Observer implementation: 284 std::stringstream expression;
224 void OnVirtualTimeBudgetExpired( 285 while (true) {
225 const emulation::VirtualTimeBudgetExpiredParams& params) override { 286 int c = fgetc(stdin);
226 OnPageReady(); 287 if (c == EOF || c == '\n') {
227 } 288 break;
228 289 }
229 // page::Observer implementation: 290 expression << static_cast<char>(c);
230 void OnLoadEventFired(const page::LoadEventFiredParams& params) override { 291 }
231 if (base::CommandLine::ForCurrentProcess()->HasSwitch( 292 if (expression.str() == "quit") {
232 switches::kVirtualTimeBudget)) {
233 return;
234 }
235 OnPageReady();
236 }
237
238 void OnPageReady() {
239 if (processed_page_ready_)
240 return;
241 processed_page_ready_ = true;
242
243 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
244 FetchDom();
245 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
246 switches::kRepl)) {
247 LOG(INFO)
248 << "Type a Javascript expression to evaluate or \"quit\" to exit.";
249 InputExpression();
250 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
251 switches::kScreenshot)) {
252 CaptureScreenshot();
253 } else {
254 Shutdown();
255 }
256 }
257
258 void FetchDom() {
259 devtools_client_->GetRuntime()->Evaluate(
260 "document.body.innerHTML",
261 base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
262 }
263
264 void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) {
265 if (result->HasExceptionDetails()) {
266 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: "
267 << result->GetExceptionDetails()->GetText();
268 } else {
269 std::string dom;
270 if (result->GetResult()->GetValue()->GetAsString(&dom)) {
271 printf("%s\n", dom.c_str());
272 }
273 }
274 Shutdown(); 293 Shutdown();
275 } 294 return;
276 295 }
277 void InputExpression() { 296 devtools_client_->GetRuntime()->Evaluate(
278 // Note that a real system should read user input asynchronously, because 297 expression.str(), base::Bind(&HeadlessShell::OnExpressionResult,
279 // otherwise all other browser activity is suspended (e.g., page loading). 298 weak_factory_.GetWeakPtr()));
280 printf(">>> "); 299 }
281 std::stringstream expression; 300
282 while (true) { 301 void HeadlessShell::OnExpressionResult(
283 int c = fgetc(stdin); 302 std::unique_ptr<runtime::EvaluateResult> result) {
284 if (c == EOF || c == '\n') { 303 std::unique_ptr<base::Value> value = result->Serialize();
285 break; 304 std::string result_json;
286 } 305 base::JSONWriter::Write(*value, &result_json);
287 expression << static_cast<char>(c); 306 printf("%s\n", result_json.c_str());
288 } 307 InputExpression();
289 if (expression.str() == "quit") { 308 }
290 Shutdown(); 309
291 return; 310 void HeadlessShell::CaptureScreenshot() {
292 } 311 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
293 devtools_client_->GetRuntime()->Evaluate( 312 page::CaptureScreenshotParams::Builder().Build(),
294 expression.str(), base::Bind(&HeadlessShell::OnExpressionResult, 313 base::Bind(&HeadlessShell::OnScreenshotCaptured,
295 weak_factory_.GetWeakPtr())); 314 weak_factory_.GetWeakPtr()));
296 } 315 }
297 316
298 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { 317 void HeadlessShell::OnScreenshotCaptured(
299 std::unique_ptr<base::Value> value = result->Serialize(); 318 std::unique_ptr<page::CaptureScreenshotResult> result) {
300 std::string result_json; 319 base::FilePath file_name =
301 base::JSONWriter::Write(*value, &result_json); 320 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
302 printf("%s\n", result_json.c_str()); 321 switches::kScreenshot);
303 InputExpression(); 322 if (file_name.empty()) {
304 } 323 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName);
305 324 }
306 void CaptureScreenshot() { 325
307 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot( 326 screenshot_file_stream_.reset(
308 page::CaptureScreenshotParams::Builder().Build(), 327 new net::FileStream(browser_->BrowserFileThread()));
309 base::Bind(&HeadlessShell::OnScreenshotCaptured, 328 const int open_result = screenshot_file_stream_->Open(
310 weak_factory_.GetWeakPtr())); 329 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
311 } 330 base::File::FLAG_ASYNC,
312 331 base::Bind(&HeadlessShell::OnScreenshotFileOpened,
313 void OnScreenshotCaptured( 332 weak_factory_.GetWeakPtr(), base::Passed(std::move(result)),
314 std::unique_ptr<page::CaptureScreenshotResult> result) { 333 file_name));
315 base::FilePath file_name = 334 if (open_result != net::ERR_IO_PENDING) {
316 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( 335 // Operation could not be started.
317 switches::kScreenshot); 336 OnScreenshotFileOpened(nullptr, file_name, open_result);
318 if (file_name.empty()) { 337 }
319 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName); 338 }
320 } 339
321 340 void HeadlessShell::OnScreenshotFileOpened(
322 screenshot_file_stream_.reset( 341 std::unique_ptr<page::CaptureScreenshotResult> result,
323 new net::FileStream(browser_->BrowserFileThread())); 342 const base::FilePath file_name,
324 const int open_result = screenshot_file_stream_->Open( 343 const int open_result) {
325 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | 344 if (open_result != net::OK) {
326 base::File::FLAG_ASYNC, 345 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
327 base::Bind(&HeadlessShell::OnScreenshotFileOpened, 346 << " was unsuccessful, could not open file: "
328 weak_factory_.GetWeakPtr(), base::Passed(std::move(result)), 347 << net::ErrorToString(open_result);
329 file_name)); 348 return;
330 if (open_result != net::ERR_IO_PENDING) { 349 }
331 // Operation could not be started. 350
332 OnScreenshotFileOpened(nullptr, file_name, open_result); 351 std::string decoded_png;
333 } 352 base::Base64Decode(result->GetData(), &decoded_png);
334 } 353 scoped_refptr<net::IOBufferWithSize> buf =
335 354 new net::IOBufferWithSize(decoded_png.size());
336 void OnScreenshotFileOpened( 355 memcpy(buf->data(), decoded_png.data(), decoded_png.size());
337 std::unique_ptr<page::CaptureScreenshotResult> result, 356 const int write_result = screenshot_file_stream_->Write(
338 const base::FilePath file_name, 357 buf.get(), buf->size(),
339 const int open_result) { 358 base::Bind(&HeadlessShell::OnScreenshotFileWritten,
340 if (open_result != net::OK) { 359 weak_factory_.GetWeakPtr(), file_name, buf->size()));
341 LOG(ERROR) << "Writing screenshot to file " << file_name.value() 360 if (write_result != net::ERR_IO_PENDING) {
342 << " was unsuccessful, could not open file: " 361 // Operation may have completed successfully or failed.
343 << net::ErrorToString(open_result); 362 OnScreenshotFileWritten(file_name, buf->size(), write_result);
344 return; 363 }
345 } 364 }
346 365
347 std::string decoded_png; 366 void HeadlessShell::OnScreenshotFileWritten(const base::FilePath file_name,
348 base::Base64Decode(result->GetData(), &decoded_png); 367 const int length,
349 scoped_refptr<net::IOBufferWithSize> buf = 368 const int write_result) {
350 new net::IOBufferWithSize(decoded_png.size()); 369 if (write_result < length) {
351 memcpy(buf->data(), decoded_png.data(), decoded_png.size()); 370 // TODO(eseckler): Support recovering from partial writes.
352 const int write_result = screenshot_file_stream_->Write( 371 LOG(ERROR) << "Writing screenshot to file " << file_name.value()
353 buf.get(), buf->size(), 372 << " was unsuccessful: " << net::ErrorToString(write_result);
354 base::Bind(&HeadlessShell::OnScreenshotFileWritten, 373 } else {
355 weak_factory_.GetWeakPtr(), file_name, buf->size())); 374 LOG(INFO) << "Screenshot written to file " << file_name.value() << "."
356 if (write_result != net::ERR_IO_PENDING) { 375 << std::endl;
357 // Operation may have completed successfully or failed. 376 }
358 OnScreenshotFileWritten(file_name, buf->size(), write_result); 377 int close_result = screenshot_file_stream_->Close(base::Bind(
359 } 378 &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr()));
360 } 379 if (close_result != net::ERR_IO_PENDING) {
361 380 // Operation could not be started.
362 void OnScreenshotFileWritten(const base::FilePath file_name, 381 OnScreenshotFileClosed(close_result);
363 const int length, 382 }
364 const int write_result) { 383 }
365 if (write_result < length) { 384
366 // TODO(eseckler): Support recovering from partial writes. 385 void HeadlessShell::OnScreenshotFileClosed(const int close_result) {
367 LOG(ERROR) << "Writing screenshot to file " << file_name.value() 386 Shutdown();
368 << " was unsuccessful: " << net::ErrorToString(write_result); 387 }
369 } else { 388
370 LOG(INFO) << "Screenshot written to file " << file_name.value() << "." 389 bool HeadlessShell::RemoteDebuggingEnabled() const {
371 << std::endl; 390 const base::CommandLine& command_line =
372 } 391 *base::CommandLine::ForCurrentProcess();
373 int close_result = screenshot_file_stream_->Close(base::Bind( 392 return command_line.HasSwitch(switches::kRemoteDebuggingPort);
374 &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr())); 393 }
375 if (close_result != net::ERR_IO_PENDING) {
376 // Operation could not be started.
377 OnScreenshotFileClosed(close_result);
378 }
379 }
380
381 void OnScreenshotFileClosed(const int close_result) { Shutdown(); }
382
383 bool RemoteDebuggingEnabled() const {
384 const base::CommandLine& command_line =
385 *base::CommandLine::ForCurrentProcess();
386 return command_line.HasSwitch(switches::kRemoteDebuggingPort);
387 }
388
389 private:
390 GURL url_;
391 HeadlessBrowser* browser_; // Not owned.
392 std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
393 HeadlessWebContents* web_contents_;
394 bool processed_page_ready_;
395 std::unique_ptr<net::FileStream> screenshot_file_stream_;
396 HeadlessBrowserContext* browser_context_;
397 std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_;
398 base::WeakPtrFactory<HeadlessShell> weak_factory_;
399
400 DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
401 };
402 394
403 bool ValidateCommandLine(const base::CommandLine& command_line) { 395 bool ValidateCommandLine(const base::CommandLine& command_line) {
404 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) { 396 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort)) {
405 if (command_line.GetArgs().size() <= 1) 397 if (command_line.GetArgs().size() <= 1)
406 return true; 398 return true;
407 LOG(ERROR) << "Open multiple tabs is only supported when the " 399 LOG(ERROR) << "Open multiple tabs is only supported when the "
408 << "remote debug port is set."; 400 << "remote debug port is set.";
409 return false; 401 return false;
410 } 402 }
411 if (command_line.HasSwitch(switches::kDumpDom)) { 403 if (command_line.HasSwitch(switches::kDumpDom)) {
(...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after
516 builder.SetOverrideWebPreferencesCallback(base::Bind([]( 508 builder.SetOverrideWebPreferencesCallback(base::Bind([](
517 WebPreferences* preferences) { preferences->hide_scrollbars = true; })); 509 WebPreferences* preferences) { preferences->hide_scrollbars = true; }));
518 } 510 }
519 511
520 return HeadlessBrowserMain( 512 return HeadlessBrowserMain(
521 builder.Build(), 513 builder.Build(),
522 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); 514 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell)));
523 } 515 }
524 516
525 } // namespace headless 517 } // namespace headless
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698