chrome_launcher.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/format_macros.h"
16#include "base/json/json_reader.h"
17#include "base/json/json_writer.h"
18#include "base/logging.h"
19#include "base/process/kill.h"
20#include "base/process/launch.h"
21#include "base/strings/string_number_conversions.h"
22#include "base/strings/string_util.h"
23#include "base/strings/stringprintf.h"
24#include "base/strings/utf_string_conversions.h"
25#include "base/threading/platform_thread.h"
26#include "base/time/time.h"
27#include "base/values.h"
28#include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
29#include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
30#include "chrome/test/chromedriver/chrome/chrome_existing_impl.h"
31#include "chrome/test/chromedriver/chrome/chrome_finder.h"
32#include "chrome/test/chromedriver/chrome/device_manager.h"
33#include "chrome/test/chromedriver/chrome/devtools_http_client.h"
34#include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
35#include "chrome/test/chromedriver/chrome/status.h"
36#include "chrome/test/chromedriver/chrome/user_data_dir.h"
37#include "chrome/test/chromedriver/chrome/version.h"
38#include "chrome/test/chromedriver/chrome/web_view.h"
39#include "chrome/test/chromedriver/chrome/zip.h"
40#include "chrome/test/chromedriver/net/port_server.h"
41#include "chrome/test/chromedriver/net/url_request_context_getter.h"
42#include "crypto/sha2.h"
43
44#if defined(OS_POSIX)
45#include <fcntl.h>
46#include <sys/stat.h>
47#include <sys/types.h>
48#endif
49
50namespace {
51
52const char* kCommonSwitches[] = {
53    "ignore-certificate-errors", "metrics-recording-only"};
54
55#if defined(OS_LINUX)
56const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
57#endif
58
59Status UnpackAutomationExtension(const base::FilePath& temp_dir,
60                                 base::FilePath* automation_extension) {
61  std::string decoded_extension;
62  if (!base::Base64Decode(kAutomationExtension, &decoded_extension))
63    return Status(kUnknownError, "failed to base64decode automation extension");
64
65  base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
66  int size = static_cast<int>(decoded_extension.length());
67  if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
68      != size) {
69    return Status(kUnknownError, "failed to write automation extension zip");
70  }
71
72  base::FilePath extension_dir = temp_dir.AppendASCII("internal");
73  if (!zip::Unzip(extension_zip, extension_dir))
74    return Status(kUnknownError, "failed to unzip automation extension");
75
76  *automation_extension = extension_dir;
77  return Status(kOk);
78}
79
80Status PrepareCommandLine(int port,
81                          const Capabilities& capabilities,
82                          CommandLine* prepared_command,
83                          base::ScopedTempDir* user_data_dir,
84                          base::ScopedTempDir* extension_dir,
85                          std::vector<std::string>* extension_bg_pages) {
86  base::FilePath program = capabilities.binary;
87  if (program.empty()) {
88    if (!FindChrome(&program))
89      return Status(kUnknownError, "cannot find Chrome binary");
90  } else if (!base::PathExists(program)) {
91    return Status(kUnknownError,
92                  base::StringPrintf("no chrome binary at %" PRFilePath,
93                                     program.value().c_str()));
94  }
95  CommandLine command(program);
96  Switches switches;
97
98  // TODO(chrisgao): Add "disable-sync" when chrome 30- is not supported.
99  // For chrome 30-, it leads to crash when opening chrome://settings.
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("full-memory-crash-report");
105  switches.SetSwitch("no-first-run");
106  switches.SetSwitch("disable-background-networking");
107  switches.SetSwitch("disable-web-resources");
108  switches.SetSwitch("safebrowsing-disable-auto-update");
109  switches.SetSwitch("safebrowsing-disable-download-protection");
110  switches.SetSwitch("disable-client-side-phishing-detection");
111  switches.SetSwitch("disable-component-update");
112  switches.SetSwitch("disable-default-apps");
113  switches.SetSwitch("enable-logging");
114  switches.SetSwitch("logging-level", "1");
115  switches.SetSwitch("password-store", "basic");
116  switches.SetSwitch("use-mock-keychain");
117  switches.SetSwitch("remote-debugging-port", base::IntToString(port));
118
119  for (std::set<std::string>::const_iterator iter =
120           capabilities.exclude_switches.begin();
121       iter != capabilities.exclude_switches.end();
122       ++iter) {
123    switches.RemoveSwitch(*iter);
124  }
125  switches.SetFromSwitches(capabilities.switches);
126
127  if (!switches.HasSwitch("user-data-dir")) {
128    command.AppendArg("data:,");
129    if (!user_data_dir->CreateUniqueTempDir())
130      return Status(kUnknownError, "cannot create temp dir for user data dir");
131    switches.SetSwitch("user-data-dir", user_data_dir->path().value());
132    Status status = internal::PrepareUserDataDir(
133        user_data_dir->path(), capabilities.prefs.get(),
134        capabilities.local_state.get());
135    if (status.IsError())
136      return status;
137  }
138
139  if (!extension_dir->CreateUniqueTempDir()) {
140    return Status(kUnknownError,
141                  "cannot create temp dir for unpacking extensions");
142  }
143  Status status = internal::ProcessExtensions(capabilities.extensions,
144                                              extension_dir->path(),
145                                              true,
146                                              &switches,
147                                              extension_bg_pages);
148  if (status.IsError())
149    return status;
150  switches.AppendToCommandLine(&command);
151  *prepared_command = command;
152  return Status(kOk);
153}
154
155Status WaitForDevToolsAndCheckVersion(
156    const NetAddress& address,
157    URLRequestContextGetter* context_getter,
158    const SyncWebSocketFactory& socket_factory,
159    scoped_ptr<DevToolsHttpClient>* user_client) {
160  scoped_ptr<DevToolsHttpClient> client(new DevToolsHttpClient(
161      address, context_getter, socket_factory));
162  base::TimeTicks deadline =
163      base::TimeTicks::Now() + base::TimeDelta::FromSeconds(20);
164  Status status = client->Init(deadline - base::TimeTicks::Now());
165  if (status.IsError())
166    return status;
167  if (client->build_no() < kMinimumSupportedChromeBuildNo) {
168    return Status(kUnknownError, "Chrome version must be >= " +
169        GetMinimumSupportedChromeVersion());
170  }
171
172  while (base::TimeTicks::Now() < deadline) {
173    WebViewsInfo views_info;
174    client->GetWebViewsInfo(&views_info);
175    for (size_t i = 0; i < views_info.GetSize(); ++i) {
176      if (views_info.Get(i).type == WebViewInfo::kPage) {
177        *user_client = client.Pass();
178        return Status(kOk);
179      }
180    }
181    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50));
182  }
183  return Status(kUnknownError, "unable to discover open pages");
184}
185
186Status LaunchExistingChromeSession(
187    URLRequestContextGetter* context_getter,
188    const SyncWebSocketFactory& socket_factory,
189    const Capabilities& capabilities,
190    ScopedVector<DevToolsEventListener>& devtools_event_listeners,
191    scoped_ptr<Chrome>* chrome) {
192  Status status(kOk);
193  scoped_ptr<DevToolsHttpClient> devtools_client;
194  status = WaitForDevToolsAndCheckVersion(
195      capabilities.debugger_address, context_getter, socket_factory,
196      &devtools_client);
197  if (status.IsError()) {
198    return Status(kUnknownError, "cannot connect to chrome at " +
199                      capabilities.debugger_address.ToString(),
200                  status);
201  }
202  chrome->reset(new ChromeExistingImpl(devtools_client.Pass(),
203                                       devtools_event_listeners));
204  return Status(kOk);
205}
206
207Status LaunchDesktopChrome(
208    URLRequestContextGetter* context_getter,
209    int port,
210    scoped_ptr<PortReservation> port_reservation,
211    const SyncWebSocketFactory& socket_factory,
212    const Capabilities& capabilities,
213    ScopedVector<DevToolsEventListener>& devtools_event_listeners,
214    scoped_ptr<Chrome>* chrome) {
215  CommandLine command(CommandLine::NO_PROGRAM);
216  base::ScopedTempDir user_data_dir;
217  base::ScopedTempDir extension_dir;
218  std::vector<std::string> extension_bg_pages;
219  Status status = PrepareCommandLine(port,
220                                     capabilities,
221                                     &command,
222                                     &user_data_dir,
223                                     &extension_dir,
224                                     &extension_bg_pages);
225  if (status.IsError())
226    return status;
227
228  base::LaunchOptions options;
229
230#if defined(OS_LINUX)
231  // If minidump path is set in the capability, enable minidump for crashes.
232  if (!capabilities.minidump_path.empty()) {
233    VLOG(0) << "Minidump generation specified. Will save dumps to: "
234            << capabilities.minidump_path;
235
236    options.environ["CHROME_HEADLESS"] = 1;
237    options.environ["BREAKPAD_DUMP_LOCATION"] = capabilities.minidump_path;
238
239    if (!command.HasSwitch(kEnableCrashReport))
240      command.AppendSwitch(kEnableCrashReport);
241  }
242#endif
243
244#if !defined(OS_WIN)
245  if (!capabilities.log_path.empty())
246    options.environ["CHROME_LOG_FILE"] = capabilities.log_path;
247  if (capabilities.detach)
248    options.new_process_group = true;
249#endif
250
251#if defined(OS_POSIX)
252  base::FileHandleMappingVector no_stderr;
253  int devnull = -1;
254  file_util::ScopedFD scoped_devnull(&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 = open("/dev/null", O_WRONLY);
259    if (devnull == -1)
260      return Status(kUnknownError, "couldn't open /dev/null");
261    no_stderr.push_back(std::make_pair(devnull, 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.IsOk())
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->StartApp(capabilities.android_package,
364                            capabilities.android_activity,
365                            capabilities.android_process,
366                            switches.ToString(), port);
367  if (!status.IsOk()) {
368    device->StopApp();
369    return status;
370  }
371
372  scoped_ptr<DevToolsHttpClient> devtools_client;
373  status = WaitForDevToolsAndCheckVersion(NetAddress(port),
374                                          context_getter,
375                                          socket_factory,
376                                          &devtools_client);
377  if (status.IsError())
378    return status;
379
380  chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
381                                      devtools_event_listeners,
382                                      port_reservation.Pass(),
383                                      device.Pass()));
384  return Status(kOk);
385}
386
387}  // namespace
388
389Status LaunchChrome(
390    URLRequestContextGetter* context_getter,
391    const SyncWebSocketFactory& socket_factory,
392    DeviceManager* device_manager,
393    PortServer* port_server,
394    PortManager* port_manager,
395    const Capabilities& capabilities,
396    ScopedVector<DevToolsEventListener>& devtools_event_listeners,
397    scoped_ptr<Chrome>* chrome) {
398  if (capabilities.IsExistingBrowser()) {
399    return LaunchExistingChromeSession(
400        context_getter, socket_factory,
401        capabilities, devtools_event_listeners, chrome);
402  }
403
404  int port = 0;
405  scoped_ptr<PortReservation> port_reservation;
406  Status port_status(kOk);
407  if (port_server)
408    port_status = port_server->ReservePort(&port, &port_reservation);
409  else
410    port_status = port_manager->ReservePort(&port, &port_reservation);
411  if (port_status.IsError())
412    return Status(kUnknownError, "cannot reserve port for Chrome", port_status);
413
414  if (capabilities.IsAndroid()) {
415    return LaunchAndroidChrome(context_getter,
416                               port,
417                               port_reservation.Pass(),
418                               socket_factory,
419                               capabilities,
420                               devtools_event_listeners,
421                               device_manager,
422                               chrome);
423  } else {
424    return LaunchDesktopChrome(context_getter,
425                               port,
426                               port_reservation.Pass(),
427                               socket_factory,
428                               capabilities,
429                               devtools_event_listeners,
430                               chrome);
431  }
432}
433
434namespace internal {
435
436void ConvertHexadecimalToIDAlphabet(std::string* id) {
437  for (size_t i = 0; i < id->size(); ++i) {
438    int val;
439    if (base::HexStringToInt(base::StringPiece(id->begin() + i,
440                                               id->begin() + i + 1),
441                             &val)) {
442      (*id)[i] = val + 'a';
443    } else {
444      (*id)[i] = 'a';
445    }
446  }
447}
448
449std::string GenerateExtensionId(const std::string& input) {
450  uint8 hash[16];
451  crypto::SHA256HashString(input, hash, sizeof(hash));
452  std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
453  ConvertHexadecimalToIDAlphabet(&output);
454  return output;
455}
456
457Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest,
458                                  const std::string& id,
459                                  std::string* bg_page) {
460  std::string bg_page_name;
461  bool persistent = true;
462  manifest->GetBoolean("background.persistent", &persistent);
463  const base::Value* unused_value;
464  if (manifest->Get("background.scripts", &unused_value))
465    bg_page_name = "_generated_background_page.html";
466  manifest->GetString("background.page", &bg_page_name);
467  manifest->GetString("background_page", &bg_page_name);
468  if (bg_page_name.empty() || !persistent)
469    return Status(kOk);
470  *bg_page = "chrome-extension://" + id + "/" + bg_page_name;
471  return Status(kOk);
472}
473
474Status ProcessExtension(const std::string& extension,
475                        const base::FilePath& temp_dir,
476                        base::FilePath* path,
477                        std::string* bg_page) {
478  // Decodes extension string.
479  // Some WebDriver client base64 encoders follow RFC 1521, which require that
480  // 'encoded lines be no more than 76 characters long'. Just remove any
481  // newlines.
482  std::string extension_base64;
483  RemoveChars(extension, "\n", &extension_base64);
484  std::string decoded_extension;
485  if (!base::Base64Decode(extension_base64, &decoded_extension))
486    return Status(kUnknownError, "cannot base64 decode");
487
488  // Get extension's ID from public key in crx file.
489  // Assumes crx v2. See http://developer.chrome.com/extensions/crx.html.
490  std::string key_len_str = decoded_extension.substr(8, 4);
491  if (key_len_str.size() != 4)
492    return Status(kUnknownError, "cannot extract public key length");
493  uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
494  std::string public_key = decoded_extension.substr(16, key_len);
495  if (key_len != public_key.size())
496    return Status(kUnknownError, "invalid public key length");
497  std::string public_key_base64;
498  if (!base::Base64Encode(public_key, &public_key_base64))
499    return Status(kUnknownError, "cannot base64 encode public key");
500  std::string id = GenerateExtensionId(public_key);
501
502  // Unzip the crx file.
503  base::ScopedTempDir temp_crx_dir;
504  if (!temp_crx_dir.CreateUniqueTempDir())
505    return Status(kUnknownError, "cannot create temp dir");
506  base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
507  int size = static_cast<int>(decoded_extension.length());
508  if (file_util::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
509      size) {
510    return Status(kUnknownError, "cannot write file");
511  }
512  base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id);
513  if (!zip::Unzip(extension_crx, extension_dir))
514    return Status(kUnknownError, "cannot unzip");
515
516  // Parse the manifest and set the 'key' if not already present.
517  base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json"));
518  std::string manifest_data;
519  if (!base::ReadFileToString(manifest_path, &manifest_data))
520    return Status(kUnknownError, "cannot read manifest");
521  scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data));
522  base::DictionaryValue* manifest;
523  if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
524    return Status(kUnknownError, "invalid manifest");
525  if (!manifest->HasKey("key")) {
526    manifest->SetString("key", public_key_base64);
527    base::JSONWriter::Write(manifest, &manifest_data);
528    if (file_util::WriteFile(
529            manifest_path, manifest_data.c_str(), manifest_data.size()) !=
530        static_cast<int>(manifest_data.size())) {
531      return Status(kUnknownError, "cannot add 'key' to manifest");
532    }
533  }
534
535  // Get extension's background page URL, if there is one.
536  std::string bg_page_tmp;
537  Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp);
538  if (status.IsError())
539    return status;
540
541  *path = extension_dir;
542  if (bg_page_tmp.size())
543    *bg_page = bg_page_tmp;
544  return Status(kOk);
545}
546
547void UpdateExtensionSwitch(Switches* switches,
548                           const char name[],
549                           const base::FilePath::StringType& extension) {
550  base::FilePath::StringType value = switches->GetSwitchValueNative(name);
551  if (value.length())
552    value += FILE_PATH_LITERAL(",");
553  value += extension;
554  switches->SetSwitch(name, value);
555}
556
557Status ProcessExtensions(const std::vector<std::string>& extensions,
558                         const base::FilePath& temp_dir,
559                         bool include_automation_extension,
560                         Switches* switches,
561                         std::vector<std::string>* bg_pages) {
562  std::vector<std::string> bg_pages_tmp;
563  std::vector<base::FilePath::StringType> extension_paths;
564  for (size_t i = 0; i < extensions.size(); ++i) {
565    base::FilePath path;
566    std::string bg_page;
567    Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page);
568    if (status.IsError()) {
569      return Status(
570          kUnknownError,
571          base::StringPrintf("cannot process extension #%" PRIuS, i + 1),
572          status);
573    }
574    extension_paths.push_back(path.value());
575    if (bg_page.length())
576      bg_pages_tmp.push_back(bg_page);
577  }
578
579  if (include_automation_extension) {
580    base::FilePath automation_extension;
581    Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
582    if (status.IsError())
583      return status;
584    if (switches->HasSwitch("disable-extensions")) {
585      UpdateExtensionSwitch(switches, "load-component-extension",
586                            automation_extension.value());
587    } else {
588      extension_paths.push_back(automation_extension.value());
589    }
590  }
591
592  if (extension_paths.size()) {
593    base::FilePath::StringType extension_paths_value = JoinString(
594        extension_paths, FILE_PATH_LITERAL(','));
595    UpdateExtensionSwitch(switches, "load-extension", extension_paths_value);
596  }
597  bg_pages->swap(bg_pages_tmp);
598  return Status(kOk);
599}
600
601Status WritePrefsFile(
602    const std::string& template_string,
603    const base::DictionaryValue* custom_prefs,
604    const base::FilePath& path) {
605  int code;
606  std::string error_msg;
607  scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
608          template_string, 0, &code, &error_msg));
609  base::DictionaryValue* prefs;
610  if (!template_value || !template_value->GetAsDictionary(&prefs)) {
611    return Status(kUnknownError,
612                  "cannot parse internal JSON template: " + error_msg);
613  }
614
615  if (custom_prefs) {
616    for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
617         it.Advance()) {
618      prefs->Set(it.key(), it.value().DeepCopy());
619    }
620  }
621
622  std::string prefs_str;
623  base::JSONWriter::Write(prefs, &prefs_str);
624  VLOG(0) << "Populating " << path.BaseName().value()
625          << " file: " << PrettyPrintValue(*prefs);
626  if (static_cast<int>(prefs_str.length()) != file_util::WriteFile(
627          path, prefs_str.c_str(), prefs_str.length())) {
628    return Status(kUnknownError, "failed to write prefs file");
629  }
630  return Status(kOk);
631}
632
633Status PrepareUserDataDir(
634    const base::FilePath& user_data_dir,
635    const base::DictionaryValue* custom_prefs,
636    const base::DictionaryValue* custom_local_state) {
637  base::FilePath default_dir = user_data_dir.AppendASCII("Default");
638  if (!file_util::CreateDirectory(default_dir))
639    return Status(kUnknownError, "cannot create default profile directory");
640
641  Status status = WritePrefsFile(
642      kPreferences,
643      custom_prefs,
644      default_dir.AppendASCII("Preferences"));
645  if (status.IsError())
646    return status;
647
648  status = WritePrefsFile(
649      kLocalState,
650      custom_local_state,
651      user_data_dir.AppendASCII("Local State"));
652  if (status.IsError())
653    return status;
654
655  // Write empty "First Run" file, otherwise Chrome will wipe the default
656  // profile that was written.
657  if (file_util::WriteFile(
658          user_data_dir.AppendASCII("First Run"), "", 0) != 0) {
659    return Status(kUnknownError, "failed to write first run file");
660  }
661  return Status(kOk);
662}
663
664}  // namespace internal
665