1 // Copyright (c) 2012 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 "libcef/common/alloy/alloy_main_delegate.h"
6
7 #include <tuple>
8
9 #include "libcef/browser/alloy/alloy_browser_context.h"
10 #include "libcef/browser/alloy/alloy_content_browser_client.h"
11 #include "libcef/common/cef_switches.h"
12 #include "libcef/common/command_line_impl.h"
13 #include "libcef/common/crash_reporting.h"
14 #include "libcef/common/extensions/extensions_util.h"
15 #include "libcef/common/resource_util.h"
16 #include "libcef/renderer/alloy/alloy_content_renderer_client.h"
17
18 #include "base/base_switches.h"
19 #include "base/command_line.h"
20 #include "base/files/file_path.h"
21 #include "base/files/file_util.h"
22 #include "base/path_service.h"
23 #include "base/stl_util.h"
24 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_util.h"
26 #include "base/synchronization/waitable_event.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/child/pdf_child_init.h"
29 #include "chrome/common/chrome_constants.h"
30 #include "chrome/common/chrome_paths.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/utility/chrome_content_utility_client.h"
33 #include "components/component_updater/component_updater_paths.h"
34 #include "components/content_settings/core/common/content_settings_pattern.h"
35 #include "components/embedder_support/switches.h"
36 #include "components/spellcheck/common/spellcheck_features.h"
37 #include "components/viz/common/features.h"
38 #include "content/public/common/content_features.h"
39 #include "content/public/common/content_switches.h"
40 #include "content/public/common/main_function_params.h"
41 #include "content/public/common/url_constants.h"
42 #include "extensions/common/constants.h"
43 #include "net/base/features.h"
44 #include "pdf/pdf_ppapi.h"
45 #include "sandbox/policy/switches.h"
46 #include "services/network/public/cpp/features.h"
47 #include "third_party/blink/public/common/switches.h"
48 #include "ui/base/resource/resource_bundle.h"
49 #include "ui/base/ui_base_features.h"
50 #include "ui/base/ui_base_paths.h"
51 #include "ui/base/ui_base_switches.h"
52
53 #if BUILDFLAG(IS_MAC)
54 #include "libcef/common/util_mac.h"
55 #endif
56
57 #if BUILDFLAG(IS_WIN)
58 #include "ui/base/resource/resource_bundle_win.h"
59 #endif
60
61 namespace {
62
63 const char* const kNonWildcardDomainNonPortSchemes[] = {
64 extensions::kExtensionScheme, content::kChromeDevToolsScheme,
65 content::kChromeUIScheme, content::kChromeUIUntrustedScheme};
66 const size_t kNonWildcardDomainNonPortSchemesSize =
67 base::size(kNonWildcardDomainNonPortSchemes);
68
69 } // namespace
70
AlloyMainDelegate(CefMainRunnerHandler * runner,CefSettings * settings,CefRefPtr<CefApp> application)71 AlloyMainDelegate::AlloyMainDelegate(CefMainRunnerHandler* runner,
72 CefSettings* settings,
73 CefRefPtr<CefApp> application)
74 : runner_(runner), settings_(settings), application_(application) {
75 // Necessary so that exported functions from base_impl.cc will be included
76 // in the binary.
77 extern void base_impl_stub();
78 base_impl_stub();
79
80 #if BUILDFLAG(IS_LINUX)
81 resource_util::OverrideAssetPath();
82 #endif
83 }
84
~AlloyMainDelegate()85 AlloyMainDelegate::~AlloyMainDelegate() {}
86
PreBrowserMain()87 void AlloyMainDelegate::PreBrowserMain() {
88 runner_->PreBrowserMain();
89 }
90
BasicStartupComplete(int * exit_code)91 bool AlloyMainDelegate::BasicStartupComplete(int* exit_code) {
92 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
93 std::string process_type =
94 command_line->GetSwitchValueASCII(switches::kProcessType);
95
96 #if BUILDFLAG(IS_POSIX)
97 // Read the crash configuration file. Platforms using Breakpad also add a
98 // command-line switch. On Windows this is done from chrome_elf.
99 crash_reporting::BasicStartupComplete(command_line);
100 #endif
101
102 if (process_type.empty()) {
103 // In the browser process. Populate the global command-line object.
104 if (settings_->command_line_args_disabled) {
105 // Remove any existing command-line arguments.
106 base::CommandLine::StringVector argv;
107 argv.push_back(command_line->GetProgram().value());
108 command_line->InitFromArgv(argv);
109
110 const base::CommandLine::SwitchMap& map = command_line->GetSwitches();
111 const_cast<base::CommandLine::SwitchMap*>(&map)->clear();
112 }
113
114 bool no_sandbox = settings_->no_sandbox ? true : false;
115
116 if (settings_->browser_subprocess_path.length > 0) {
117 base::FilePath file_path =
118 base::FilePath(CefString(&settings_->browser_subprocess_path));
119 if (!file_path.empty()) {
120 command_line->AppendSwitchPath(switches::kBrowserSubprocessPath,
121 file_path);
122
123 #if BUILDFLAG(IS_WIN)
124 // The sandbox is not supported when using a separate subprocess
125 // executable on Windows.
126 no_sandbox = true;
127 #endif
128 }
129 }
130
131 #if BUILDFLAG(IS_MAC)
132 if (settings_->framework_dir_path.length > 0) {
133 base::FilePath file_path =
134 base::FilePath(CefString(&settings_->framework_dir_path));
135 if (!file_path.empty())
136 command_line->AppendSwitchPath(switches::kFrameworkDirPath, file_path);
137 }
138
139 if (settings_->main_bundle_path.length > 0) {
140 base::FilePath file_path =
141 base::FilePath(CefString(&settings_->main_bundle_path));
142 if (!file_path.empty())
143 command_line->AppendSwitchPath(switches::kMainBundlePath, file_path);
144 }
145 #endif
146
147 if (no_sandbox)
148 command_line->AppendSwitch(sandbox::policy::switches::kNoSandbox);
149
150 if (settings_->user_agent.length > 0) {
151 command_line->AppendSwitchASCII(
152 embedder_support::kUserAgent,
153 CefString(&settings_->user_agent).ToString());
154 } else if (settings_->user_agent_product.length > 0) {
155 command_line->AppendSwitchASCII(
156 switches::kUserAgentProductAndVersion,
157 CefString(&settings_->user_agent_product).ToString());
158 }
159
160 if (settings_->locale.length > 0) {
161 command_line->AppendSwitchASCII(switches::kLang,
162 CefString(&settings_->locale).ToString());
163 } else if (!command_line->HasSwitch(switches::kLang)) {
164 command_line->AppendSwitchASCII(switches::kLang, "en-US");
165 }
166
167 base::FilePath log_file;
168 bool has_log_file_cmdline = false;
169 if (settings_->log_file.length > 0)
170 log_file = base::FilePath(CefString(&settings_->log_file));
171 if (log_file.empty() && command_line->HasSwitch(switches::kLogFile)) {
172 log_file = command_line->GetSwitchValuePath(switches::kLogFile);
173 if (!log_file.empty())
174 has_log_file_cmdline = true;
175 }
176 if (log_file.empty())
177 log_file = resource_util::GetDefaultLogFilePath();
178 DCHECK(!log_file.empty());
179 if (!has_log_file_cmdline)
180 command_line->AppendSwitchPath(switches::kLogFile, log_file);
181
182 if (settings_->log_severity != LOGSEVERITY_DEFAULT) {
183 std::string log_severity;
184 switch (settings_->log_severity) {
185 case LOGSEVERITY_VERBOSE:
186 log_severity = switches::kLogSeverity_Verbose;
187 break;
188 case LOGSEVERITY_INFO:
189 log_severity = switches::kLogSeverity_Info;
190 break;
191 case LOGSEVERITY_WARNING:
192 log_severity = switches::kLogSeverity_Warning;
193 break;
194 case LOGSEVERITY_ERROR:
195 log_severity = switches::kLogSeverity_Error;
196 break;
197 case LOGSEVERITY_FATAL:
198 log_severity = switches::kLogSeverity_Fatal;
199 break;
200 case LOGSEVERITY_DISABLE:
201 log_severity = switches::kLogSeverity_Disable;
202 break;
203 default:
204 break;
205 }
206 if (!log_severity.empty())
207 command_line->AppendSwitchASCII(switches::kLogSeverity, log_severity);
208 }
209
210 if (settings_->javascript_flags.length > 0) {
211 command_line->AppendSwitchASCII(
212 blink::switches::kJavaScriptFlags,
213 CefString(&settings_->javascript_flags).ToString());
214 }
215
216 if (settings_->pack_loading_disabled) {
217 command_line->AppendSwitch(switches::kDisablePackLoading);
218 } else {
219 if (settings_->resources_dir_path.length > 0) {
220 base::FilePath file_path =
221 base::FilePath(CefString(&settings_->resources_dir_path));
222 if (!file_path.empty()) {
223 command_line->AppendSwitchPath(switches::kResourcesDirPath,
224 file_path);
225 }
226 }
227
228 if (settings_->locales_dir_path.length > 0) {
229 base::FilePath file_path =
230 base::FilePath(CefString(&settings_->locales_dir_path));
231 if (!file_path.empty())
232 command_line->AppendSwitchPath(switches::kLocalesDirPath, file_path);
233 }
234 }
235
236 if (settings_->remote_debugging_port >= 1024 &&
237 settings_->remote_debugging_port <= 65535) {
238 command_line->AppendSwitchASCII(
239 switches::kRemoteDebuggingPort,
240 base::NumberToString(settings_->remote_debugging_port));
241 }
242
243 if (settings_->uncaught_exception_stack_size > 0) {
244 command_line->AppendSwitchASCII(
245 switches::kUncaughtExceptionStackSize,
246 base::NumberToString(settings_->uncaught_exception_stack_size));
247 }
248
249 #if BUILDFLAG(IS_WIN)
250 std::vector<std::string> disable_features;
251
252 if (features::kCalculateNativeWinOcclusion.default_state ==
253 base::FEATURE_ENABLED_BY_DEFAULT) {
254 // TODO: Add support for occlusion detection in combination with native
255 // parent windows (see issue #2805).
256 disable_features.push_back(features::kCalculateNativeWinOcclusion.name);
257 }
258
259 if (spellcheck::kWinUseBrowserSpellChecker.default_state ==
260 base::FEATURE_ENABLED_BY_DEFAULT) {
261 // TODO: Add support for windows spellcheck service (see issue #3055).
262 disable_features.push_back(spellcheck::kWinUseBrowserSpellChecker.name);
263 }
264
265 if (!disable_features.empty()) {
266 DCHECK(!base::FeatureList::GetInstance());
267 std::string disable_features_str =
268 command_line->GetSwitchValueASCII(switches::kDisableFeatures);
269 for (auto feature_str : disable_features) {
270 if (!disable_features_str.empty())
271 disable_features_str += ",";
272 disable_features_str += feature_str;
273 }
274 command_line->AppendSwitchASCII(switches::kDisableFeatures,
275 disable_features_str);
276 }
277 #endif // BUILDFLAG(IS_WIN)
278 }
279
280 if (application_) {
281 // Give the application a chance to view/modify the command line.
282 CefRefPtr<CefCommandLineImpl> commandLinePtr(
283 new CefCommandLineImpl(command_line, false, false));
284 application_->OnBeforeCommandLineProcessing(CefString(process_type),
285 commandLinePtr.get());
286 std::ignore = commandLinePtr->Detach(nullptr);
287 }
288
289 // Initialize logging.
290 logging::LoggingSettings log_settings;
291
292 const base::FilePath& log_file =
293 command_line->GetSwitchValuePath(switches::kLogFile);
294 DCHECK(!log_file.empty());
295 log_settings.log_file_path = log_file.value().c_str();
296
297 log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
298 log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
299
300 logging::LogSeverity log_severity = logging::LOG_INFO;
301
302 std::string log_severity_str =
303 command_line->GetSwitchValueASCII(switches::kLogSeverity);
304 if (!log_severity_str.empty()) {
305 if (base::LowerCaseEqualsASCII(log_severity_str,
306 switches::kLogSeverity_Verbose)) {
307 log_severity = logging::LOG_VERBOSE;
308 } else if (base::LowerCaseEqualsASCII(log_severity_str,
309 switches::kLogSeverity_Warning)) {
310 log_severity = logging::LOG_WARNING;
311 } else if (base::LowerCaseEqualsASCII(log_severity_str,
312 switches::kLogSeverity_Error)) {
313 log_severity = logging::LOG_ERROR;
314 } else if (base::LowerCaseEqualsASCII(log_severity_str,
315 switches::kLogSeverity_Fatal)) {
316 log_severity = logging::LOG_FATAL;
317 } else if (base::LowerCaseEqualsASCII(log_severity_str,
318 switches::kLogSeverity_Disable)) {
319 log_severity = LOGSEVERITY_DISABLE;
320 }
321 }
322
323 if (log_severity == LOGSEVERITY_DISABLE) {
324 log_settings.logging_dest = logging::LOG_NONE;
325 // By default, ERROR and FATAL messages will always be output to stderr due
326 // to the kAlwaysPrintErrorLevel value in base/logging.cc. We change the log
327 // level here so that only FATAL messages are output.
328 logging::SetMinLogLevel(logging::LOG_FATAL);
329 } else {
330 log_settings.logging_dest = logging::LOG_TO_ALL;
331 logging::SetMinLogLevel(log_severity);
332 }
333
334 logging::InitLogging(log_settings);
335
336 ContentSettingsPattern::SetNonWildcardDomainNonPortSchemes(
337 kNonWildcardDomainNonPortSchemes, kNonWildcardDomainNonPortSchemesSize);
338
339 content::SetContentClient(&content_client_);
340
341 #if BUILDFLAG(IS_MAC)
342 util_mac::BasicStartupComplete();
343 #endif
344
345 return false;
346 }
347
PreSandboxStartup()348 void AlloyMainDelegate::PreSandboxStartup() {
349 const base::CommandLine* command_line =
350 base::CommandLine::ForCurrentProcess();
351 const std::string& process_type =
352 command_line->GetSwitchValueASCII(switches::kProcessType);
353
354 if (process_type.empty()) {
355 // Only override these paths when executing the main process.
356 #if BUILDFLAG(IS_MAC)
357 util_mac::PreSandboxStartup();
358 #endif
359
360 resource_util::OverrideDefaultDownloadDir();
361 }
362
363 resource_util::OverrideUserDataDir(settings_, command_line);
364
365 if (command_line->HasSwitch(switches::kDisablePackLoading))
366 resource_bundle_delegate_.set_pack_loading_disabled(true);
367
368 // Initialize crash reporting state for this process/module.
369 // chrome::DIR_CRASH_DUMPS must be configured before calling this function.
370 crash_reporting::PreSandboxStartup(*command_line, process_type);
371
372 // Register the component_updater PathProvider.
373 component_updater::RegisterPathProvider(chrome::DIR_COMPONENTS,
374 chrome::DIR_INTERNAL_PLUGINS,
375 chrome::DIR_USER_DATA);
376
377 InitializeResourceBundle();
378 MaybePatchGdiGetFontData();
379 }
380
SandboxInitialized(const std::string & process_type)381 void AlloyMainDelegate::SandboxInitialized(const std::string& process_type) {
382 AlloyContentClient::SetPDFEntryFunctions(chrome_pdf::PPP_GetInterface,
383 chrome_pdf::PPP_InitializeModule,
384 chrome_pdf::PPP_ShutdownModule);
385 }
386
RunProcess(const std::string & process_type,content::MainFunctionParams main_function_params)387 absl::variant<int, content::MainFunctionParams> AlloyMainDelegate::RunProcess(
388 const std::string& process_type,
389 content::MainFunctionParams main_function_params) {
390 if (process_type.empty()) {
391 return runner_->RunMainProcess(std::move(main_function_params));
392 }
393
394 return std::move(main_function_params);
395 }
396
ProcessExiting(const std::string & process_type)397 void AlloyMainDelegate::ProcessExiting(const std::string& process_type) {
398 ui::ResourceBundle::CleanupSharedInstance();
399 }
400
401 #if BUILDFLAG(IS_LINUX)
ZygoteForked()402 void AlloyMainDelegate::ZygoteForked() {
403 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
404 const std::string& process_type =
405 command_line->GetSwitchValueASCII(switches::kProcessType);
406 // Initialize crash reporting state for the newly forked process.
407 crash_reporting::ZygoteForked(command_line, process_type);
408 }
409 #endif
410
CreateContentBrowserClient()411 content::ContentBrowserClient* AlloyMainDelegate::CreateContentBrowserClient() {
412 browser_client_.reset(new AlloyContentBrowserClient);
413 return browser_client_.get();
414 }
415
416 content::ContentRendererClient*
CreateContentRendererClient()417 AlloyMainDelegate::CreateContentRendererClient() {
418 renderer_client_.reset(new AlloyContentRendererClient);
419 return renderer_client_.get();
420 }
421
CreateContentUtilityClient()422 content::ContentUtilityClient* AlloyMainDelegate::CreateContentUtilityClient() {
423 utility_client_.reset(new ChromeContentUtilityClient);
424 return utility_client_.get();
425 }
426
GetGlobalRequestContext()427 CefRefPtr<CefRequestContext> AlloyMainDelegate::GetGlobalRequestContext() {
428 if (!browser_client_)
429 return nullptr;
430 return browser_client_->request_context();
431 }
432
CreateNewBrowserContext(const CefRequestContextSettings & settings,base::OnceClosure initialized_cb)433 CefBrowserContext* AlloyMainDelegate::CreateNewBrowserContext(
434 const CefRequestContextSettings& settings,
435 base::OnceClosure initialized_cb) {
436 auto context = new AlloyBrowserContext(settings);
437 context->Initialize();
438 std::move(initialized_cb).Run();
439 return context;
440 }
441
442 scoped_refptr<base::SingleThreadTaskRunner>
GetBackgroundTaskRunner()443 AlloyMainDelegate::GetBackgroundTaskRunner() {
444 if (browser_client_)
445 return browser_client_->background_task_runner();
446 return nullptr;
447 }
448
449 scoped_refptr<base::SingleThreadTaskRunner>
GetUserVisibleTaskRunner()450 AlloyMainDelegate::GetUserVisibleTaskRunner() {
451 if (browser_client_)
452 return browser_client_->user_visible_task_runner();
453 return nullptr;
454 }
455
456 scoped_refptr<base::SingleThreadTaskRunner>
GetUserBlockingTaskRunner()457 AlloyMainDelegate::GetUserBlockingTaskRunner() {
458 if (browser_client_)
459 return browser_client_->user_blocking_task_runner();
460 return nullptr;
461 }
462
463 scoped_refptr<base::SingleThreadTaskRunner>
GetRenderTaskRunner()464 AlloyMainDelegate::GetRenderTaskRunner() {
465 if (renderer_client_)
466 return renderer_client_->render_task_runner();
467 return nullptr;
468 }
469
470 scoped_refptr<base::SingleThreadTaskRunner>
GetWebWorkerTaskRunner()471 AlloyMainDelegate::GetWebWorkerTaskRunner() {
472 if (renderer_client_)
473 return renderer_client_->GetCurrentTaskRunner();
474 return nullptr;
475 }
476
InitializeResourceBundle()477 void AlloyMainDelegate::InitializeResourceBundle() {
478 const base::CommandLine* command_line =
479 base::CommandLine::ForCurrentProcess();
480 base::FilePath resources_pak_file, chrome_100_percent_pak_file,
481 chrome_200_percent_pak_file, locales_dir;
482
483 base::FilePath resources_dir;
484 if (command_line->HasSwitch(switches::kResourcesDirPath)) {
485 resources_dir =
486 command_line->GetSwitchValuePath(switches::kResourcesDirPath);
487 }
488 if (resources_dir.empty())
489 resources_dir = resource_util::GetResourcesDir();
490 if (!resources_dir.empty())
491 base::PathService::Override(chrome::DIR_RESOURCES, resources_dir);
492
493 if (!resource_bundle_delegate_.pack_loading_disabled()) {
494 if (!resources_dir.empty()) {
495 CHECK(resources_dir.IsAbsolute());
496 resources_pak_file =
497 resources_dir.Append(FILE_PATH_LITERAL("resources.pak"));
498 chrome_100_percent_pak_file =
499 resources_dir.Append(FILE_PATH_LITERAL("chrome_100_percent.pak"));
500 chrome_200_percent_pak_file =
501 resources_dir.Append(FILE_PATH_LITERAL("chrome_200_percent.pak"));
502 }
503
504 if (command_line->HasSwitch(switches::kLocalesDirPath))
505 locales_dir = command_line->GetSwitchValuePath(switches::kLocalesDirPath);
506
507 if (!locales_dir.empty())
508 base::PathService::Override(ui::DIR_LOCALES, locales_dir);
509 }
510
511 #if BUILDFLAG(IS_WIN)
512 // From chrome/app/chrome_main_delegate.cc
513 // Throbber icons and cursors are still stored in chrome.dll,
514 // this can be killed once those are merged into resources.pak. See
515 // GlassBrowserFrameView::InitThrobberIcons(), https://crbug.com/368327 and
516 // https://crbug.com/1178117.
517 auto module_handle =
518 ::GetModuleHandle(CefAppManager::Get()->GetResourceDllName());
519 if (!module_handle)
520 module_handle = ::GetModuleHandle(NULL);
521
522 ui::SetResourcesDataDLL(module_handle);
523 #endif
524
525 std::string locale = command_line->GetSwitchValueASCII(switches::kLang);
526 DCHECK(!locale.empty());
527
528 const std::string loaded_locale =
529 ui::ResourceBundle::InitSharedInstanceWithLocale(
530 locale, &resource_bundle_delegate_,
531 ui::ResourceBundle::LOAD_COMMON_RESOURCES);
532 if (!loaded_locale.empty() && g_browser_process)
533 g_browser_process->SetApplicationLocale(loaded_locale);
534
535 ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
536
537 if (!resource_bundle_delegate_.pack_loading_disabled()) {
538 if (loaded_locale.empty())
539 LOG(ERROR) << "Could not load locale pak for " << locale;
540
541 resource_bundle_delegate_.set_allow_pack_file_load(true);
542
543 if (base::PathExists(resources_pak_file)) {
544 resource_bundle.AddDataPackFromPath(resources_pak_file,
545 ui::kScaleFactorNone);
546 } else {
547 LOG(ERROR) << "Could not load resources.pak";
548 }
549
550 // Always load the 1x data pack first as the 2x data pack contains both 1x
551 // and 2x images. The 1x data pack only has 1x images, thus passes in an
552 // accurate scale factor to gfx::ImageSkia::AddRepresentation.
553 if (resource_util::IsScaleFactorSupported(ui::k100Percent)) {
554 if (base::PathExists(chrome_100_percent_pak_file)) {
555 resource_bundle.AddDataPackFromPath(chrome_100_percent_pak_file,
556 ui::k100Percent);
557 } else {
558 LOG(ERROR) << "Could not load chrome_100_percent.pak";
559 }
560 }
561
562 if (resource_util::IsScaleFactorSupported(ui::k200Percent)) {
563 if (base::PathExists(chrome_200_percent_pak_file)) {
564 resource_bundle.AddDataPackFromPath(chrome_200_percent_pak_file,
565 ui::k200Percent);
566 } else {
567 LOG(ERROR) << "Could not load chrome_200_percent.pak";
568 }
569 }
570
571 // Skip the default pak file loading that would otherwise occur in
572 // ResourceBundle::LoadChromeResources().
573 resource_bundle_delegate_.set_allow_pack_file_load(false);
574 }
575 }
576