chrome_launcher.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright (c) 2013 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 "chrome/test/chromedriver/chrome_launcher.h" 6 7#include <algorithm> 8#include <vector> 9 10#include "base/base64.h" 11#include "base/basictypes.h" 12#include "base/command_line.h" 13#include "base/file_util.h" 14#include "base/files/file_path.h" 15#include "base/files/scoped_file.h" 16#include "base/format_macros.h" 17#include "base/json/json_reader.h" 18#include "base/json/json_writer.h" 19#include "base/logging.h" 20#include "base/process/kill.h" 21#include "base/process/launch.h" 22#include "base/strings/string_number_conversions.h" 23#include "base/strings/string_util.h" 24#include "base/strings/stringprintf.h" 25#include "base/strings/utf_string_conversions.h" 26#include "base/threading/platform_thread.h" 27#include "base/time/time.h" 28#include "base/values.h" 29#include "chrome/common/chrome_constants.h" 30#include "chrome/test/chromedriver/chrome/chrome_android_impl.h" 31#include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h" 32#include "chrome/test/chromedriver/chrome/chrome_existing_impl.h" 33#include "chrome/test/chromedriver/chrome/chrome_finder.h" 34#include "chrome/test/chromedriver/chrome/device_manager.h" 35#include "chrome/test/chromedriver/chrome/devtools_http_client.h" 36#include "chrome/test/chromedriver/chrome/embedded_automation_extension.h" 37#include "chrome/test/chromedriver/chrome/status.h" 38#include "chrome/test/chromedriver/chrome/user_data_dir.h" 39#include "chrome/test/chromedriver/chrome/version.h" 40#include "chrome/test/chromedriver/chrome/web_view.h" 41#include "chrome/test/chromedriver/net/port_server.h" 42#include "chrome/test/chromedriver/net/url_request_context_getter.h" 43#include "crypto/sha2.h" 44#include "third_party/zlib/google/zip.h" 45 46#if defined(OS_POSIX) 47#include <fcntl.h> 48#include <sys/stat.h> 49#include <sys/types.h> 50#endif 51 52namespace { 53 54const char* kCommonSwitches[] = { 55 "ignore-certificate-errors", "metrics-recording-only"}; 56 57#if defined(OS_LINUX) 58const char* kEnableCrashReport = "enable-crash-reporter-for-testing"; 59#endif 60 61Status UnpackAutomationExtension(const base::FilePath& temp_dir, 62 base::FilePath* automation_extension) { 63 std::string decoded_extension; 64 if (!base::Base64Decode(kAutomationExtension, &decoded_extension)) 65 return Status(kUnknownError, "failed to base64decode automation extension"); 66 67 base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip"); 68 int size = static_cast<int>(decoded_extension.length()); 69 if (base::WriteFile(extension_zip, decoded_extension.c_str(), size) 70 != size) { 71 return Status(kUnknownError, "failed to write automation extension zip"); 72 } 73 74 base::FilePath extension_dir = temp_dir.AppendASCII("internal"); 75 if (!zip::Unzip(extension_zip, extension_dir)) 76 return Status(kUnknownError, "failed to unzip automation extension"); 77 78 *automation_extension = extension_dir; 79 return Status(kOk); 80} 81 82Status PrepareCommandLine(int port, 83 const Capabilities& capabilities, 84 CommandLine* prepared_command, 85 base::ScopedTempDir* user_data_dir, 86 base::ScopedTempDir* extension_dir, 87 std::vector<std::string>* extension_bg_pages) { 88 base::FilePath program = capabilities.binary; 89 if (program.empty()) { 90 if (!FindChrome(&program)) 91 return Status(kUnknownError, "cannot find Chrome binary"); 92 } else if (!base::PathExists(program)) { 93 return Status(kUnknownError, 94 base::StringPrintf("no chrome binary at %" PRFilePath, 95 program.value().c_str())); 96 } 97 CommandLine command(program); 98 Switches switches; 99 100 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i) 101 switches.SetSwitch(kCommonSwitches[i]); 102 switches.SetSwitch("disable-hang-monitor"); 103 switches.SetSwitch("disable-prompt-on-repost"); 104 switches.SetSwitch("disable-sync"); 105 switches.SetSwitch("full-memory-crash-report"); 106 switches.SetSwitch("no-first-run"); 107 switches.SetSwitch("disable-background-networking"); 108 switches.SetSwitch("disable-web-resources"); 109 switches.SetSwitch("safebrowsing-disable-auto-update"); 110 switches.SetSwitch("safebrowsing-disable-download-protection"); 111 switches.SetSwitch("disable-client-side-phishing-detection"); 112 switches.SetSwitch("disable-component-update"); 113 switches.SetSwitch("disable-default-apps"); 114 switches.SetSwitch("enable-logging"); 115 switches.SetSwitch("logging-level", "1"); 116 switches.SetSwitch("password-store", "basic"); 117 switches.SetSwitch("use-mock-keychain"); 118 switches.SetSwitch("remote-debugging-port", base::IntToString(port)); 119 120 for (std::set<std::string>::const_iterator iter = 121 capabilities.exclude_switches.begin(); 122 iter != capabilities.exclude_switches.end(); 123 ++iter) { 124 switches.RemoveSwitch(*iter); 125 } 126 switches.SetFromSwitches(capabilities.switches); 127 128 if (!switches.HasSwitch("user-data-dir")) { 129 command.AppendArg("data:,"); 130 if (!user_data_dir->CreateUniqueTempDir()) 131 return Status(kUnknownError, "cannot create temp dir for user data dir"); 132 switches.SetSwitch("user-data-dir", user_data_dir->path().value()); 133 Status status = internal::PrepareUserDataDir( 134 user_data_dir->path(), capabilities.prefs.get(), 135 capabilities.local_state.get()); 136 if (status.IsError()) 137 return status; 138 } 139 140 if (!extension_dir->CreateUniqueTempDir()) { 141 return Status(kUnknownError, 142 "cannot create temp dir for unpacking extensions"); 143 } 144 Status status = internal::ProcessExtensions(capabilities.extensions, 145 extension_dir->path(), 146 true, 147 &switches, 148 extension_bg_pages); 149 if (status.IsError()) 150 return status; 151 switches.AppendToCommandLine(&command); 152 *prepared_command = command; 153 return Status(kOk); 154} 155 156Status WaitForDevToolsAndCheckVersion( 157 const NetAddress& address, 158 URLRequestContextGetter* context_getter, 159 const SyncWebSocketFactory& socket_factory, 160 scoped_ptr<DevToolsHttpClient>* user_client) { 161 scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient( 162 address, context_getter, socket_factory)); 163 base::TimeTicks deadline = 164 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(60); 165 Status status = client->Init(deadline - base::TimeTicks::Now()); 166 if (status.IsError()) 167 return status; 168 if (client->build_no() < kMinimumSupportedChromeBuildNo) { 169 return Status(kUnknownError, "Chrome version must be >= " + 170 GetMinimumSupportedChromeVersion()); 171 } 172 173 while (base::TimeTicks::Now() < deadline) { 174 WebViewsInfo views_info; 175 client->GetWebViewsInfo(&views_info); 176 for (size_t i = 0; i < views_info.GetSize(); ++i) { 177 if (views_info.Get(i).type == WebViewInfo::kPage) { 178 *user_client = client.Pass(); 179 return Status(kOk); 180 } 181 } 182 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); 183 } 184 return Status(kUnknownError, "unable to discover open pages"); 185} 186 187Status LaunchExistingChromeSession( 188 URLRequestContextGetter* context_getter, 189 const SyncWebSocketFactory& socket_factory, 190 const Capabilities& capabilities, 191 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 192 scoped_ptr<Chrome>* chrome) { 193 Status status(kOk); 194 scoped_ptr<DevToolsHttpClient> devtools_client; 195 status = WaitForDevToolsAndCheckVersion( 196 capabilities.debugger_address, context_getter, socket_factory, 197 &devtools_client); 198 if (status.IsError()) { 199 return Status(kUnknownError, "cannot connect to chrome at " + 200 capabilities.debugger_address.ToString(), 201 status); 202 } 203 chrome->reset(new ChromeExistingImpl(devtools_client.Pass(), 204 devtools_event_listeners)); 205 return Status(kOk); 206} 207 208Status LaunchDesktopChrome( 209 URLRequestContextGetter* context_getter, 210 int port, 211 scoped_ptr<PortReservation> port_reservation, 212 const SyncWebSocketFactory& socket_factory, 213 const Capabilities& capabilities, 214 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 215 scoped_ptr<Chrome>* chrome) { 216 CommandLine command(CommandLine::NO_PROGRAM); 217 base::ScopedTempDir user_data_dir; 218 base::ScopedTempDir extension_dir; 219 std::vector<std::string> extension_bg_pages; 220 Status status = PrepareCommandLine(port, 221 capabilities, 222 &command, 223 &user_data_dir, 224 &extension_dir, 225 &extension_bg_pages); 226 if (status.IsError()) 227 return status; 228 229 base::LaunchOptions options; 230 231#if defined(OS_LINUX) 232 // If minidump path is set in the capability, enable minidump for crashes. 233 if (!capabilities.minidump_path.empty()) { 234 VLOG(0) << "Minidump generation specified. Will save dumps to: " 235 << capabilities.minidump_path; 236 237 options.environ["CHROME_HEADLESS"] = 1; 238 options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path; 239 240 if (!command.HasSwitch(kEnableCrashReport)) 241 command.AppendSwitch(kEnableCrashReport); 242 } 243#endif 244 245#if !defined(OS_WIN) 246 if (!capabilities.log_path.empty()) 247 options.environ["CHROME_LOG_FILE"] = capabilities.log_path; 248 if (capabilities.detach) 249 options.new_process_group = true; 250#endif 251 252#if defined(OS_POSIX) 253 base::FileHandleMappingVector no_stderr; 254 base::ScopedFD devnull; 255 if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) { 256 // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse 257 // users. 258 devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY))); 259 if (!devnull.is_valid()) 260 return Status(kUnknownError, "couldn't open /dev/null"); 261 no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO)); 262 options.fds_to_remap = &no_stderr; 263 } 264#endif 265 266#if defined(OS_WIN) 267 std::string command_string = base::WideToUTF8(command.GetCommandLineString()); 268#else 269 std::string command_string = command.GetCommandLineString(); 270#endif 271 VLOG(0) << "Launching chrome: " << command_string; 272 base::ProcessHandle process; 273 if (!base::LaunchProcess(command, options, &process)) 274 return Status(kUnknownError, "chrome failed to start"); 275 276 scoped_ptr<DevToolsHttpClient> devtools_client; 277 status = WaitForDevToolsAndCheckVersion( 278 NetAddress(port), context_getter, socket_factory, &devtools_client); 279 280 if (status.IsError()) { 281 int exit_code; 282 base::TerminationStatus chrome_status = 283 base::GetTerminationStatus(process, &exit_code); 284 if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) { 285 std::string termination_reason; 286 switch (chrome_status) { 287 case base::TERMINATION_STATUS_NORMAL_TERMINATION: 288 termination_reason = "exited normally"; 289 break; 290 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: 291 termination_reason = "exited abnormally"; 292 break; 293 case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: 294 termination_reason = "was killed"; 295 break; 296 case base::TERMINATION_STATUS_PROCESS_CRASHED: 297 termination_reason = "crashed"; 298 break; 299 default: 300 termination_reason = "unknown"; 301 break; 302 } 303 return Status(kUnknownError, 304 "Chrome failed to start: " + termination_reason); 305 } 306 if (!base::KillProcess(process, 0, true)) { 307 int exit_code; 308 if (base::GetTerminationStatus(process, &exit_code) == 309 base::TERMINATION_STATUS_STILL_RUNNING) 310 return Status(kUnknownError, "cannot kill Chrome", status); 311 } 312 return status; 313 } 314 scoped_ptr<ChromeDesktopImpl> chrome_desktop( 315 new ChromeDesktopImpl(devtools_client.Pass(), 316 devtools_event_listeners, 317 port_reservation.Pass(), 318 process, 319 command, 320 &user_data_dir, 321 &extension_dir)); 322 for (size_t i = 0; i < extension_bg_pages.size(); ++i) { 323 VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages[i]; 324 scoped_ptr<WebView> web_view; 325 Status status = chrome_desktop->WaitForPageToLoad( 326 extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view); 327 if (status.IsError()) { 328 return Status(kUnknownError, 329 "failed to wait for extension background page to load: " + 330 extension_bg_pages[i], 331 status); 332 } 333 } 334 *chrome = chrome_desktop.Pass(); 335 return Status(kOk); 336} 337 338Status LaunchAndroidChrome( 339 URLRequestContextGetter* context_getter, 340 int port, 341 scoped_ptr<PortReservation> port_reservation, 342 const SyncWebSocketFactory& socket_factory, 343 const Capabilities& capabilities, 344 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 345 DeviceManager* device_manager, 346 scoped_ptr<Chrome>* chrome) { 347 Status status(kOk); 348 scoped_ptr<Device> device; 349 if (capabilities.android_device_serial.empty()) { 350 status = device_manager->AcquireDevice(&device); 351 } else { 352 status = device_manager->AcquireSpecificDevice( 353 capabilities.android_device_serial, &device); 354 } 355 if (status.IsError()) 356 return status; 357 358 Switches switches(capabilities.switches); 359 for (size_t i = 0; i < arraysize(kCommonSwitches); ++i) 360 switches.SetSwitch(kCommonSwitches[i]); 361 switches.SetSwitch("disable-fre"); 362 switches.SetSwitch("enable-remote-debugging"); 363 status = device->SetUp(capabilities.android_package, 364 capabilities.android_activity, 365 capabilities.android_process, 366 switches.ToString(), 367 capabilities.android_use_running_app, 368 port); 369 if (status.IsError()) { 370 device->TearDown(); 371 return status; 372 } 373 374 scoped_ptr<DevToolsHttpClient> devtools_client; 375 status = WaitForDevToolsAndCheckVersion(NetAddress(port), 376 context_getter, 377 socket_factory, 378 &devtools_client); 379 if (status.IsError()) { 380 device->TearDown(); 381 return status; 382 } 383 384 chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(), 385 devtools_event_listeners, 386 port_reservation.Pass(), 387 device.Pass())); 388 return Status(kOk); 389} 390 391} // namespace 392 393Status LaunchChrome( 394 URLRequestContextGetter* context_getter, 395 const SyncWebSocketFactory& socket_factory, 396 DeviceManager* device_manager, 397 PortServer* port_server, 398 PortManager* port_manager, 399 const Capabilities& capabilities, 400 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 401 scoped_ptr<Chrome>* chrome) { 402 if (capabilities.IsExistingBrowser()) { 403 return LaunchExistingChromeSession( 404 context_getter, socket_factory, 405 capabilities, devtools_event_listeners, chrome); 406 } 407 408 int port = 0; 409 scoped_ptr<PortReservation> port_reservation; 410 Status port_status(kOk); 411 412 if (capabilities.IsAndroid()) { 413 port_status = port_manager->ReservePortFromPool(&port, &port_reservation); 414 if (port_status.IsError()) 415 return Status(kUnknownError, "cannot reserve port for Chrome", 416 port_status); 417 return LaunchAndroidChrome(context_getter, 418 port, 419 port_reservation.Pass(), 420 socket_factory, 421 capabilities, 422 devtools_event_listeners, 423 device_manager, 424 chrome); 425 } else { 426 if (port_server) 427 port_status = port_server->ReservePort(&port, &port_reservation); 428 else 429 port_status = port_manager->ReservePort(&port, &port_reservation); 430 if (port_status.IsError()) 431 return Status(kUnknownError, "cannot reserve port for Chrome", 432 port_status); 433 return LaunchDesktopChrome(context_getter, 434 port, 435 port_reservation.Pass(), 436 socket_factory, 437 capabilities, 438 devtools_event_listeners, 439 chrome); 440 } 441} 442 443namespace internal { 444 445void ConvertHexadecimalToIDAlphabet(std::string* id) { 446 for (size_t i = 0; i < id->size(); ++i) { 447 int val; 448 if (base::HexStringToInt(base::StringPiece(id->begin() + i, 449 id->begin() + i + 1), 450 &val)) { 451 (*id)[i] = val + 'a'; 452 } else { 453 (*id)[i] = 'a'; 454 } 455 } 456} 457 458std::string GenerateExtensionId(const std::string& input) { 459 uint8 hash[16]; 460 crypto::SHA256HashString(input, hash, sizeof(hash)); 461 std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash))); 462 ConvertHexadecimalToIDAlphabet(&output); 463 return output; 464} 465 466Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest, 467 const std::string& id, 468 std::string* bg_page) { 469 std::string bg_page_name; 470 bool persistent = true; 471 manifest->GetBoolean("background.persistent", &persistent); 472 const base::Value* unused_value; 473 if (manifest->Get("background.scripts", &unused_value)) 474 bg_page_name = "_generated_background_page.html"; 475 manifest->GetString("background.page", &bg_page_name); 476 manifest->GetString("background_page", &bg_page_name); 477 if (bg_page_name.empty() || !persistent) 478 return Status(kOk); 479 *bg_page = "chrome-extension://" + id + "/" + bg_page_name; 480 return Status(kOk); 481} 482 483Status ProcessExtension(const std::string& extension, 484 const base::FilePath& temp_dir, 485 base::FilePath* path, 486 std::string* bg_page) { 487 // Decodes extension string. 488 // Some WebDriver client base64 encoders follow RFC 1521, which require that 489 // 'encoded lines be no more than 76 characters long'. Just remove any 490 // newlines. 491 std::string extension_base64; 492 base::RemoveChars(extension, "\n", &extension_base64); 493 std::string decoded_extension; 494 if (!base::Base64Decode(extension_base64, &decoded_extension)) 495 return Status(kUnknownError, "cannot base64 decode"); 496 497 // Get extension's ID from public key in crx file. 498 // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html. 499 std::string key_len_str = decoded_extension.substr(8, 4); 500 if (key_len_str.size() != 4) 501 return Status(kUnknownError, "cannot extract public key length"); 502 uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str()); 503 std::string public_key = decoded_extension.substr(16, key_len); 504 if (key_len != public_key.size()) 505 return Status(kUnknownError, "invalid public key length"); 506 std::string public_key_base64; 507 base::Base64Encode(public_key, &public_key_base64); 508 std::string id = GenerateExtensionId(public_key); 509 510 // Unzip the crx file. 511 base::ScopedTempDir temp_crx_dir; 512 if (!temp_crx_dir.CreateUniqueTempDir()) 513 return Status(kUnknownError, "cannot create temp dir"); 514 base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx"); 515 int size = static_cast<int>(decoded_extension.length()); 516 if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) != 517 size) { 518 return Status(kUnknownError, "cannot write file"); 519 } 520 base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id); 521 if (!zip::Unzip(extension_crx, extension_dir)) 522 return Status(kUnknownError, "cannot unzip"); 523 524 // Parse the manifest and set the 'key' if not already present. 525 base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json")); 526 std::string manifest_data; 527 if (!base::ReadFileToString(manifest_path, &manifest_data)) 528 return Status(kUnknownError, "cannot read manifest"); 529 scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data)); 530 base::DictionaryValue* manifest; 531 if (!manifest_value || !manifest_value->GetAsDictionary(&manifest)) 532 return Status(kUnknownError, "invalid manifest"); 533 534 std::string manifest_key_base64; 535 if (manifest->GetString("key", &manifest_key_base64)) { 536 // If there is a key in both the header and the manifest, use the key in the 537 // manifest. This allows chromedriver users users who generate dummy crxs 538 // to set the manifest key and have a consistent ID. 539 std::string manifest_key; 540 if (!base::Base64Decode(manifest_key_base64, &manifest_key)) 541 return Status(kUnknownError, "'key' in manifest is not base64 encoded"); 542 std::string manifest_id = GenerateExtensionId(manifest_key); 543 if (id != manifest_id) { 544 LOG(WARNING) 545 << "Public key in crx header is different from key in manifest" 546 << std::endl << "key from header: " << public_key_base64 547 << std::endl << "key from manifest: " << manifest_key_base64 548 << std::endl << "generated extension id from header key: " << id 549 << std::endl << "generated extension id from manifest key: " 550 << manifest_id; 551 id = manifest_id; 552 } 553 } else { 554 manifest->SetString("key", public_key_base64); 555 base::JSONWriter::Write(manifest, &manifest_data); 556 if (base::WriteFile( 557 manifest_path, manifest_data.c_str(), manifest_data.size()) != 558 static_cast<int>(manifest_data.size())) { 559 return Status(kUnknownError, "cannot add 'key' to manifest"); 560 } 561 } 562 563 // Get extension's background page URL, if there is one. 564 std::string bg_page_tmp; 565 Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp); 566 if (status.IsError()) 567 return status; 568 569 *path = extension_dir; 570 if (bg_page_tmp.size()) 571 *bg_page = bg_page_tmp; 572 return Status(kOk); 573} 574 575void UpdateExtensionSwitch(Switches* switches, 576 const char name[], 577 const base::FilePath::StringType& extension) { 578 base::FilePath::StringType value = switches->GetSwitchValueNative(name); 579 if (value.length()) 580 value += FILE_PATH_LITERAL(","); 581 value += extension; 582 switches->SetSwitch(name, value); 583} 584 585Status ProcessExtensions(const std::vector<std::string>& extensions, 586 const base::FilePath& temp_dir, 587 bool include_automation_extension, 588 Switches* switches, 589 std::vector<std::string>* bg_pages) { 590 std::vector<std::string> bg_pages_tmp; 591 std::vector<base::FilePath::StringType> extension_paths; 592 for (size_t i = 0; i < extensions.size(); ++i) { 593 base::FilePath path; 594 std::string bg_page; 595 Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page); 596 if (status.IsError()) { 597 return Status( 598 kUnknownError, 599 base::StringPrintf("cannot process extension #%" PRIuS, i + 1), 600 status); 601 } 602 extension_paths.push_back(path.value()); 603 if (bg_page.length()) 604 bg_pages_tmp.push_back(bg_page); 605 } 606 607 if (include_automation_extension) { 608 base::FilePath automation_extension; 609 Status status = UnpackAutomationExtension(temp_dir, &automation_extension); 610 if (status.IsError()) 611 return status; 612 if (switches->HasSwitch("disable-extensions")) { 613 UpdateExtensionSwitch(switches, "load-component-extension", 614 automation_extension.value()); 615 } else { 616 extension_paths.push_back(automation_extension.value()); 617 } 618 } 619 620 if (extension_paths.size()) { 621 base::FilePath::StringType extension_paths_value = JoinString( 622 extension_paths, FILE_PATH_LITERAL(',')); 623 UpdateExtensionSwitch(switches, "load-extension", extension_paths_value); 624 } 625 bg_pages->swap(bg_pages_tmp); 626 return Status(kOk); 627} 628 629Status WritePrefsFile( 630 const std::string& template_string, 631 const base::DictionaryValue* custom_prefs, 632 const base::FilePath& path) { 633 int code; 634 std::string error_msg; 635 scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError( 636 template_string, 0, &code, &error_msg)); 637 base::DictionaryValue* prefs; 638 if (!template_value || !template_value->GetAsDictionary(&prefs)) { 639 return Status(kUnknownError, 640 "cannot parse internal JSON template: " + error_msg); 641 } 642 643 if (custom_prefs) { 644 for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd(); 645 it.Advance()) { 646 prefs->Set(it.key(), it.value().DeepCopy()); 647 } 648 } 649 650 std::string prefs_str; 651 base::JSONWriter::Write(prefs, &prefs_str); 652 VLOG(0) << "Populating " << path.BaseName().value() 653 << " file: " << PrettyPrintValue(*prefs); 654 if (static_cast<int>(prefs_str.length()) != base::WriteFile( 655 path, prefs_str.c_str(), prefs_str.length())) { 656 return Status(kUnknownError, "failed to write prefs file"); 657 } 658 return Status(kOk); 659} 660 661Status PrepareUserDataDir( 662 const base::FilePath& user_data_dir, 663 const base::DictionaryValue* custom_prefs, 664 const base::DictionaryValue* custom_local_state) { 665 base::FilePath default_dir = 666 user_data_dir.AppendASCII(chrome::kInitialProfile); 667 if (!base::CreateDirectory(default_dir)) 668 return Status(kUnknownError, "cannot create default profile directory"); 669 670 Status status = 671 WritePrefsFile(kPreferences, 672 custom_prefs, 673 default_dir.Append(chrome::kPreferencesFilename)); 674 if (status.IsError()) 675 return status; 676 677 status = WritePrefsFile(kLocalState, 678 custom_local_state, 679 user_data_dir.Append(chrome::kLocalStateFilename)); 680 if (status.IsError()) 681 return status; 682 683 // Write empty "First Run" file, otherwise Chrome will wipe the default 684 // profile that was written. 685 if (base::WriteFile( 686 user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) { 687 return Status(kUnknownError, "failed to write first run file"); 688 } 689 return Status(kOk); 690} 691 692} // namespace internal 693