// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "base/lazy_instance.h" #include "base/message_loop.h" #include "base/metrics/field_trial.h" #include "base/metrics/stats_counters.h" #include "base/path_service.h" #include "base/process_util.h" #include "base/stringprintf.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/chrome_content_browser_client.h" #include "chrome/browser/defaults.h" #include "chrome/browser/diagnostics/diagnostics_main.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_content_client.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/logging_chrome.h" #include "chrome/common/profiling.h" #include "chrome/common/url_constants.h" #include "chrome/plugin/chrome_content_plugin_client.h" #include "chrome/renderer/chrome_content_renderer_client.h" #include "chrome/utility/chrome_content_utility_client.h" #include "content/app/content_main.h" #include "content/app/content_main_delegate.h" #include "content/browser/renderer_host/render_process_host.h" #include "content/renderer/renderer_main.h" #include "content/common/content_client.h" #include "content/common/content_counters.h" #include "content/common/content_paths.h" #include "content/common/content_switches.h" #include "media/base/media.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_switches.h" #if defined(OS_WIN) #include #include #include #include "base/string_util.h" #include "base/win/registry.h" #include "chrome/browser/policy/policy_path_parser.h" #include "policy/policy_constants.h" #include "sandbox/src/sandbox.h" #include "tools/memory_watcher/memory_watcher.h" #endif #if defined(OS_MACOSX) #include "base/mac/mac_util.h" #include "base/mac/os_crash_dumps.h" #include "chrome/app/breakpad_mac.h" #include "chrome/app/chrome_main_mac.h" #include "chrome/browser/mac/relauncher.h" #include "chrome/common/chrome_paths_internal.h" #include "chrome/common/mac/cfbundle_blocker.h" #include "chrome/common/mac/objc_zombie.h" #include "grit/chromium_strings.h" #include "third_party/WebKit/Source/WebKit/mac/WebCoreSupport/WebSystemInterface.h" #include "ui/base/l10n/l10n_util_mac.h" #endif #if defined(OS_POSIX) #include #include #endif #if defined(OS_POSIX) && !defined(OS_MACOSX) #include "chrome/common/nacl_fork_delegate_linux.h" #endif #if defined(OS_CHROMEOS) #include "base/sys_info.h" #include "chrome/browser/chromeos/boot_times_loader.h" #endif #if defined(USE_X11) #include #include #include #include #include #include "ui/base/x/x11_util.h" #endif #if defined(USE_LINUX_BREAKPAD) #include "chrome/app/breakpad_linux.h" #endif base::LazyInstance g_chrome_content_browser_client(base::LINKER_INITIALIZED); base::LazyInstance g_chrome_content_renderer_client(base::LINKER_INITIALIZED); base::LazyInstance g_chrome_content_utility_client(base::LINKER_INITIALIZED); base::LazyInstance g_chrome_content_plugin_client(base::LINKER_INITIALIZED); extern int NaClMain(const MainFunctionParams&); extern int ServiceProcessMain(const MainFunctionParams&); #if defined(OS_WIN) #define DLLEXPORT __declspec(dllexport) // We use extern C for the prototype DLLEXPORT to avoid C++ name mangling. extern "C" { DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance, sandbox::SandboxInterfaceInfo* sandbox_info); } #elif defined(OS_POSIX) extern "C" { __attribute__((visibility("default"))) int ChromeMain(int argc, const char** argv); } #endif namespace { #if defined(OS_WIN) const wchar_t kProfilingDll[] = L"memory_watcher.dll"; // Load the memory profiling DLL. All it needs to be activated // is to be loaded. Return true on success, false otherwise. bool LoadMemoryProfiler() { HMODULE prof_module = LoadLibrary(kProfilingDll); return prof_module != NULL; } // Early versions of Chrome incorrectly registered a chromehtml: URL handler, // which gives us nothing but trouble. Avoid launching chrome this way since // some apps fail to properly escape arguments. bool HasDeprecatedArguments(const std::wstring& command_line) { const wchar_t kChromeHtml[] = L"chromehtml:"; std::wstring command_line_lower = command_line; // We are only searching for ASCII characters so this is OK. StringToLowerASCII(&command_line_lower); std::wstring::size_type pos = command_line_lower.find(kChromeHtml); return (pos != std::wstring::npos); } // Checks if the registry key exists in the given hive and expands any // variables in the string. bool LoadUserDataDirPolicyFromRegistry(HKEY hive, const std::wstring& key_name, FilePath* user_data_dir) { std::wstring value; base::win::RegKey hklm_policy_key(hive, policy::kRegistrySubKey, KEY_READ); if (hklm_policy_key.ReadValue(key_name.c_str(), &value) == ERROR_SUCCESS) { *user_data_dir = FilePath(policy::path_parser::ExpandPathVariables(value)); return true; } return false; } void CheckUserDataDirPolicy(FilePath* user_data_dir) { DCHECK(user_data_dir); // We are running as Chrome Frame if we were invoked with user-data-dir, // chrome-frame, and automation-channel switches. CommandLine* command_line = CommandLine::ForCurrentProcess(); const bool is_chrome_frame = !user_data_dir->empty() && command_line->HasSwitch(switches::kChromeFrame) && command_line->HasSwitch(switches::kAutomationClientChannelID); // In the case of Chrome Frame, the last path component of the user-data-dir // provided on the command line must be preserved since it is specific to // CF's host. FilePath cf_host_dir; if (is_chrome_frame) cf_host_dir = user_data_dir->BaseName(); // Policy from the HKLM hive has precedence over HKCU so if we have one here // we don't have to try to load HKCU. const char* key_name_ascii = (is_chrome_frame ? policy::key::kGCFUserDataDir : policy::key::kUserDataDir); std::wstring key_name(ASCIIToWide(key_name_ascii)); if (LoadUserDataDirPolicyFromRegistry(HKEY_LOCAL_MACHINE, key_name, user_data_dir) || LoadUserDataDirPolicyFromRegistry(HKEY_CURRENT_USER, key_name, user_data_dir)) { // A Group Policy value was loaded. Append the Chrome Frame host directory // if relevant. if (is_chrome_frame) *user_data_dir = user_data_dir->Append(cf_host_dir); } } #endif // defined(OS_WIN) #if defined(OS_LINUX) static void AdjustLinuxOOMScore(const std::string& process_type) { // Browsers and zygotes should still be killable, but killed last. const int kZygoteScore = 0; // The minimum amount to bump a score by. This is large enough that // even if it's translated into the old values, it will still go up // by at least one. const int kScoreBump = 100; // This is the lowest score that renderers and extensions start with // in the OomPriorityManager. const int kRendererScore = chrome::kLowestRendererOomScore; // For "miscellaneous" things, we want them after renderers, // but before plugins. const int kMiscScore = kRendererScore - kScoreBump; // We want plugins to die after the renderers. const int kPluginScore = kMiscScore - kScoreBump; int score = -1; DCHECK(kMiscScore > 0); DCHECK(kPluginScore > 0); if (process_type == switches::kPluginProcess || process_type == switches::kPpapiPluginProcess) { score = kPluginScore; } else if (process_type == switches::kPpapiBrokerProcess) { // The broker should be killed before the PPAPI plugin. score = kPluginScore + kScoreBump; } else if (process_type == switches::kUtilityProcess || process_type == switches::kWorkerProcess || process_type == switches::kGpuProcess || process_type == switches::kServiceProcess) { score = kMiscScore; #ifndef DISABLE_NACL } else if (process_type == switches::kNaClLoaderProcess) { score = kPluginScore; #endif } else if (process_type == switches::kZygoteProcess || process_type.empty()) { // For zygotes and unlabeled process types, we want to still make // them killable by the OOM killer. score = kZygoteScore; } else if (process_type == switches::kExtensionProcess || process_type == switches::kRendererProcess) { LOG(WARNING) << "process type '" << process_type << "' " << "should be created through the zygote."; // When debugging, these process types can end up being run // directly, but this isn't the typical path for assigning the OOM // score for them. Still, we want to assign a score that is // somewhat representative for debugging. score = kRendererScore; } else { NOTREACHED() << "Unknown process type"; } if (score > -1) base::AdjustOOMScore(base::GetCurrentProcId(), score); } #endif // defined(OS_LINUX) // Enable the heap profiler if the appropriate command-line switch is // present, bailing out of the app we can't. void EnableHeapProfiler(const CommandLine& command_line) { #if defined(OS_WIN) if (command_line.HasSwitch(switches::kMemoryProfiling)) if (!LoadMemoryProfiler()) exit(-1); #endif } void InitializeChromeContentRendererClient() { content::GetContentClient()->set_renderer( &g_chrome_content_renderer_client.Get()); } void InitializeChromeContentClient(const std::string& process_type) { if (process_type.empty()) { content::GetContentClient()->set_browser( &g_chrome_content_browser_client.Get()); } else if (process_type == switches::kPluginProcess) { content::GetContentClient()->set_plugin( &g_chrome_content_plugin_client.Get()); } else if (process_type == switches::kRendererProcess || process_type == switches::kExtensionProcess) { InitializeChromeContentRendererClient(); } else if (process_type == switches::kUtilityProcess) { content::GetContentClient()->set_utility( &g_chrome_content_utility_client.Get()); } } // Returns true if this subprocess type needs the ResourceBundle initialized // and resources loaded. bool SubprocessNeedsResourceBundle(const std::string& process_type) { return #if defined(OS_WIN) || defined(OS_MACOSX) // Windows needs resources for the default/null plugin. // Mac needs them for the plugin process name. process_type == switches::kPluginProcess || #endif #if defined(OS_POSIX) && !defined(OS_MACOSX) // The zygote process opens the resources for the renderers. process_type == switches::kZygoteProcess || #endif #if defined(OS_MACOSX) // Mac needs them to for scrollbar related images. process_type == switches::kWorkerProcess || #endif process_type == switches::kRendererProcess || process_type == switches::kExtensionProcess || process_type == switches::kUtilityProcess; } #if defined(OS_MACOSX) // Update the name shown in Activity Monitor so users are less likely to ask // why Chrome has so many processes. void SetMacProcessName(const std::string& process_type) { // Don't worry about the browser process, its gets the stock name. int name_id = 0; if (process_type == switches::kRendererProcess) { name_id = IDS_RENDERER_APP_NAME; } else if (process_type == switches::kPluginProcess || process_type == switches::kPpapiPluginProcess) { name_id = IDS_PLUGIN_APP_NAME; } else if (process_type == switches::kExtensionProcess) { name_id = IDS_WORKER_APP_NAME; } else if (process_type == switches::kUtilityProcess) { name_id = IDS_UTILITY_APP_NAME; } if (name_id) { NSString* app_name = l10n_util::GetNSString(name_id); base::mac::SetProcessName(base::mac::NSToCFCast(app_name)); } } #endif // defined(OS_MACOSX) #if defined(OS_POSIX) // Check for --version and --product-version; return true if we encountered // one of these switches and should exit now. bool HandleVersionSwitches(const CommandLine& command_line) { const chrome::VersionInfo version_info; #if !defined(OS_MACOSX) if (command_line.HasSwitch(switches::kProductVersion)) { printf("%s\n", version_info.Version().c_str()); return true; } #endif if (command_line.HasSwitch(switches::kVersion)) { printf("%s %s %s\n", version_info.Name().c_str(), version_info.Version().c_str(), chrome::VersionInfo::GetVersionStringModifier().c_str()); return true; } return false; } #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) // Show the man page if --help or -h is on the command line. void HandleHelpSwitches(const CommandLine& command_line) { if (command_line.HasSwitch(switches::kHelp) || command_line.HasSwitch(switches::kHelpShort)) { FilePath binary(command_line.argv()[0]); execlp("man", "man", binary.BaseName().value().c_str(), NULL); PLOG(FATAL) << "execlp failed"; } } #endif #endif // OS_POSIX struct MainFunction { const char* name; int (*function)(const MainFunctionParams&); }; class ChromeMainDelegate : public content::ContentMainDelegate { public: virtual bool BasicStartupComplete(int* exit_code) OVERRIDE { #if defined(OS_CHROMEOS) chromeos::BootTimesLoader::Get()->SaveChromeMainStats(); #endif #if defined(OS_MACOSX) // TODO(shess): Enable zombies for everyone. http://crbug.com/94551 DCHECK(ObjcEvilDoers::ZombieEnable(true, 1000)); SetUpBundleOverrides(); chrome::common::mac::EnableCFBundleBlocker(); #endif Profiling::ProcessStarted(); const CommandLine& command_line = *CommandLine::ForCurrentProcess(); std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); #if defined(OS_POSIX) if (HandleVersionSwitches(command_line)) { *exit_code = 0; return true; // Got a --version switch; exit with a success error code. } #if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) // This will directly exit if the user asked for help. HandleHelpSwitches(command_line); #endif #endif // OS_POSIX // If we are in diagnostics mode this is the end of the line. After the // diagnostics are run the process will invariably exit. if (command_line.HasSwitch(switches::kDiagnostics)) { *exit_code = DiagnosticsMain(command_line); return true; } #if defined(OS_WIN) // Must do this before any other usage of command line! if (HasDeprecatedArguments(command_line.GetCommandLineString())) { *exit_code = 1; return true; } #endif if (process_type == "" && command_line.HasSwitch(switches::kEnableBenchmarking)) { base::FieldTrial::EnableBenchmarking(); } return false; } #if defined(OS_MACOSX) void InitMacCrashReporter(const CommandLine& command_line, const std::string& process_type) { // TODO(mark): Right now, InitCrashReporter() needs to be called after // CommandLine::Init() and chrome::RegisterPathProvider(). Ideally, // Breakpad initialization could occur sooner, preferably even before the // framework dylib is even loaded, to catch potential early crashes. InitCrashReporter(); #if defined(NDEBUG) bool is_debug_build = false; #else bool is_debug_build = true; #endif // Details on when we enable Apple's Crash reporter. // // Motivation: // In debug mode it takes Apple's crash reporter eons to generate a crash // dump. // // What we do: // * We only pass crashes for foreground processes to Apple's Crash // reporter. At the time of this writing, that means just the Browser // process. // * If Breakpad is enabled, it will pass browser crashes to Crash Reporter // itself. // * If Breakpad is disabled, we only turn on Crash Reporter for the // Browser process in release mode. if (!command_line.HasSwitch(switches::kDisableBreakpad)) { bool disable_apple_crash_reporter = is_debug_build || base::mac::IsBackgroundOnlyProcess(); if (!IsCrashReporterEnabled() && disable_apple_crash_reporter) { base::mac::DisableOSCrashDumps(); } } // Mac Chrome is packaged with a main app bundle and a helper app bundle. // The main app bundle should only be used for the browser process, so it // should never see a --type switch (switches::kProcessType). Likewise, // the helper should always have a --type switch. // // This check is done this late so there is already a call to // base::mac::IsBackgroundOnlyProcess(), so there is no change in // startup/initialization order. // The helper's Info.plist marks it as a background only app. if (base::mac::IsBackgroundOnlyProcess()) { CHECK(command_line.HasSwitch(switches::kProcessType) && !process_type.empty()) << "Helper application requires --type."; // In addition, some helper flavors only work with certain process types. FilePath executable; if (PathService::Get(base::FILE_EXE, &executable) && executable.value().size() >= 3) { std::string last_three = executable.value().substr(executable.value().size() - 3); if (last_three == " EH") { CHECK(process_type == switches::kPluginProcess || process_type == switches::kUtilityProcess) << "Executable-heap process requires --type=" << switches::kPluginProcess << " or " << switches::kUtilityProcess << ", saw " << process_type; } else if (last_three == " NP") { CHECK_EQ(switches::kNaClLoaderProcess, process_type) << "Non-PIE process requires --type=" << switches::kNaClLoaderProcess << ", saw " << process_type; } else { CHECK(process_type != switches::kPluginProcess && process_type != switches::kNaClLoaderProcess) << "Non-executable-heap PIE process is intolerant of --type=" << switches::kPluginProcess << " and " << switches::kNaClLoaderProcess << ", saw " << process_type; } } } else { CHECK(!command_line.HasSwitch(switches::kProcessType) && process_type.empty()) << "Main application forbids --type, saw " << process_type; } if (IsCrashReporterEnabled()) InitCrashProcessInfo(); } #endif // defined(OS_MACOSX) virtual void PreSandboxStartup() OVERRIDE { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); // Initialize the content client which that code uses to talk to Chrome. content::SetContentClient(&chrome_content_client_); InitializeChromeContentClient(process_type); // Initialize the Chrome path provider. chrome::RegisterPathProvider(); #if defined(OS_MACOSX) // On the Mac, the child executable lives at a predefined location within // the app bundle's versioned directory. PathService::Override(content::CHILD_PROCESS_EXE, chrome::GetVersionedDirectory(). Append(chrome::kHelperProcessExecutablePath)); InitMacCrashReporter(command_line, process_type); #endif // Notice a user data directory override if any FilePath user_data_dir = command_line.GetSwitchValuePath(switches::kUserDataDir); #if defined(OS_MACOSX) || defined(OS_WIN) CheckUserDataDirPolicy(&user_data_dir); #endif if (!user_data_dir.empty()) CHECK(PathService::Override(chrome::DIR_USER_DATA, user_data_dir)); startup_timer_.reset(new base::StatsScope (content::Counters::chrome_main())); // Enable the heap profiler as early as possible! EnableHeapProfiler(command_line); // Enable Message Loop related state asap. if (command_line.HasSwitch(switches::kMessageLoopHistogrammer)) MessageLoop::EnableHistogrammer(true); // Single-process is an unsupported and not fully tested mode, so // don't enable it for official Chrome builds. #if !defined(GOOGLE_CHROME_BUILD) if (command_line.HasSwitch(switches::kSingleProcess)) { RenderProcessHost::set_run_renderer_in_process(true); #if defined(OS_MACOSX) // TODO(port-mac): This is from renderer_main_platform_delegate.cc. // shess tried to refactor things appropriately, but it sprawled out // of control because different platforms needed different styles of // initialization. Try again once we understand the process // architecture needed and where it should live. InitWebCoreSystemInterface(); #endif InitializeChromeContentRendererClient(); } #endif // GOOGLE_CHROME_BUILD logging::OldFileDeletionState file_state = logging::APPEND_TO_OLD_LOG_FILE; if (process_type.empty()) { file_state = logging::DELETE_OLD_LOG_FILE; } logging::InitChromeLogging(command_line, file_state); // Register internal Chrome schemes so they'll be parsed correctly. This // must happen before we process any URLs with the affected schemes, and // must be done in all processes that work with these URLs (i.e. including // renderers). chrome::RegisterChromeSchemes(); #if defined(OS_WIN) // TODO(darin): Kill this once http://crbug.com/52609 is fixed. ResourceBundle::SetResourcesDataDLL(_AtlBaseModule.GetResourceInstance()); #endif if (SubprocessNeedsResourceBundle(process_type)) { // Initialize ResourceBundle which handles files loaded from external // sources. The language should have been passed in to us from the // browser process as a command line flag. DCHECK(command_line.HasSwitch(switches::kLang) || process_type == switches::kZygoteProcess); // TODO(markusheintz): The command line flag --lang is actually processed // by the CommandLinePrefStore, and made available through the PrefService // via the preference prefs::kApplicationLocale. The browser process uses // the --lang flag to pass the value of the PrefService in here. Maybe // this value could be passed in a different way. const std::string locale = command_line.GetSwitchValueASCII(switches::kLang); const std::string loaded_locale = ResourceBundle::InitSharedInstance(locale); CHECK(!loaded_locale.empty()) << "Locale could not be found for " << locale; #if defined(OS_MACOSX) // Update the process name (need resources to get the strings, so // only do this when ResourcesBundle has been initialized). SetMacProcessName(process_type); #endif // defined(OS_MACOSX) } #if defined(USE_LINUX_BREAKPAD) // Needs to be called after we have chrome::DIR_USER_DATA. BrowserMain // sets this up for the browser process in a different manner. Zygotes // need to call InitCrashReporter() in RunZygote(). if (!process_type.empty() && process_type != switches::kZygoteProcess) InitCrashReporter(); #endif #if defined(OS_CHROMEOS) // Read and cache ChromeOS version from file, // to be used from inside the sandbox. int32 major_version, minor_version, bugfix_version; base::SysInfo::OperatingSystemVersionNumbers( &major_version, &minor_version, &bugfix_version); #endif } virtual void SandboxInitialized(const std::string& process_type) OVERRIDE { startup_timer_->Stop(); // End of Startup Time Measurement. // Note: If you are adding a new process type below, be sure to adjust the // AdjustLinuxOOMScore function too. #if defined(OS_LINUX) AdjustLinuxOOMScore(process_type); #endif } virtual int RunProcess( const std::string& process_type, const MainFunctionParams& main_function_params) OVERRIDE { static const MainFunction kMainFunctions[] = { // An extension process is just a renderer process. We use a different // command line argument to differentiate crash reports. { switches::kExtensionProcess, RendererMain }, { switches::kServiceProcess, ServiceProcessMain }, #if defined(OS_MACOSX) { switches::kRelauncherProcess, mac_relauncher::internal::RelauncherMain }, #endif #if !defined(DISABLE_NACL) { switches::kNaClLoaderProcess, NaClMain }, #endif // DISABLE_NACL }; for (size_t i = 0; i < arraysize(kMainFunctions); ++i) { if (process_type == kMainFunctions[i].name) return kMainFunctions[i].function(main_function_params); } NOTREACHED() << "Unknown process type: " << process_type; return 1; } virtual void ProcessExiting(const std::string& process_type) OVERRIDE { if (SubprocessNeedsResourceBundle(process_type)) ResourceBundle::CleanupSharedInstance(); logging::CleanupChromeLogging(); #if defined(OS_MACOSX) && defined(GOOGLE_CHROME_BUILD) // TODO(mark): See the TODO(mark) at InitCrashReporter. DestructCrashReporter(); #endif // OS_MACOSX && GOOGLE_CHROME_BUILD } #if defined(OS_MACOSX) virtual bool ProcessRegistersWithSystemProcess( const std::string& process_type) OVERRIDE { return process_type == switches::kNaClLoaderProcess || process_type == switches::kExtensionProcess; } virtual bool ShouldSendMachPort(const std::string& process_type) OVERRIDE { return process_type != switches::kRelauncherProcess && process_type != switches::kServiceProcess; } virtual bool DelaySandboxInitialization( const std::string& process_type) OVERRIDE { // Extensions are really renderers. // NaClLoader does this in NaClMainPlatformDelegate::EnableSandbox(). // No sandbox needed for relauncher. return process_type == switches::kExtensionProcess || process_type == switches::kNaClLoaderProcess || process_type == switches::kRelauncherProcess; } #elif defined(OS_POSIX) virtual ZygoteForkDelegate* ZygoteStarting() OVERRIDE { // Each Renderer we spawn will re-attempt initialization of the media // libraries, at which point failure will be detected and handled, so // we do not need to cope with initialization failures here. FilePath media_path; if (PathService::Get(chrome::DIR_MEDIA_LIBS, &media_path)) media::InitializeMediaLibrary(media_path); #if defined(DISABLE_NACL) return NULL; #else return new NaClForkDelegate(); #endif } virtual void ZygoteForked() OVERRIDE { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); #if defined(USE_LINUX_BREAKPAD) // Needs to be called after we have chrome::DIR_USER_DATA. BrowserMain sets // this up for the browser process in a different manner. InitCrashReporter(); #endif InitializeChromeContentClient(process_type); } #endif // OS_MACOSX private: chrome::ChromeContentClient chrome_content_client_; scoped_ptr > startup_timer_; }; } // namespace #if defined(OS_WIN) DLLEXPORT int __cdecl ChromeMain(HINSTANCE instance, sandbox::SandboxInterfaceInfo* sandbox_info) { ChromeMainDelegate chrome_main_delegate; return content::ContentMain(instance, sandbox_info, &chrome_main_delegate); #elif defined(OS_POSIX) int ChromeMain(int argc, const char** argv) { ChromeMainDelegate chrome_main_delegate; return content::ContentMain(argc, argv, &chrome_main_delegate); #endif }