// Copyright (c) 2013 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 "chrome/test/chromedriver/chrome_launcher.h" #include #include #include #include #include #include "base/base64.h" #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/format_macros.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/process/kill.h" #include "base/process/launch.h" #include "base/process/process.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/common/chrome_constants.h" #include "chrome/test/chromedriver/chrome/chrome_android_impl.h" #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" #include "chrome/test/chromedriver/chrome/chrome_finder.h" #include "chrome/test/chromedriver/chrome/chrome_remote_impl.h" #include "chrome/test/chromedriver/chrome/device_manager.h" #include "chrome/test/chromedriver/chrome/devtools_client_impl.h" #include "chrome/test/chromedriver/chrome/devtools_event_listener.h" #include "chrome/test/chromedriver/chrome/devtools_http_client.h" #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h" #include "chrome/test/chromedriver/chrome/status.h" #include "chrome/test/chromedriver/chrome/user_data_dir.h" #include "chrome/test/chromedriver/chrome/version.h" #include "chrome/test/chromedriver/chrome/web_view.h" #include "chrome/test/chromedriver/net/port_server.h" #include "chrome/test/chromedriver/net/url_request_context_getter.h" #include "crypto/rsa_private_key.h" #include "crypto/sha2.h" #include "third_party/zlib/google/zip.h" #if defined(OS_POSIX) #include #include #include #elif defined(OS_WIN) #include "chrome/test/chromedriver/keycode_text_conversion.h" #endif namespace { const char* const kCommonSwitches[] = { "disable-popup-blocking", "ignore-certificate-errors", "metrics-recording-only" }; const char* const kDesktopSwitches[] = { "disable-hang-monitor", "disable-prompt-on-repost", "disable-sync", "no-first-run", "disable-background-networking", "disable-web-resources", "safebrowsing-disable-auto-update", "safebrowsing-disable-download-protection", "disable-client-side-phishing-detection", "disable-component-update", "disable-default-apps", "enable-logging", "log-level=0", "password-store=basic", "use-mock-keychain", "test-type=webdriver" }; const char* const kAndroidSwitches[] = { "disable-fre", "enable-remote-debugging" }; #if defined(OS_LINUX) const char kEnableCrashReport[] = "enable-crash-reporter-for-testing"; #endif Status UnpackAutomationExtension(const base::FilePath& temp_dir, base::FilePath* automation_extension) { std::string decoded_extension; if (!base::Base64Decode(kAutomationExtension, &decoded_extension)) return Status(kUnknownError, "failed to base64decode automation extension"); base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip"); int size = static_cast(decoded_extension.length()); if (base::WriteFile(extension_zip, decoded_extension.c_str(), size) != size) { return Status(kUnknownError, "failed to write automation extension zip"); } base::FilePath extension_dir = temp_dir.AppendASCII("internal"); if (!zip::Unzip(extension_zip, extension_dir)) return Status(kUnknownError, "failed to unzip automation extension"); *automation_extension = extension_dir; return Status(kOk); } Status PrepareCommandLine(uint16_t port, const Capabilities& capabilities, base::CommandLine* prepared_command, base::ScopedTempDir* user_data_dir, base::ScopedTempDir* extension_dir, std::vector* extension_bg_pages) { base::FilePath program = capabilities.binary; if (program.empty()) { if (!FindChrome(&program)) return Status(kUnknownError, "cannot find Chrome binary"); } else if (!base::PathExists(program)) { return Status(kUnknownError, base::StringPrintf("no chrome binary at %" PRFilePath, program.value().c_str())); } base::CommandLine command(program); Switches switches; for (const auto& common_switch : kCommonSwitches) switches.SetUnparsedSwitch(common_switch); for (const auto& desktop_switch : kDesktopSwitches) switches.SetUnparsedSwitch(desktop_switch); switches.SetSwitch("remote-debugging-port", base::UintToString(port)); for (const auto& excluded_switch : capabilities.exclude_switches) { switches.RemoveSwitch(excluded_switch); } switches.SetFromSwitches(capabilities.switches); base::FilePath user_data_dir_path; if (switches.HasSwitch("user-data-dir")) { user_data_dir_path = base::FilePath( switches.GetSwitchValueNative("user-data-dir")); } else { command.AppendArg("data:,"); if (!user_data_dir->CreateUniqueTempDir()) return Status(kUnknownError, "cannot create temp dir for user data dir"); switches.SetSwitch("user-data-dir", user_data_dir->path().value()); user_data_dir_path = user_data_dir->path(); } Status status = internal::PrepareUserDataDir(user_data_dir_path, capabilities.prefs.get(), capabilities.local_state.get()); if (status.IsError()) return status; if (!extension_dir->CreateUniqueTempDir()) { return Status(kUnknownError, "cannot create temp dir for unpacking extensions"); } status = internal::ProcessExtensions(capabilities.extensions, extension_dir->path(), true, &switches, extension_bg_pages); if (status.IsError()) return status; switches.AppendToCommandLine(&command); *prepared_command = command; return Status(kOk); } Status WaitForDevToolsAndCheckVersion( const NetAddress& address, URLRequestContextGetter* context_getter, const SyncWebSocketFactory& socket_factory, const Capabilities* capabilities, scoped_ptr* user_client) { scoped_ptr device_metrics; if (capabilities && capabilities->device_metrics) device_metrics.reset(new DeviceMetrics(*capabilities->device_metrics)); scoped_ptr> window_types; if (capabilities && !capabilities->window_types.empty()) { window_types.reset( new std::set(capabilities->window_types)); } else { window_types.reset(new std::set()); } scoped_ptr client(new DevToolsHttpClient( address, context_getter, socket_factory, std::move(device_metrics), std::move(window_types))); base::TimeTicks deadline = base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60); Status status = client->Init(deadline - base::TimeTicks::Now()); if (status.IsError()) return status; base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); if (cmd_line->HasSwitch("disable-build-check")) { LOG(WARNING) << "You are using an unsupported command-line switch: " "--disable-build-check. Please don't report bugs that " "cannot be reproduced with this switch removed."; } else if (client->browser_info()->build_no < kMinimumSupportedChromeBuildNo) { return Status(kUnknownError, "Chrome version must be >= " + GetMinimumSupportedChromeVersion()); } while (base::TimeTicks::Now() < deadline) { WebViewsInfo views_info; client->GetWebViewsInfo(&views_info); for (size_t i = 0; i < views_info.GetSize(); ++i) { if (views_info.Get(i).type == WebViewInfo::kPage) { *user_client = std::move(client); return Status(kOk); } } base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); } return Status(kUnknownError, "unable to discover open pages"); } Status CreateBrowserwideDevToolsClientAndConnect( const NetAddress& address, const PerfLoggingPrefs& perf_logging_prefs, const SyncWebSocketFactory& socket_factory, const ScopedVector& devtools_event_listeners, scoped_ptr* browser_client) { scoped_ptr client(new DevToolsClientImpl( socket_factory, base::StringPrintf("ws://%s/devtools/browser/", address.ToString().c_str()), DevToolsClientImpl::kBrowserwideDevToolsClientId)); for (ScopedVector::const_iterator it = devtools_event_listeners.begin(); it != devtools_event_listeners.end(); ++it) { // Only add listeners that subscribe to the browser-wide |DevToolsClient|. // Otherwise, listeners will think this client is associated with a webview, // and will send unrecognized commands to it. if ((*it)->subscribes_to_browser()) client->AddListener(*it); } // Provide the client regardless of whether it connects, so that Chrome always // has a valid |devtools_websocket_client_|. If not connected, no listeners // will be notified, and client will just return kDisconnected errors if used. *browser_client = std::move(client); // To avoid unnecessary overhead, only connect if tracing is enabled, since // the browser-wide client is currently only used for tracing. if (!perf_logging_prefs.trace_categories.empty()) { Status status = (*browser_client)->ConnectIfNecessary(); if (status.IsError()) return status; } return Status(kOk); } Status LaunchRemoteChromeSession( URLRequestContextGetter* context_getter, const SyncWebSocketFactory& socket_factory, const Capabilities& capabilities, ScopedVector* devtools_event_listeners, scoped_ptr* chrome) { Status status(kOk); scoped_ptr devtools_http_client; status = WaitForDevToolsAndCheckVersion( capabilities.debugger_address, context_getter, socket_factory, NULL, &devtools_http_client); if (status.IsError()) { return Status(kUnknownError, "cannot connect to chrome at " + capabilities.debugger_address.ToString(), status); } scoped_ptr devtools_websocket_client; status = CreateBrowserwideDevToolsClientAndConnect( capabilities.debugger_address, capabilities.perf_logging_prefs, socket_factory, *devtools_event_listeners, &devtools_websocket_client); if (status.IsError()) { LOG(WARNING) << "Browser-wide DevTools client failed to connect: " << status.message(); } chrome->reset(new ChromeRemoteImpl(std::move(devtools_http_client), std::move(devtools_websocket_client), *devtools_event_listeners)); return Status(kOk); } Status LaunchDesktopChrome( URLRequestContextGetter* context_getter, uint16_t port, scoped_ptr port_reservation, const SyncWebSocketFactory& socket_factory, const Capabilities& capabilities, ScopedVector* devtools_event_listeners, scoped_ptr* chrome) { base::CommandLine command(base::CommandLine::NO_PROGRAM); base::ScopedTempDir user_data_dir; base::ScopedTempDir extension_dir; std::vector extension_bg_pages; Status status = PrepareCommandLine(port, capabilities, &command, &user_data_dir, &extension_dir, &extension_bg_pages); if (status.IsError()) return status; base::LaunchOptions options; #if defined(OS_LINUX) // If minidump path is set in the capability, enable minidump for crashes. if (!capabilities.minidump_path.empty()) { VLOG(0) << "Minidump generation specified. Will save dumps to: " << capabilities.minidump_path; options.environ["CHROME_HEADLESS"] = 1; options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path; if (!command.HasSwitch(kEnableCrashReport)) command.AppendSwitch(kEnableCrashReport); } // We need to allow new privileges so that chrome's setuid sandbox can run. options.allow_new_privs = true; #endif #if !defined(OS_WIN) if (!capabilities.log_path.empty()) options.environ["CHROME_LOG_FILE"] = capabilities.log_path; if (capabilities.detach) options.new_process_group = true; #endif #if defined(OS_POSIX) base::FileHandleMappingVector no_stderr; base::ScopedFD devnull; if (!base::CommandLine::ForCurrentProcess()->HasSwitch("verbose")) { // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse // users. devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY))); if (!devnull.is_valid()) return Status(kUnknownError, "couldn't open /dev/null"); no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); options.fds_to_remap = &no_stderr; } #elif defined(OS_WIN) if (!SwitchToUSKeyboardLayout()) VLOG(0) << "Can not set to US keyboard layout - Some keycodes may be" "interpreted incorrectly"; #endif #if defined(OS_WIN) std::string command_string = base::WideToUTF8(command.GetCommandLineString()); #else std::string command_string = command.GetCommandLineString(); #endif VLOG(0) << "Launching chrome: " << command_string; base::Process process = base::LaunchProcess(command, options); if (!process.IsValid()) return Status(kUnknownError, "chrome failed to start"); scoped_ptr devtools_http_client; status = WaitForDevToolsAndCheckVersion( NetAddress(port), context_getter, socket_factory, &capabilities, &devtools_http_client); if (status.IsError()) { int exit_code; base::TerminationStatus chrome_status = base::GetTerminationStatus(process.Handle(), &exit_code); if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) { std::string termination_reason; switch (chrome_status) { case base::TERMINATION_STATUS_NORMAL_TERMINATION: termination_reason = "exited normally"; break; case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: termination_reason = "exited abnormally"; break; case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: #if defined(OS_CHROMEOS) case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM: #endif termination_reason = "was killed"; break; case base::TERMINATION_STATUS_PROCESS_CRASHED: termination_reason = "crashed"; break; case base::TERMINATION_STATUS_LAUNCH_FAILED: termination_reason = "failed to launch"; break; default: termination_reason = "unknown"; break; } return Status(kUnknownError, "Chrome failed to start: " + termination_reason); } if (!process.Terminate(0, true)) { int exit_code; if (base::GetTerminationStatus(process.Handle(), &exit_code) == base::TERMINATION_STATUS_STILL_RUNNING) return Status(kUnknownError, "cannot kill Chrome", status); } return status; } scoped_ptr devtools_websocket_client; status = CreateBrowserwideDevToolsClientAndConnect( NetAddress(port), capabilities.perf_logging_prefs, socket_factory, *devtools_event_listeners, &devtools_websocket_client); if (status.IsError()) { LOG(WARNING) << "Browser-wide DevTools client failed to connect: " << status.message(); } scoped_ptr chrome_desktop(new ChromeDesktopImpl( std::move(devtools_http_client), std::move(devtools_websocket_client), *devtools_event_listeners, std::move(port_reservation), std::move(process), command, &user_data_dir, &extension_dir)); for (size_t i = 0; i < extension_bg_pages.size(); ++i) { VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages[i]; scoped_ptr web_view; Status status = chrome_desktop->WaitForPageToLoad( extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view); if (status.IsError()) { return Status(kUnknownError, "failed to wait for extension background page to load: " + extension_bg_pages[i], status); } } *chrome = std::move(chrome_desktop); return Status(kOk); } Status LaunchAndroidChrome( URLRequestContextGetter* context_getter, uint16_t port, scoped_ptr port_reservation, const SyncWebSocketFactory& socket_factory, const Capabilities& capabilities, ScopedVector* devtools_event_listeners, DeviceManager* device_manager, scoped_ptr* chrome) { Status status(kOk); scoped_ptr device; if (capabilities.android_device_serial.empty()) { status = device_manager->AcquireDevice(&device); } else { status = device_manager->AcquireSpecificDevice( capabilities.android_device_serial, &device); } if (status.IsError()) return status; Switches switches(capabilities.switches); for (auto common_switch : kCommonSwitches) switches.SetUnparsedSwitch(common_switch); for (auto android_switch : kAndroidSwitches) switches.SetUnparsedSwitch(android_switch); for (auto excluded_switch : capabilities.exclude_switches) switches.RemoveSwitch(excluded_switch); status = device->SetUp(capabilities.android_package, capabilities.android_activity, capabilities.android_process, switches.ToString(), capabilities.android_use_running_app, port); if (status.IsError()) { device->TearDown(); return status; } scoped_ptr devtools_http_client; status = WaitForDevToolsAndCheckVersion(NetAddress(port), context_getter, socket_factory, &capabilities, &devtools_http_client); if (status.IsError()) { device->TearDown(); return status; } scoped_ptr devtools_websocket_client; status = CreateBrowserwideDevToolsClientAndConnect( NetAddress(port), capabilities.perf_logging_prefs, socket_factory, *devtools_event_listeners, &devtools_websocket_client); if (status.IsError()) { LOG(WARNING) << "Browser-wide DevTools client failed to connect: " << status.message(); } chrome->reset(new ChromeAndroidImpl( std::move(devtools_http_client), std::move(devtools_websocket_client), *devtools_event_listeners, std::move(port_reservation), std::move(device))); return Status(kOk); } } // namespace Status LaunchChrome( URLRequestContextGetter* context_getter, const SyncWebSocketFactory& socket_factory, DeviceManager* device_manager, PortServer* port_server, PortManager* port_manager, const Capabilities& capabilities, ScopedVector* devtools_event_listeners, scoped_ptr* chrome) { if (capabilities.IsRemoteBrowser()) { return LaunchRemoteChromeSession( context_getter, socket_factory, capabilities, devtools_event_listeners, chrome); } uint16_t port = 0; scoped_ptr port_reservation; Status port_status(kOk); if (capabilities.IsAndroid()) { if (port_server) port_status = port_server->ReservePort(&port, &port_reservation); else port_status = port_manager->ReservePortFromPool(&port, &port_reservation); if (port_status.IsError()) return Status(kUnknownError, "cannot reserve port for Chrome", port_status); return LaunchAndroidChrome( context_getter, port, std::move(port_reservation), socket_factory, capabilities, devtools_event_listeners, device_manager, chrome); } else { if (port_server) port_status = port_server->ReservePort(&port, &port_reservation); else port_status = port_manager->ReservePort(&port, &port_reservation); if (port_status.IsError()) return Status(kUnknownError, "cannot reserve port for Chrome", port_status); return LaunchDesktopChrome(context_getter, port, std::move(port_reservation), socket_factory, capabilities, devtools_event_listeners, chrome); } } namespace internal { void ConvertHexadecimalToIDAlphabet(std::string* id) { for (size_t i = 0; i < id->size(); ++i) { int val; if (base::HexStringToInt(base::StringPiece(id->begin() + i, id->begin() + i + 1), &val)) { (*id)[i] = val + 'a'; } else { (*id)[i] = 'a'; } } } std::string GenerateExtensionId(const std::string& input) { uint8_t hash[16]; crypto::SHA256HashString(input, hash, sizeof(hash)); std::string output = base::ToLowerASCII(base::HexEncode(hash, sizeof(hash))); ConvertHexadecimalToIDAlphabet(&output); return output; } Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest, const std::string& id, std::string* bg_page) { std::string bg_page_name; bool persistent = true; manifest->GetBoolean("background.persistent", &persistent); const base::Value* unused_value; if (manifest->Get("background.scripts", &unused_value)) bg_page_name = "_generated_background_page.html"; manifest->GetString("background.page", &bg_page_name); manifest->GetString("background_page", &bg_page_name); if (bg_page_name.empty() || !persistent) return Status(kOk); *bg_page = "chrome-extension://" + id + "/" + bg_page_name; return Status(kOk); } Status ProcessExtension(const std::string& extension, const base::FilePath& temp_dir, base::FilePath* path, std::string* bg_page) { // Decodes extension string. // Some WebDriver client base64 encoders follow RFC 1521, which require that // 'encoded lines be no more than 76 characters long'. Just remove any // newlines. std::string extension_base64; base::RemoveChars(extension, "\n", &extension_base64); std::string decoded_extension; if (!base::Base64Decode(extension_base64, &decoded_extension)) return Status(kUnknownError, "cannot base64 decode"); // If the file is a crx file, extract the extension's ID from its public key. // Otherwise generate a random public key and use its derived extension ID. std::string public_key; std::string magic_header = decoded_extension.substr(0, 4); if (magic_header.size() != 4) return Status(kUnknownError, "cannot extract magic number"); const bool is_crx_file = magic_header == "Cr24"; if (is_crx_file) { // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx. std::string key_len_str = decoded_extension.substr(8, 4); if (key_len_str.size() != 4) return Status(kUnknownError, "cannot extract public key length"); uint32_t key_len = *reinterpret_cast(key_len_str.c_str()); public_key = decoded_extension.substr(16, key_len); if (key_len != public_key.size()) return Status(kUnknownError, "invalid public key length"); } else { // Not a CRX file. Generate RSA keypair to get a valid extension id. scoped_ptr key_pair( crypto::RSAPrivateKey::Create(2048)); if (!key_pair) return Status(kUnknownError, "cannot generate RSA key pair"); std::vector public_key_vector; if (!key_pair->ExportPublicKey(&public_key_vector)) return Status(kUnknownError, "cannot extract public key"); public_key = std::string(reinterpret_cast(&public_key_vector.front()), public_key_vector.size()); } std::string public_key_base64; base::Base64Encode(public_key, &public_key_base64); std::string id = GenerateExtensionId(public_key); // Unzip the crx file. base::ScopedTempDir temp_crx_dir; if (!temp_crx_dir.CreateUniqueTempDir()) return Status(kUnknownError, "cannot create temp dir"); base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); int size = static_cast(decoded_extension.length()); if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) != size) { return Status(kUnknownError, "cannot write file"); } base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id); if (!zip::Unzip(extension_crx, extension_dir)) return Status(kUnknownError, "cannot unzip"); // Parse the manifest and set the 'key' if not already present. base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json")); std::string manifest_data; if (!base::ReadFileToString(manifest_path, &manifest_data)) return Status(kUnknownError, "cannot read manifest"); scoped_ptr manifest_value = base::JSONReader::Read(manifest_data); base::DictionaryValue* manifest; if (!manifest_value || !manifest_value->GetAsDictionary(&manifest)) return Status(kUnknownError, "invalid manifest"); std::string manifest_key_base64; if (manifest->GetString("key", &manifest_key_base64)) { // If there is a key in both the header and the manifest, use the key in the // manifest. This allows chromedriver users users who generate dummy crxs // to set the manifest key and have a consistent ID. std::string manifest_key; if (!base::Base64Decode(manifest_key_base64, &manifest_key)) return Status(kUnknownError, "'key' in manifest is not base64 encoded"); std::string manifest_id = GenerateExtensionId(manifest_key); if (id != manifest_id) { if (is_crx_file) { LOG(WARNING) << "Public key in crx header is different from key in manifest" << std::endl << "key from header: " << public_key_base64 << std::endl << "key from manifest: " << manifest_key_base64 << std::endl << "generated extension id from header key: " << id << std::endl << "generated extension id from manifest key: " << manifest_id; } id = manifest_id; } } else { manifest->SetString("key", public_key_base64); base::JSONWriter::Write(*manifest, &manifest_data); if (base::WriteFile( manifest_path, manifest_data.c_str(), manifest_data.size()) != static_cast(manifest_data.size())) { return Status(kUnknownError, "cannot add 'key' to manifest"); } } // Get extension's background page URL, if there is one. std::string bg_page_tmp; Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp); if (status.IsError()) return status; *path = extension_dir; if (bg_page_tmp.size()) *bg_page = bg_page_tmp; return Status(kOk); } void UpdateExtensionSwitch(Switches* switches, const char name[], const base::FilePath::StringType& extension) { base::FilePath::StringType value = switches->GetSwitchValueNative(name); if (value.length()) value += FILE_PATH_LITERAL(","); value += extension; switches->SetSwitch(name, value); } Status ProcessExtensions(const std::vector& extensions, const base::FilePath& temp_dir, bool include_automation_extension, Switches* switches, std::vector* bg_pages) { std::vector bg_pages_tmp; std::vector extension_paths; for (size_t i = 0; i < extensions.size(); ++i) { base::FilePath path; std::string bg_page; Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page); if (status.IsError()) { return Status( kUnknownError, base::StringPrintf("cannot process extension #%" PRIuS, i + 1), status); } extension_paths.push_back(path.value()); if (bg_page.length()) bg_pages_tmp.push_back(bg_page); } if (include_automation_extension) { base::FilePath automation_extension; Status status = UnpackAutomationExtension(temp_dir, &automation_extension); if (status.IsError()) return status; if (switches->HasSwitch("disable-extensions")) { UpdateExtensionSwitch(switches, "load-component-extension", automation_extension.value()); } else { extension_paths.push_back(automation_extension.value()); } } if (extension_paths.size()) { base::FilePath::StringType extension_paths_value = base::JoinString( extension_paths, base::FilePath::StringType(1, ',')); UpdateExtensionSwitch(switches, "load-extension", extension_paths_value); } bg_pages->swap(bg_pages_tmp); return Status(kOk); } Status WritePrefsFile( const std::string& template_string, const base::DictionaryValue* custom_prefs, const base::FilePath& path) { int code; std::string error_msg; scoped_ptr template_value = base::JSONReader::ReadAndReturnError( template_string, 0, &code, &error_msg); base::DictionaryValue* prefs; if (!template_value || !template_value->GetAsDictionary(&prefs)) { return Status(kUnknownError, "cannot parse internal JSON template: " + error_msg); } if (custom_prefs) { for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd(); it.Advance()) { prefs->Set(it.key(), it.value().DeepCopy()); } } std::string prefs_str; base::JSONWriter::Write(*prefs, &prefs_str); VLOG(0) << "Populating " << path.BaseName().value() << " file: " << PrettyPrintValue(*prefs); if (static_cast(prefs_str.length()) != base::WriteFile( path, prefs_str.c_str(), prefs_str.length())) { return Status(kUnknownError, "failed to write prefs file"); } return Status(kOk); } Status PrepareUserDataDir( const base::FilePath& user_data_dir, const base::DictionaryValue* custom_prefs, const base::DictionaryValue* custom_local_state) { base::FilePath default_dir = user_data_dir.AppendASCII(chrome::kInitialProfile); if (!base::CreateDirectory(default_dir)) return Status(kUnknownError, "cannot create default profile directory"); std::string preferences; base::FilePath preferences_path = default_dir.Append(chrome::kPreferencesFilename); if (base::PathExists(preferences_path)) base::ReadFileToString(preferences_path, &preferences); else preferences = kPreferences; Status status = WritePrefsFile(preferences, custom_prefs, default_dir.Append(chrome::kPreferencesFilename)); if (status.IsError()) return status; std::string local_state; base::FilePath local_state_path = user_data_dir.Append(chrome::kLocalStateFilename); if (base::PathExists(local_state_path)) base::ReadFileToString(local_state_path, &local_state); else local_state = kLocalState; status = WritePrefsFile(local_state, custom_local_state, user_data_dir.Append(chrome::kLocalStateFilename)); if (status.IsError()) return status; // Write empty "First Run" file, otherwise Chrome will wipe the default // profile that was written. if (base::WriteFile( user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) { return Status(kUnknownError, "failed to write first run file"); } return Status(kOk); } } // namespace internal