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