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