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
3 // found in the LICENSE file.
4
5 #include <memory>
6 #include <sstream>
7 #include <string>
8 #include <utility>
9
10 #include "base/base_switches.h"
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/command_line.h"
14 #include "base/environment.h"
15 #include "base/files/file_path.h"
16 #include "base/files/file_util.h"
17 #include "base/i18n/rtl.h"
18 #include "base/json/json_writer.h"
19 #include "base/location.h"
20 #include "base/memory/weak_ptr.h"
21 #include "base/numerics/safe_conversions.h"
22 #include "base/path_service.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/task/post_task.h"
26 #include "base/task/thread_pool.h"
27 #include "base/task_runner_util.h"
28 #include "build/branding_buildflags.h"
29 #include "build/build_config.h"
30 #include "cc/base/switches.h"
31 #include "components/os_crypt/os_crypt_switches.h"
32 #include "components/viz/common/switches.h"
33 #include "content/public/app/content_main.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/common/content_switches.h"
36 #include "headless/app/headless_shell.h"
37 #include "headless/app/headless_shell_switches.h"
38 #include "headless/lib/headless_content_main_delegate.h"
39 #include "headless/public/headless_devtools_target.h"
40 #include "net/base/filename_util.h"
41 #include "net/base/host_port_pair.h"
42 #include "net/base/io_buffer.h"
43 #include "net/base/ip_address.h"
44 #include "net/base/net_errors.h"
45 #include "net/http/http_util.h"
46 #include "net/socket/ssl_client_socket.h"
47 #include "net/ssl/ssl_key_logger_impl.h"
48 #include "services/network/public/cpp/network_switches.h"
49 #include "ui/gfx/geometry/size.h"
50
51 #if defined(OS_WIN)
52 #include "components/crash/core/app/crash_switches.h"
53 #include "components/crash/core/app/run_as_crashpad_handler_win.h"
54 #include "sandbox/win/src/sandbox_types.h"
55 #endif
56
57 #if !defined(CHROME_MULTIPLE_DLL_CHILD)
58 #include "headless/lib/browser/headless_browser_impl.h"
59 #include "headless/lib/browser/headless_devtools.h"
60 #endif
61
62 namespace headless {
63
64 namespace {
65
66 // By default listen to incoming DevTools connections on localhost.
67 const char kUseLocalHostForDevToolsHttpServer[] = "localhost";
68 // Default file name for screenshot. Can be overriden by "--screenshot" switch.
69 const char kDefaultScreenshotFileName[] = "screenshot.png";
70 // Default file name for pdf. Can be overriden by "--print-to-pdf" switch.
71 const char kDefaultPDFFileName[] = "output.pdf";
72
ParseWindowSize(const std::string & window_size,gfx::Size * parsed_window_size)73 bool ParseWindowSize(const std::string& window_size,
74 gfx::Size* parsed_window_size) {
75 int width = 0;
76 int height = 0;
77 if (sscanf(window_size.c_str(), "%d%*[x,]%d", &width, &height) >= 2 &&
78 width >= 0 && height >= 0) {
79 parsed_window_size->set_width(width);
80 parsed_window_size->set_height(height);
81 return true;
82 }
83 return false;
84 }
85
ParseFontRenderHinting(const std::string & font_render_hinting_string,gfx::FontRenderParams::Hinting * font_render_hinting)86 bool ParseFontRenderHinting(
87 const std::string& font_render_hinting_string,
88 gfx::FontRenderParams::Hinting* font_render_hinting) {
89 if (font_render_hinting_string == "max") {
90 *font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_MAX;
91 } else if (font_render_hinting_string == "full") {
92 *font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_FULL;
93 } else if (font_render_hinting_string == "medium") {
94 *font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_MEDIUM;
95 } else if (font_render_hinting_string == "slight") {
96 *font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_SLIGHT;
97 } else if (font_render_hinting_string == "none") {
98 *font_render_hinting = gfx::FontRenderParams::Hinting::HINTING_NONE;
99 } else {
100 return false;
101 }
102 return true;
103 }
104
105 #if !defined(CHROME_MULTIPLE_DLL_CHILD)
ConvertArgumentToURL(const base::CommandLine::StringType & arg)106 GURL ConvertArgumentToURL(const base::CommandLine::StringType& arg) {
107 GURL url(arg);
108 if (url.is_valid() && url.has_scheme())
109 return url;
110
111 return net::FilePathToFileURL(
112 base::MakeAbsoluteFilePath(base::FilePath(arg)));
113 }
114
ConvertArgumentsToURLs(const base::CommandLine::StringVector & args)115 std::vector<GURL> ConvertArgumentsToURLs(
116 const base::CommandLine::StringVector& args) {
117 std::vector<GURL> urls;
118 urls.reserve(args.size());
119 for (auto it = args.rbegin(); it != args.rend(); ++it)
120 urls.push_back(ConvertArgumentToURL(*it));
121 return urls;
122 }
123
124 // Gets file path into ssl_keylog_file from command line argument or
125 // environment variable. Command line argument has priority when
126 // both specified.
GetSSLKeyLogFile(const base::CommandLine * command_line)127 base::FilePath GetSSLKeyLogFile(const base::CommandLine* command_line) {
128 if (command_line->HasSwitch(switches::kSSLKeyLogFile)) {
129 base::FilePath path =
130 command_line->GetSwitchValuePath(switches::kSSLKeyLogFile);
131 if (!path.empty())
132 return path;
133 LOG(WARNING) << "ssl-key-log-file argument missing";
134 }
135 std::unique_ptr<base::Environment> env(base::Environment::Create());
136 std::string path_str;
137 env->GetVar("SSLKEYLOGFILE", &path_str);
138 #if defined(OS_WIN)
139 // base::Environment returns environment variables in UTF-8 on Windows.
140 return base::FilePath(base::UTF8ToUTF16(path_str));
141 #else
142 return base::FilePath(path_str);
143 #endif
144 }
145
146 #endif // !defined(CHROME_MULTIPLE_DLL_CHILD)
147
RunContentMain(HeadlessBrowser::Options options,base::OnceCallback<void (HeadlessBrowser *)> on_browser_start_callback)148 int RunContentMain(
149 HeadlessBrowser::Options options,
150 base::OnceCallback<void(HeadlessBrowser*)> on_browser_start_callback) {
151 content::ContentMainParams params(nullptr);
152 #if defined(OS_WIN)
153 // Sandbox info has to be set and initialized.
154 CHECK(options.sandbox_info);
155 params.instance = options.instance;
156 params.sandbox_info = std::move(options.sandbox_info);
157 #elif !defined(OS_ANDROID)
158 params.argc = options.argc;
159 params.argv = options.argv;
160 #endif
161
162 // TODO(skyostil): Implement custom message pumps.
163 DCHECK(!options.message_pump);
164
165 #if defined(CHROME_MULTIPLE_DLL_CHILD)
166 HeadlessContentMainDelegate delegate(std::move(options));
167 #else
168 auto browser = std::make_unique<HeadlessBrowserImpl>(
169 std::move(on_browser_start_callback), std::move(options));
170 HeadlessContentMainDelegate delegate(std::move(browser));
171 #endif
172 params.delegate = &delegate;
173 return content::ContentMain(params);
174 }
175
ValidateCommandLine(const base::CommandLine & command_line)176 bool ValidateCommandLine(const base::CommandLine& command_line) {
177 if (!command_line.HasSwitch(switches::kRemoteDebuggingPort) &&
178 !command_line.HasSwitch(switches::kRemoteDebuggingPipe)) {
179 if (command_line.GetArgs().size() <= 1)
180 return true;
181 LOG(ERROR) << "Open multiple tabs is only supported when "
182 << "remote debugging is enabled.";
183 return false;
184 }
185 if (command_line.HasSwitch(switches::kDefaultBackgroundColor)) {
186 LOG(ERROR) << "Setting default background color is disabled "
187 << "when remote debugging is enabled.";
188 return false;
189 }
190 if (command_line.HasSwitch(switches::kDumpDom)) {
191 LOG(ERROR) << "Dump DOM is disabled when remote debugging is enabled.";
192 return false;
193 }
194 if (command_line.HasSwitch(switches::kPrintToPDF)) {
195 LOG(ERROR) << "Print to PDF is disabled "
196 << "when remote debugging is enabled.";
197 return false;
198 }
199 if (command_line.HasSwitch(switches::kRepl)) {
200 LOG(ERROR) << "Evaluate Javascript is disabled "
201 << "when remote debugging is enabled.";
202 return false;
203 }
204 if (command_line.HasSwitch(switches::kScreenshot)) {
205 LOG(ERROR) << "Capture screenshot is disabled "
206 << "when remote debugging is enabled.";
207 return false;
208 }
209 if (command_line.HasSwitch(switches::kTimeout)) {
210 LOG(ERROR) << "Navigation timeout is disabled "
211 << "when remote debugging is enabled.";
212 return false;
213 }
214 if (command_line.HasSwitch(switches::kVirtualTimeBudget)) {
215 LOG(ERROR) << "Virtual time budget is disabled "
216 << "when remote debugging is enabled.";
217 return false;
218 }
219 return true;
220 }
221
222 } // namespace
223
224 HeadlessShell::HeadlessShell() = default;
225
226 HeadlessShell::~HeadlessShell() = default;
227
228 #if !defined(CHROME_MULTIPLE_DLL_CHILD)
OnStart(HeadlessBrowser * browser)229 void HeadlessShell::OnStart(HeadlessBrowser* browser) {
230 browser_ = browser;
231 devtools_client_ = HeadlessDevToolsClient::Create();
232 file_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
233 {base::MayBlock(), base::TaskPriority::BEST_EFFORT});
234
235 HeadlessBrowserContext::Builder context_builder =
236 browser_->CreateBrowserContextBuilder();
237 // TODO(eseckler): These switches should also affect BrowserContexts that
238 // are created via DevTools later.
239 base::FilePath ssl_keylog_file =
240 GetSSLKeyLogFile(base::CommandLine::ForCurrentProcess());
241 if (!ssl_keylog_file.empty()) {
242 net::SSLClientSocket::SetSSLKeyLogger(
243 std::make_unique<net::SSLKeyLoggerImpl>(ssl_keylog_file));
244 }
245
246 // Retrieve the locale set by InitApplicationLocale() in
247 // headless_content_main_delegate.cc in a way that is free of side-effects.
248 context_builder.SetAcceptLanguage(base::i18n::GetConfiguredLocale());
249
250 browser_context_ = context_builder.Build();
251 browser_->SetDefaultBrowserContext(browser_context_);
252
253 base::CommandLine::StringVector args =
254 base::CommandLine::ForCurrentProcess()->GetArgs();
255
256 // If no explicit URL is present, navigate to about:blank, unless we're being
257 // driven by debugger.
258 if (args.empty() && !base::CommandLine::ForCurrentProcess()->HasSwitch(
259 switches::kRemoteDebuggingPipe)) {
260 #if defined(OS_WIN)
261 args.push_back(L"about:blank");
262 #else
263 args.push_back("about:blank");
264 #endif
265 }
266
267 if (!args.empty()) {
268 base::PostTaskAndReplyWithResult(
269 file_task_runner_.get(), FROM_HERE,
270 base::BindOnce(&ConvertArgumentsToURLs, args),
271 base::BindOnce(&HeadlessShell::OnGotURLs, weak_factory_.GetWeakPtr()));
272 }
273 }
274
OnGotURLs(const std::vector<GURL> & urls)275 void HeadlessShell::OnGotURLs(const std::vector<GURL>& urls) {
276 HeadlessWebContents::Builder builder(
277 browser_context_->CreateWebContentsBuilder());
278 for (const auto& url : urls) {
279 HeadlessWebContents* web_contents = builder.SetInitialURL(url).Build();
280 if (!web_contents) {
281 LOG(ERROR) << "Navigation to " << url << " failed";
282 browser_->Shutdown();
283 return;
284 }
285 if (!web_contents_ && !RemoteDebuggingEnabled()) {
286 // TODO(jzfeng): Support observing multiple targets.
287 url_ = url;
288 web_contents_ = web_contents;
289 web_contents_->AddObserver(this);
290 }
291 }
292 }
293
Detach()294 void HeadlessShell::Detach() {
295 if (!RemoteDebuggingEnabled()) {
296 devtools_client_->GetEmulation()->GetExperimental()->RemoveObserver(this);
297 devtools_client_->GetInspector()->GetExperimental()->RemoveObserver(this);
298 devtools_client_->GetPage()->GetExperimental()->RemoveObserver(this);
299 if (web_contents_->GetDevToolsTarget()) {
300 web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
301 }
302 }
303 web_contents_->RemoveObserver(this);
304 web_contents_ = nullptr;
305 }
306
Shutdown()307 void HeadlessShell::Shutdown() {
308 if (web_contents_)
309 Detach();
310 browser_context_->Close();
311 browser_->Shutdown();
312 }
313
DevToolsTargetReady()314 void HeadlessShell::DevToolsTargetReady() {
315 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
316 web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
317 devtools_client_->GetInspector()->GetExperimental()->AddObserver(this);
318 devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
319 devtools_client_->GetPage()->Enable();
320
321 devtools_client_->GetEmulation()->GetExperimental()->AddObserver(this);
322
323 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
324 switches::kDefaultBackgroundColor)) {
325 std::string color_hex =
326 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
327 switches::kDefaultBackgroundColor);
328 uint32_t color;
329 CHECK(base::HexStringToUInt(color_hex, &color))
330 << "Expected a hex value for --default-background-color=";
331 auto rgba = dom::RGBA::Builder()
332 .SetR((color & 0xff000000) >> 24)
333 .SetG((color & 0x00ff0000) >> 16)
334 .SetB((color & 0x0000ff00) >> 8)
335 .SetA(color & 0x000000ff)
336 .Build();
337 devtools_client_->GetEmulation()
338 ->GetExperimental()
339 ->SetDefaultBackgroundColorOverride(
340 emulation::SetDefaultBackgroundColorOverrideParams::Builder()
341 .SetColor(std::move(rgba))
342 .Build());
343 }
344
345 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
346 switches::kVirtualTimeBudget)) {
347 std::string budget_ms_ascii =
348 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
349 switches::kVirtualTimeBudget);
350 int budget_ms;
351 CHECK(base::StringToInt(budget_ms_ascii, &budget_ms))
352 << "Expected an integer value for --virtual-time-budget=";
353 devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
354 emulation::SetVirtualTimePolicyParams::Builder()
355 .SetPolicy(
356 emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING)
357 .SetBudget(budget_ms)
358 .Build());
359 } else {
360 // Check if the document had already finished loading by the time we
361 // attached.
362 PollReadyState();
363 }
364
365 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kTimeout)) {
366 std::string timeout_ms_ascii =
367 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
368 switches::kTimeout);
369 int timeout_ms;
370 CHECK(base::StringToInt(timeout_ms_ascii, &timeout_ms))
371 << "Expected an integer value for --timeout=";
372 browser_->BrowserMainThread()->PostDelayedTask(
373 FROM_HERE,
374 base::BindOnce(&HeadlessShell::FetchTimeout,
375 weak_factory_.GetWeakPtr()),
376 base::TimeDelta::FromMilliseconds(timeout_ms));
377 }
378 // TODO(skyostil): Implement more features to demonstrate the devtools API.
379 }
380
HeadlessWebContentsDestroyed()381 void HeadlessShell::HeadlessWebContentsDestroyed() {
382 // Detach now, but defer shutdown till the HeadlessWebContents
383 // removal is complete.
384 Detach();
385 browser_->BrowserMainThread()->PostTask(
386 FROM_HERE,
387 base::BindOnce(&HeadlessShell::Shutdown, weak_factory_.GetWeakPtr()));
388 }
389 #endif // !defined(CHROME_MULTIPLE_DLL_CHILD)
390
FetchTimeout()391 void HeadlessShell::FetchTimeout() {
392 LOG(INFO) << "Timeout.";
393 devtools_client_->GetPage()->GetExperimental()->StopLoading(
394 page::StopLoadingParams::Builder().Build());
395 }
396
OnTargetCrashed(const inspector::TargetCrashedParams & params)397 void HeadlessShell::OnTargetCrashed(
398 const inspector::TargetCrashedParams& params) {
399 LOG(ERROR) << "Abnormal renderer termination.";
400 // NB this never gets called if remote debugging is enabled.
401 Shutdown();
402 }
403
PollReadyState()404 void HeadlessShell::PollReadyState() {
405 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
406 // We need to check the current location in addition to the ready state to
407 // be sure the expected page is ready.
408 devtools_client_->GetRuntime()->Evaluate(
409 "document.readyState + ' ' + document.location.href",
410 base::BindOnce(&HeadlessShell::OnReadyState, weak_factory_.GetWeakPtr()));
411 }
412
OnReadyState(std::unique_ptr<runtime::EvaluateResult> result)413 void HeadlessShell::OnReadyState(
414 std::unique_ptr<runtime::EvaluateResult> result) {
415 // |result| can be nullptr if HeadlessDevToolsClientImpl::DispatchMessageReply
416 // sees an error.
417 if (result && result->GetResult()->GetValue()->is_string()) {
418 std::stringstream stream(result->GetResult()->GetValue()->GetString());
419 std::string ready_state;
420 std::string url;
421 stream >> ready_state;
422 stream >> url;
423
424 if (ready_state == "complete" &&
425 (url_.spec() == url || url != "about:blank")) {
426 OnPageReady();
427 return;
428 }
429 }
430 }
431
432 // emulation::Observer implementation:
OnVirtualTimeBudgetExpired(const emulation::VirtualTimeBudgetExpiredParams & params)433 void HeadlessShell::OnVirtualTimeBudgetExpired(
434 const emulation::VirtualTimeBudgetExpiredParams& params) {
435 OnPageReady();
436 }
437
438 // page::Observer implementation:
OnLoadEventFired(const page::LoadEventFiredParams & params)439 void HeadlessShell::OnLoadEventFired(const page::LoadEventFiredParams& params) {
440 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
441 switches::kVirtualTimeBudget)) {
442 return;
443 }
444 OnPageReady();
445 }
446
OnPageReady()447 void HeadlessShell::OnPageReady() {
448 if (processed_page_ready_)
449 return;
450 processed_page_ready_ = true;
451
452 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kDumpDom)) {
453 FetchDom();
454 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
455 switches::kRepl)) {
456 LOG(INFO)
457 << "Type a Javascript expression to evaluate or \"quit\" to exit.";
458 InputExpression();
459 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
460 switches::kScreenshot)) {
461 CaptureScreenshot();
462 } else if (base::CommandLine::ForCurrentProcess()->HasSwitch(
463 switches::kPrintToPDF)) {
464 PrintToPDF();
465 } else {
466 Shutdown();
467 }
468 }
469
FetchDom()470 void HeadlessShell::FetchDom() {
471 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
472 devtools_client_->GetRuntime()->Evaluate(
473 "(document.doctype ? new "
474 "XMLSerializer().serializeToString(document.doctype) + '\\n' : '') + "
475 "document.documentElement.outerHTML",
476 base::BindOnce(&HeadlessShell::OnDomFetched, weak_factory_.GetWeakPtr()));
477 }
478
OnDomFetched(std::unique_ptr<runtime::EvaluateResult> result)479 void HeadlessShell::OnDomFetched(
480 std::unique_ptr<runtime::EvaluateResult> result) {
481 if (result->HasExceptionDetails()) {
482 LOG(ERROR) << "Failed to serialize document: "
483 << result->GetExceptionDetails()->GetText();
484 } else {
485 printf("%s\n", result->GetResult()->GetValue()->GetString().c_str());
486 }
487 Shutdown();
488 }
489
InputExpression()490 void HeadlessShell::InputExpression() {
491 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
492 // Note that a real system should read user input asynchronously, because
493 // otherwise all other browser activity is suspended (e.g., page loading).
494 printf(">>> ");
495 std::stringstream expression;
496 while (true) {
497 int c = fgetc(stdin);
498 if (c == '\n')
499 break;
500 if (c == EOF) {
501 // If there's no expression, then quit.
502 if (expression.str().size() == 0) {
503 printf("\n");
504 Shutdown();
505 return;
506 }
507 break;
508 }
509 expression << static_cast<char>(c);
510 }
511 if (expression.str() == "quit") {
512 Shutdown();
513 return;
514 }
515 devtools_client_->GetRuntime()->Evaluate(
516 expression.str(), base::BindOnce(&HeadlessShell::OnExpressionResult,
517 weak_factory_.GetWeakPtr()));
518 }
519
OnExpressionResult(std::unique_ptr<runtime::EvaluateResult> result)520 void HeadlessShell::OnExpressionResult(
521 std::unique_ptr<runtime::EvaluateResult> result) {
522 std::unique_ptr<base::Value> value = result->Serialize();
523 std::string result_json;
524 base::JSONWriter::Write(*value, &result_json);
525 printf("%s\n", result_json.c_str());
526 InputExpression();
527 }
528
CaptureScreenshot()529 void HeadlessShell::CaptureScreenshot() {
530 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
531 devtools_client_->GetPage()->GetExperimental()->CaptureScreenshot(
532 page::CaptureScreenshotParams::Builder().Build(),
533 base::BindOnce(&HeadlessShell::OnScreenshotCaptured,
534 weak_factory_.GetWeakPtr()));
535 }
536
OnScreenshotCaptured(std::unique_ptr<page::CaptureScreenshotResult> result)537 void HeadlessShell::OnScreenshotCaptured(
538 std::unique_ptr<page::CaptureScreenshotResult> result) {
539 if (!result) {
540 LOG(ERROR) << "Capture screenshot failed";
541 Shutdown();
542 return;
543 }
544 WriteFile(switches::kScreenshot, kDefaultScreenshotFileName,
545 result->GetData());
546 }
547
PrintToPDF()548 void HeadlessShell::PrintToPDF() {
549 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
550 devtools_client_->GetPage()->GetExperimental()->PrintToPDF(
551 page::PrintToPDFParams::Builder()
552 .SetDisplayHeaderFooter(true)
553 .SetPrintBackground(true)
554 .SetPreferCSSPageSize(true)
555 .Build(),
556 base::BindOnce(&HeadlessShell::OnPDFCreated, weak_factory_.GetWeakPtr()));
557 }
558
OnPDFCreated(std::unique_ptr<page::PrintToPDFResult> result)559 void HeadlessShell::OnPDFCreated(
560 std::unique_ptr<page::PrintToPDFResult> result) {
561 if (!result) {
562 LOG(ERROR) << "Print to PDF failed";
563 Shutdown();
564 return;
565 }
566 WriteFile(switches::kPrintToPDF, kDefaultPDFFileName, result->GetData());
567 }
568
WriteFile(const std::string & file_path_switch,const std::string & default_file_name,const protocol::Binary & data)569 void HeadlessShell::WriteFile(const std::string& file_path_switch,
570 const std::string& default_file_name,
571 const protocol::Binary& data) {
572 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
573
574 base::FilePath file_name =
575 base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
576 file_path_switch);
577 if (file_name.empty())
578 file_name = base::FilePath().AppendASCII(default_file_name);
579
580 file_proxy_ = std::make_unique<base::FileProxy>(file_task_runner_.get());
581 if (!file_proxy_->CreateOrOpen(
582 file_name, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE,
583 base::BindOnce(&HeadlessShell::OnFileOpened,
584 weak_factory_.GetWeakPtr(), data, file_name))) {
585 // Operation could not be started.
586 OnFileOpened(protocol::Binary(), file_name, base::File::FILE_ERROR_FAILED);
587 }
588 }
589
OnFileOpened(const protocol::Binary & data,const base::FilePath file_name,base::File::Error error_code)590 void HeadlessShell::OnFileOpened(const protocol::Binary& data,
591 const base::FilePath file_name,
592 base::File::Error error_code) {
593 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
594 if (!file_proxy_->IsValid()) {
595 LOG(ERROR) << "Writing to file " << file_name.value()
596 << " was unsuccessful, could not open file: "
597 << base::File::ErrorToString(error_code);
598 return;
599 }
600 if (!file_proxy_->Write(
601 0, reinterpret_cast<const char*>(data.data()), data.size(),
602 base::BindOnce(&HeadlessShell::OnFileWritten,
603 weak_factory_.GetWeakPtr(), file_name, data.size()))) {
604 // Operation may have completed successfully or failed.
605 OnFileWritten(file_name, data.size(), base::File::FILE_ERROR_FAILED, 0);
606 }
607 }
608
OnFileWritten(const base::FilePath file_name,const size_t length,base::File::Error error_code,int write_result)609 void HeadlessShell::OnFileWritten(const base::FilePath file_name,
610 const size_t length,
611 base::File::Error error_code,
612 int write_result) {
613 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
614 if (write_result < static_cast<int>(length)) {
615 // TODO(eseckler): Support recovering from partial writes.
616 LOG(ERROR) << "Writing to file " << file_name.value()
617 << " was unsuccessful: "
618 << base::File::ErrorToString(error_code);
619 } else {
620 LOG(INFO) << "Written to file " << file_name.value() << ".";
621 }
622 if (!file_proxy_->Close(base::BindOnce(&HeadlessShell::OnFileClosed,
623 weak_factory_.GetWeakPtr()))) {
624 // Operation could not be started.
625 OnFileClosed(base::File::FILE_ERROR_FAILED);
626 }
627 }
628
OnFileClosed(base::File::Error error_code)629 void HeadlessShell::OnFileClosed(base::File::Error error_code) {
630 Shutdown();
631 }
632
RemoteDebuggingEnabled() const633 bool HeadlessShell::RemoteDebuggingEnabled() const {
634 const base::CommandLine& command_line =
635 *base::CommandLine::ForCurrentProcess();
636 return (command_line.HasSwitch(switches::kRemoteDebuggingPort) ||
637 command_line.HasSwitch(switches::kRemoteDebuggingPipe));
638 }
639
640 #if defined(OS_WIN)
HeadlessShellMain(HINSTANCE instance,sandbox::SandboxInterfaceInfo * sandbox_info)641 int HeadlessShellMain(HINSTANCE instance,
642 sandbox::SandboxInterfaceInfo* sandbox_info) {
643 base::CommandLine::Init(0, nullptr);
644 #if defined(HEADLESS_USE_CRASHPAD)
645 std::string process_type =
646 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
647 ::switches::kProcessType);
648 if (process_type == crash_reporter::switches::kCrashpadHandler) {
649 return crash_reporter::RunAsCrashpadHandler(
650 *base::CommandLine::ForCurrentProcess(), base::FilePath(),
651 ::switches::kProcessType, switches::kUserDataDir);
652 }
653 #endif // defined(HEADLESS_USE_CRASHPAD)
654 RunChildProcessIfNeeded(instance, sandbox_info);
655 HeadlessBrowser::Options::Builder builder(0, nullptr);
656 builder.SetInstance(instance);
657 builder.SetSandboxInfo(std::move(sandbox_info));
658 #else
659 int HeadlessShellMain(int argc, const char** argv) {
660 base::CommandLine::Init(argc, argv);
661 RunChildProcessIfNeeded(argc, argv);
662 HeadlessBrowser::Options::Builder builder(argc, argv);
663 #endif // defined(OS_WIN)
664 HeadlessShell shell;
665
666 #if defined(OS_FUCHSIA)
667 // TODO(fuchsia): Remove this when GPU accelerated compositing is ready.
668 base::CommandLine::ForCurrentProcess()->AppendSwitch(::switches::kDisableGpu);
669 #endif
670
671 base::CommandLine& command_line(*base::CommandLine::ForCurrentProcess());
672 if (!ValidateCommandLine(command_line))
673 return EXIT_FAILURE;
674
675 // Crash reporting in headless mode is enabled by default in official builds.
676 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
677 builder.SetCrashReporterEnabled(true);
678 base::FilePath dumps_path;
679 base::PathService::Get(base::DIR_TEMP, &dumps_path);
680 builder.SetCrashDumpsDir(dumps_path);
681 #endif
682
683 #if defined(OS_MACOSX)
684 command_line.AppendSwitch(os_crypt::switches::kUseMockKeychain);
685 #endif
686
687 if (command_line.HasSwitch(switches::kDeterministicMode)) {
688 command_line.AppendSwitch(switches::kEnableBeginFrameControl);
689
690 // Compositor flags
691 command_line.AppendSwitch(::switches::kRunAllCompositorStagesBeforeDraw);
692 command_line.AppendSwitch(::switches::kDisableNewContentRenderingTimeout);
693 // Ensure that image animations don't resync their animation timestamps when
694 // looping back around.
695 command_line.AppendSwitch(::switches::kDisableImageAnimationResync);
696
697 // Renderer flags
698 command_line.AppendSwitch(cc::switches::kDisableThreadedAnimation);
699 command_line.AppendSwitch(::switches::kDisableThreadedScrolling);
700 command_line.AppendSwitch(cc::switches::kDisableCheckerImaging);
701 }
702
703 if (command_line.HasSwitch(switches::kEnableBeginFrameControl))
704 builder.SetEnableBeginFrameControl(true);
705
706 if (command_line.HasSwitch(switches::kEnableCrashReporter))
707 builder.SetCrashReporterEnabled(true);
708 if (command_line.HasSwitch(switches::kDisableCrashReporter))
709 builder.SetCrashReporterEnabled(false);
710 if (command_line.HasSwitch(switches::kCrashDumpsDir)) {
711 builder.SetCrashDumpsDir(
712 command_line.GetSwitchValuePath(switches::kCrashDumpsDir));
713 }
714
715 // Enable devtools if requested, by specifying a port (and optional address).
716 if (command_line.HasSwitch(::switches::kRemoteDebuggingPort)) {
717 std::string address = kUseLocalHostForDevToolsHttpServer;
718 if (command_line.HasSwitch(switches::kRemoteDebuggingAddress)) {
719 address =
720 command_line.GetSwitchValueASCII(switches::kRemoteDebuggingAddress);
721 net::IPAddress parsed_address;
722 if (!net::ParseURLHostnameToAddress(address, &parsed_address)) {
723 LOG(ERROR) << "Invalid devtools server address";
724 return EXIT_FAILURE;
725 }
726 }
727 int parsed_port;
728 std::string port_str =
729 command_line.GetSwitchValueASCII(::switches::kRemoteDebuggingPort);
730 if (!base::StringToInt(port_str, &parsed_port) ||
731 !base::IsValueInRangeForNumericType<uint16_t>(parsed_port)) {
732 LOG(ERROR) << "Invalid devtools server port";
733 return EXIT_FAILURE;
734 }
735 const net::HostPortPair endpoint(address,
736 base::checked_cast<uint16_t>(parsed_port));
737 builder.EnableDevToolsServer(endpoint);
738 }
739 if (command_line.HasSwitch(::switches::kRemoteDebuggingPipe))
740 builder.EnableDevToolsPipe();
741
742 if (command_line.HasSwitch(switches::kProxyServer)) {
743 std::string proxy_server =
744 command_line.GetSwitchValueASCII(switches::kProxyServer);
745 auto proxy_config = std::make_unique<net::ProxyConfig>();
746 proxy_config->proxy_rules().ParseFromString(proxy_server);
747 if (command_line.HasSwitch(switches::kProxyBypassList)) {
748 std::string bypass_list =
749 command_line.GetSwitchValueASCII(switches::kProxyBypassList);
750 proxy_config->proxy_rules().bypass_rules.ParseFromString(bypass_list);
751 }
752 builder.SetProxyConfig(std::move(proxy_config));
753 }
754
755 if (command_line.HasSwitch(switches::kUseGL)) {
756 builder.SetGLImplementation(
757 command_line.GetSwitchValueASCII(switches::kUseGL));
758 }
759
760 if (command_line.HasSwitch(switches::kUserDataDir)) {
761 builder.SetUserDataDir(
762 command_line.GetSwitchValuePath(switches::kUserDataDir));
763 builder.SetIncognitoMode(false);
764 }
765
766 if (command_line.HasSwitch(switches::kWindowSize)) {
767 std::string window_size =
768 command_line.GetSwitchValueASCII(switches::kWindowSize);
769 gfx::Size parsed_window_size;
770 if (!ParseWindowSize(window_size, &parsed_window_size)) {
771 LOG(ERROR) << "Malformed window size";
772 return EXIT_FAILURE;
773 }
774 builder.SetWindowSize(parsed_window_size);
775 }
776
777 if (command_line.HasSwitch(switches::kHideScrollbars)) {
778 builder.SetOverrideWebPreferencesCallback(
779 base::BindRepeating([](WebPreferences* preferences) {
780 preferences->hide_scrollbars = true;
781 }));
782 }
783
784 if (command_line.HasSwitch(switches::kUserAgent)) {
785 std::string ua = command_line.GetSwitchValueASCII(switches::kUserAgent);
786 if (net::HttpUtil::IsValidHeaderValue(ua))
787 builder.SetUserAgent(ua);
788 }
789
790 if (command_line.HasSwitch(switches::kFontRenderHinting)) {
791 std::string font_render_hinting_string =
792 command_line.GetSwitchValueASCII(switches::kFontRenderHinting);
793 gfx::FontRenderParams::Hinting font_render_hinting;
794 if (ParseFontRenderHinting(font_render_hinting_string,
795 &font_render_hinting)) {
796 builder.SetFontRenderHinting(font_render_hinting);
797 } else {
798 LOG(ERROR) << "Unknown font-render-hinting parameter value";
799 return EXIT_FAILURE;
800 }
801 }
802
803 if (command_line.HasSwitch(switches::kBlockNewWebContents))
804 builder.SetBlockNewWebContents(true);
805
806 return HeadlessBrowserMain(
807 builder.Build(),
808 base::BindOnce(&HeadlessShell::OnStart, base::Unretained(&shell)));
809 }
810
811 int HeadlessShellMain(const content::ContentMainParams& params) {
812 #if defined(OS_WIN)
813 return HeadlessShellMain(params.instance, params.sandbox_info);
814 #else
815 return HeadlessShellMain(params.argc, params.argv);
816 #endif
817 }
818
819 #if defined(OS_WIN)
820 void RunChildProcessIfNeeded(HINSTANCE instance,
821 sandbox::SandboxInterfaceInfo* sandbox_info) {
822 base::CommandLine::Init(0, nullptr);
823 HeadlessBrowser::Options::Builder builder(0, nullptr);
824 builder.SetInstance(instance);
825 builder.SetSandboxInfo(std::move(sandbox_info));
826 #else
827 void RunChildProcessIfNeeded(int argc, const char** argv) {
828 base::CommandLine::Init(argc, argv);
829 HeadlessBrowser::Options::Builder builder(argc, argv);
830 #endif // defined(OS_WIN)
831 const base::CommandLine& command_line(
832 *base::CommandLine::ForCurrentProcess());
833
834 if (!command_line.HasSwitch(::switches::kProcessType))
835 return;
836
837 if (command_line.HasSwitch(switches::kUserAgent)) {
838 std::string ua = command_line.GetSwitchValueASCII(switches::kUserAgent);
839 if (net::HttpUtil::IsValidHeaderValue(ua))
840 builder.SetUserAgent(ua);
841 }
842
843 exit(RunContentMain(builder.Build(),
844 base::OnceCallback<void(HeadlessBrowser*)>()));
845 }
846
847 int HeadlessBrowserMain(
848 HeadlessBrowser::Options options,
849 base::OnceCallback<void(HeadlessBrowser*)> on_browser_start_callback) {
850 DCHECK(!on_browser_start_callback.is_null());
851 #if DCHECK_IS_ON()
852 // The browser can only be initialized once.
853 static bool browser_was_initialized;
854 DCHECK(!browser_was_initialized);
855 browser_was_initialized = true;
856
857 // Child processes should not end up here.
858 DCHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch(
859 ::switches::kProcessType));
860 #endif
861 return RunContentMain(std::move(options),
862 std::move(on_browser_start_callback));
863 }
864
865 } // namespace headless
866