OLD | NEW |
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 #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/ref_counted.h" | 16 #include "base/memory/ref_counted.h" |
| 17 #include "base/memory/weak_ptr.h" |
17 #include "base/numerics/safe_conversions.h" | 18 #include "base/numerics/safe_conversions.h" |
18 #include "base/strings/string_number_conversions.h" | 19 #include "base/strings/string_number_conversions.h" |
19 #include "content/public/common/content_switches.h" | 20 #include "content/public/common/content_switches.h" |
20 #include "headless/app/headless_shell_switches.h" | 21 #include "headless/app/headless_shell_switches.h" |
21 #include "headless/public/devtools/domains/emulation.h" | 22 #include "headless/public/devtools/domains/emulation.h" |
22 #include "headless/public/devtools/domains/inspector.h" | 23 #include "headless/public/devtools/domains/inspector.h" |
23 #include "headless/public/devtools/domains/page.h" | 24 #include "headless/public/devtools/domains/page.h" |
24 #include "headless/public/devtools/domains/runtime.h" | 25 #include "headless/public/devtools/domains/runtime.h" |
25 #include "headless/public/headless_browser.h" | 26 #include "headless/public/headless_browser.h" |
26 #include "headless/public/headless_devtools_client.h" | 27 #include "headless/public/headless_devtools_client.h" |
(...skipping 30 matching lines...) Expand all Loading... |
57 class HeadlessShell : public HeadlessWebContents::Observer, | 58 class HeadlessShell : public HeadlessWebContents::Observer, |
58 emulation::ExperimentalObserver, | 59 emulation::ExperimentalObserver, |
59 inspector::ExperimentalObserver, | 60 inspector::ExperimentalObserver, |
60 page::Observer { | 61 page::Observer { |
61 public: | 62 public: |
62 HeadlessShell() | 63 HeadlessShell() |
63 : browser_(nullptr), | 64 : browser_(nullptr), |
64 devtools_client_(HeadlessDevToolsClient::Create()), | 65 devtools_client_(HeadlessDevToolsClient::Create()), |
65 web_contents_(nullptr), | 66 web_contents_(nullptr), |
66 processed_page_ready_(false), | 67 processed_page_ready_(false), |
67 browser_context_(nullptr) {} | 68 browser_context_(nullptr), |
| 69 weak_factory_(this) {} |
68 ~HeadlessShell() override {} | 70 ~HeadlessShell() override {} |
69 | 71 |
70 void OnStart(HeadlessBrowser* browser) { | 72 void OnStart(HeadlessBrowser* browser) { |
71 browser_ = browser; | 73 browser_ = browser; |
72 | 74 |
73 HeadlessBrowserContext::Builder context_builder = | 75 HeadlessBrowserContext::Builder context_builder = |
74 browser_->CreateBrowserContextBuilder(); | 76 browser_->CreateBrowserContextBuilder(); |
75 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | 77 if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
76 switches::kDeterministicFetch)) { | 78 switches::kDeterministicFetch)) { |
77 deterministic_dispatcher_.reset( | 79 deterministic_dispatcher_.reset( |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
156 << "Expected an integer value for --virtual-time-budget="; | 158 << "Expected an integer value for --virtual-time-budget="; |
157 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( | 159 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy( |
158 emulation::SetVirtualTimePolicyParams::Builder() | 160 emulation::SetVirtualTimePolicyParams::Builder() |
159 .SetPolicy(emulation::VirtualTimePolicy:: | 161 .SetPolicy(emulation::VirtualTimePolicy:: |
160 PAUSE_IF_NETWORK_FETCHES_PENDING) | 162 PAUSE_IF_NETWORK_FETCHES_PENDING) |
161 .SetBudget(budget_ms) | 163 .SetBudget(budget_ms) |
162 .Build()); | 164 .Build()); |
163 } else { | 165 } else { |
164 PollReadyState(); | 166 PollReadyState(); |
165 } | 167 } |
| 168 |
| 169 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) { |
| 170 std::string timeout_ms_ascii = |
| 171 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| 172 switches::kTimeout); |
| 173 int timeout_ms; |
| 174 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms)) |
| 175 << "Expected an integer value for --timeout="; |
| 176 browser_->BrowserMainThread()->PostDelayedTask( |
| 177 FROM_HERE, |
| 178 base::Bind(&HeadlessShell::FetchTimeout, weak_factory_.GetWeakPtr()), |
| 179 base::TimeDelta::FromMilliseconds(timeout_ms)); |
| 180 } |
| 181 |
166 // TODO(skyostil): Implement more features to demonstrate the devtools API. | 182 // TODO(skyostil): Implement more features to demonstrate the devtools API. |
167 } | 183 } |
168 | 184 |
| 185 void FetchTimeout() { |
| 186 LOG(INFO) << "Timeout."; |
| 187 devtools_client_->GetPage()->GetExperimental()->StopLoading( |
| 188 page::StopLoadingParams::Builder().Build()); |
| 189 } |
| 190 |
169 void OnTargetCrashed(const inspector::TargetCrashedParams& params) override { | 191 void OnTargetCrashed(const inspector::TargetCrashedParams& params) override { |
170 LOG(ERROR) << "Abnormal renderer termination."; | 192 LOG(ERROR) << "Abnormal renderer termination."; |
171 // NB this never gets called if remote debugging is enabled. | 193 // NB this never gets called if remote debugging is enabled. |
172 Shutdown(); | 194 Shutdown(); |
173 } | 195 } |
174 | 196 |
175 void PollReadyState() { | 197 void PollReadyState() { |
176 // We need to check the current location in addition to the ready state to | 198 // We need to check the current location in addition to the ready state to |
177 // be sure the expected page is ready. | 199 // be sure the expected page is ready. |
178 devtools_client_->GetRuntime()->Evaluate( | 200 devtools_client_->GetRuntime()->Evaluate( |
179 "document.readyState + ' ' + document.location.href", | 201 "document.readyState + ' ' + document.location.href", |
180 base::Bind(&HeadlessShell::OnReadyState, base::Unretained(this))); | 202 base::Bind(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr())); |
181 } | 203 } |
182 | 204 |
183 void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) { | 205 void OnReadyState(std::unique_ptr<runtime::EvaluateResult> result) { |
184 std::string ready_state_and_url; | 206 std::string ready_state_and_url; |
185 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { | 207 if (result->GetResult()->GetValue()->GetAsString(&ready_state_and_url)) { |
186 std::stringstream stream(ready_state_and_url); | 208 std::stringstream stream(ready_state_and_url); |
187 std::string ready_state; | 209 std::string ready_state; |
188 std::string url; | 210 std::string url; |
189 stream >> ready_state; | 211 stream >> ready_state; |
190 stream >> url; | 212 stream >> url; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
229 switches::kScreenshot)) { | 251 switches::kScreenshot)) { |
230 CaptureScreenshot(); | 252 CaptureScreenshot(); |
231 } else { | 253 } else { |
232 Shutdown(); | 254 Shutdown(); |
233 } | 255 } |
234 } | 256 } |
235 | 257 |
236 void FetchDom() { | 258 void FetchDom() { |
237 devtools_client_->GetRuntime()->Evaluate( | 259 devtools_client_->GetRuntime()->Evaluate( |
238 "document.body.innerHTML", | 260 "document.body.innerHTML", |
239 base::Bind(&HeadlessShell::OnDomFetched, base::Unretained(this))); | 261 base::Bind(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr())); |
240 } | 262 } |
241 | 263 |
242 void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) { | 264 void OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result) { |
243 if (result->HasExceptionDetails()) { | 265 if (result->HasExceptionDetails()) { |
244 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: " | 266 LOG(ERROR) << "Failed to evaluate document.body.innerHTML: " |
245 << result->GetExceptionDetails()->GetText(); | 267 << result->GetExceptionDetails()->GetText(); |
246 } else { | 268 } else { |
247 std::string dom; | 269 std::string dom; |
248 if (result->GetResult()->GetValue()->GetAsString(&dom)) { | 270 if (result->GetResult()->GetValue()->GetAsString(&dom)) { |
249 std::cout << dom << std::endl; | 271 std::cout << dom << std::endl; |
250 } | 272 } |
251 } | 273 } |
252 Shutdown(); | 274 Shutdown(); |
253 } | 275 } |
254 | 276 |
255 void InputExpression() { | 277 void InputExpression() { |
256 // Note that a real system should read user input asynchronously, because | 278 // Note that a real system should read user input asynchronously, because |
257 // otherwise all other browser activity is suspended (e.g., page loading). | 279 // otherwise all other browser activity is suspended (e.g., page loading). |
258 std::string expression; | 280 std::string expression; |
259 std::cout << ">>> "; | 281 std::cout << ">>> "; |
260 std::getline(std::cin, expression); | 282 std::getline(std::cin, expression); |
261 if (std::cin.bad() || std::cin.eof() || expression == "quit") { | 283 if (std::cin.bad() || std::cin.eof() || expression == "quit") { |
262 Shutdown(); | 284 Shutdown(); |
263 return; | 285 return; |
264 } | 286 } |
265 devtools_client_->GetRuntime()->Evaluate( | 287 devtools_client_->GetRuntime()->Evaluate( |
266 expression, | 288 expression, base::Bind(&HeadlessShell::OnExpressionResult, |
267 base::Bind(&HeadlessShell::OnExpressionResult, base::Unretained(this))); | 289 weak_factory_.GetWeakPtr())); |
268 } | 290 } |
269 | 291 |
270 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { | 292 void OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result) { |
271 std::unique_ptr<base::Value> value = result->Serialize(); | 293 std::unique_ptr<base::Value> value = result->Serialize(); |
272 std::string result_json; | 294 std::string result_json; |
273 base::JSONWriter::Write(*value, &result_json); | 295 base::JSONWriter::Write(*value, &result_json); |
274 std::cout << result_json << std::endl; | 296 std::cout << result_json << std::endl; |
275 InputExpression(); | 297 InputExpression(); |
276 } | 298 } |
277 | 299 |
278 void CaptureScreenshot() { | 300 void CaptureScreenshot() { |
279 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot( | 301 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot( |
280 page::CaptureScreenshotParams::Builder().Build(), | 302 page::CaptureScreenshotParams::Builder().Build(), |
281 base::Bind(&HeadlessShell::OnScreenshotCaptured, | 303 base::Bind(&HeadlessShell::OnScreenshotCaptured, |
282 base::Unretained(this))); | 304 weak_factory_.GetWeakPtr())); |
283 } | 305 } |
284 | 306 |
285 void OnScreenshotCaptured( | 307 void OnScreenshotCaptured( |
286 std::unique_ptr<page::CaptureScreenshotResult> result) { | 308 std::unique_ptr<page::CaptureScreenshotResult> result) { |
287 base::FilePath file_name = | 309 base::FilePath file_name = |
288 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( | 310 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( |
289 switches::kScreenshot); | 311 switches::kScreenshot); |
290 if (file_name.empty()) { | 312 if (file_name.empty()) { |
291 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName); | 313 file_name = base::FilePath().AppendASCII(kDefaultScreenshotFileName); |
292 } | 314 } |
293 | 315 |
294 screenshot_file_stream_.reset( | 316 screenshot_file_stream_.reset( |
295 new net::FileStream(browser_->BrowserFileThread())); | 317 new net::FileStream(browser_->BrowserFileThread())); |
296 const int open_result = screenshot_file_stream_->Open( | 318 const int open_result = screenshot_file_stream_->Open( |
297 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | | 319 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | |
298 base::File::FLAG_ASYNC, | 320 base::File::FLAG_ASYNC, |
299 base::Bind(&HeadlessShell::OnScreenshotFileOpened, | 321 base::Bind(&HeadlessShell::OnScreenshotFileOpened, |
300 base::Unretained(this), base::Passed(std::move(result)), | 322 weak_factory_.GetWeakPtr(), base::Passed(std::move(result)), |
301 file_name)); | 323 file_name)); |
302 if (open_result != net::ERR_IO_PENDING) { | 324 if (open_result != net::ERR_IO_PENDING) { |
303 // Operation could not be started. | 325 // Operation could not be started. |
304 OnScreenshotFileOpened(nullptr, file_name, open_result); | 326 OnScreenshotFileOpened(nullptr, file_name, open_result); |
305 } | 327 } |
306 } | 328 } |
307 | 329 |
308 void OnScreenshotFileOpened( | 330 void OnScreenshotFileOpened( |
309 std::unique_ptr<page::CaptureScreenshotResult> result, | 331 std::unique_ptr<page::CaptureScreenshotResult> result, |
310 const base::FilePath file_name, | 332 const base::FilePath file_name, |
311 const int open_result) { | 333 const int open_result) { |
312 if (open_result != net::OK) { | 334 if (open_result != net::OK) { |
313 LOG(ERROR) << "Writing screenshot to file " << file_name.value() | 335 LOG(ERROR) << "Writing screenshot to file " << file_name.value() |
314 << " was unsuccessful, could not open file: " | 336 << " was unsuccessful, could not open file: " |
315 << net::ErrorToString(open_result); | 337 << net::ErrorToString(open_result); |
316 return; | 338 return; |
317 } | 339 } |
318 | 340 |
319 std::string decoded_png; | 341 std::string decoded_png; |
320 base::Base64Decode(result->GetData(), &decoded_png); | 342 base::Base64Decode(result->GetData(), &decoded_png); |
321 scoped_refptr<net::IOBufferWithSize> buf = | 343 scoped_refptr<net::IOBufferWithSize> buf = |
322 new net::IOBufferWithSize(decoded_png.size()); | 344 new net::IOBufferWithSize(decoded_png.size()); |
323 memcpy(buf->data(), decoded_png.data(), decoded_png.size()); | 345 memcpy(buf->data(), decoded_png.data(), decoded_png.size()); |
324 const int write_result = screenshot_file_stream_->Write( | 346 const int write_result = screenshot_file_stream_->Write( |
325 buf.get(), buf->size(), | 347 buf.get(), buf->size(), |
326 base::Bind(&HeadlessShell::OnScreenshotFileWritten, | 348 base::Bind(&HeadlessShell::OnScreenshotFileWritten, |
327 base::Unretained(this), file_name, buf->size())); | 349 weak_factory_.GetWeakPtr(), file_name, buf->size())); |
328 if (write_result != net::ERR_IO_PENDING) { | 350 if (write_result != net::ERR_IO_PENDING) { |
329 // Operation may have completed successfully or failed. | 351 // Operation may have completed successfully or failed. |
330 OnScreenshotFileWritten(file_name, buf->size(), write_result); | 352 OnScreenshotFileWritten(file_name, buf->size(), write_result); |
331 } | 353 } |
332 } | 354 } |
333 | 355 |
334 void OnScreenshotFileWritten(const base::FilePath file_name, | 356 void OnScreenshotFileWritten(const base::FilePath file_name, |
335 const int length, | 357 const int length, |
336 const int write_result) { | 358 const int write_result) { |
337 if (write_result < length) { | 359 if (write_result < length) { |
338 // TODO(eseckler): Support recovering from partial writes. | 360 // TODO(eseckler): Support recovering from partial writes. |
339 LOG(ERROR) << "Writing screenshot to file " << file_name.value() | 361 LOG(ERROR) << "Writing screenshot to file " << file_name.value() |
340 << " was unsuccessful: " << net::ErrorToString(write_result); | 362 << " was unsuccessful: " << net::ErrorToString(write_result); |
341 } else { | 363 } else { |
342 std::cout << "Screenshot written to file " << file_name.value() << "." | 364 std::cout << "Screenshot written to file " << file_name.value() << "." |
343 << std::endl; | 365 << std::endl; |
344 } | 366 } |
345 int close_result = screenshot_file_stream_->Close(base::Bind( | 367 int close_result = screenshot_file_stream_->Close(base::Bind( |
346 &HeadlessShell::OnScreenshotFileClosed, base::Unretained(this))); | 368 &HeadlessShell::OnScreenshotFileClosed, weak_factory_.GetWeakPtr())); |
347 if (close_result != net::ERR_IO_PENDING) { | 369 if (close_result != net::ERR_IO_PENDING) { |
348 // Operation could not be started. | 370 // Operation could not be started. |
349 OnScreenshotFileClosed(close_result); | 371 OnScreenshotFileClosed(close_result); |
350 } | 372 } |
351 } | 373 } |
352 | 374 |
353 void OnScreenshotFileClosed(const int close_result) { Shutdown(); } | 375 void OnScreenshotFileClosed(const int close_result) { Shutdown(); } |
354 | 376 |
355 bool RemoteDebuggingEnabled() const { | 377 bool RemoteDebuggingEnabled() const { |
356 const base::CommandLine& command_line = | 378 const base::CommandLine& command_line = |
357 *base::CommandLine::ForCurrentProcess(); | 379 *base::CommandLine::ForCurrentProcess(); |
358 return command_line.HasSwitch(::switches::kRemoteDebuggingPort); | 380 return command_line.HasSwitch(::switches::kRemoteDebuggingPort); |
359 } | 381 } |
360 | 382 |
361 private: | 383 private: |
362 GURL url_; | 384 GURL url_; |
363 HeadlessBrowser* browser_; // Not owned. | 385 HeadlessBrowser* browser_; // Not owned. |
364 std::unique_ptr<HeadlessDevToolsClient> devtools_client_; | 386 std::unique_ptr<HeadlessDevToolsClient> devtools_client_; |
365 HeadlessWebContents* web_contents_; | 387 HeadlessWebContents* web_contents_; |
366 bool processed_page_ready_; | 388 bool processed_page_ready_; |
367 std::unique_ptr<net::FileStream> screenshot_file_stream_; | 389 std::unique_ptr<net::FileStream> screenshot_file_stream_; |
368 HeadlessBrowserContext* browser_context_; | 390 HeadlessBrowserContext* browser_context_; |
369 std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_; | 391 std::unique_ptr<DeterministicDispatcher> deterministic_dispatcher_; |
| 392 base::WeakPtrFactory<HeadlessShell> weak_factory_; |
370 | 393 |
371 DISALLOW_COPY_AND_ASSIGN(HeadlessShell); | 394 DISALLOW_COPY_AND_ASSIGN(HeadlessShell); |
372 }; | 395 }; |
373 | 396 |
374 int HeadlessShellMain(int argc, const char** argv) { | 397 int HeadlessShellMain(int argc, const char** argv) { |
375 RunChildProcessIfNeeded(argc, argv); | 398 RunChildProcessIfNeeded(argc, argv); |
376 HeadlessShell shell; | 399 HeadlessShell shell; |
377 HeadlessBrowser::Options::Builder builder(argc, argv); | 400 HeadlessBrowser::Options::Builder builder(argc, argv); |
378 | 401 |
379 // Enable devtools if requested. | 402 // Enable devtools if requested. |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 } | 465 } |
443 builder.SetWindowSize(parsed_window_size); | 466 builder.SetWindowSize(parsed_window_size); |
444 } | 467 } |
445 | 468 |
446 return HeadlessBrowserMain( | 469 return HeadlessBrowserMain( |
447 builder.Build(), | 470 builder.Build(), |
448 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); | 471 base::Bind(&HeadlessShell::OnStart, base::Unretained(&shell))); |
449 } | 472 } |
450 | 473 |
451 } // namespace headless | 474 } // namespace headless |
OLD | NEW |