Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / test / chromedriver / chrome_launcher.cc
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/common/chrome_constants.h"
29 #include "chrome/test/chromedriver/chrome/chrome_android_impl.h"
30 #include "chrome/test/chromedriver/chrome/chrome_desktop_impl.h"
31 #include "chrome/test/chromedriver/chrome/chrome_existing_impl.h"
32 #include "chrome/test/chromedriver/chrome/chrome_finder.h"
33 #include "chrome/test/chromedriver/chrome/device_manager.h"
34 #include "chrome/test/chromedriver/chrome/devtools_http_client.h"
35 #include "chrome/test/chromedriver/chrome/embedded_automation_extension.h"
36 #include "chrome/test/chromedriver/chrome/status.h"
37 #include "chrome/test/chromedriver/chrome/user_data_dir.h"
38 #include "chrome/test/chromedriver/chrome/version.h"
39 #include "chrome/test/chromedriver/chrome/web_view.h"
40 #include "chrome/test/chromedriver/chrome/zip.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
45 #if defined(OS_POSIX)
46 #include <fcntl.h>
47 #include <sys/stat.h>
48 #include <sys/types.h>
49 #endif
50
51 namespace {
52
53 const char* kCommonSwitches[] = {
54     "ignore-certificate-errors", "metrics-recording-only"};
55
56 #if defined(OS_LINUX)
57 const char* kEnableCrashReport = "enable-crash-reporter-for-testing";
58 #endif
59
60 Status UnpackAutomationExtension(const base::FilePath& temp_dir,
61                                  base::FilePath* automation_extension) {
62   std::string decoded_extension;
63   if (!base::Base64Decode(kAutomationExtension, &decoded_extension))
64     return Status(kUnknownError, "failed to base64decode automation extension");
65
66   base::FilePath extension_zip = temp_dir.AppendASCII("internal.zip");
67   int size = static_cast<int>(decoded_extension.length());
68   if (file_util::WriteFile(extension_zip, decoded_extension.c_str(), size)
69       != size) {
70     return Status(kUnknownError, "failed to write automation extension zip");
71   }
72
73   base::FilePath extension_dir = temp_dir.AppendASCII("internal");
74   if (!zip::Unzip(extension_zip, extension_dir))
75     return Status(kUnknownError, "failed to unzip automation extension");
76
77   *automation_extension = extension_dir;
78   return Status(kOk);
79 }
80
81 Status PrepareCommandLine(int port,
82                           const Capabilities& capabilities,
83                           CommandLine* prepared_command,
84                           base::ScopedTempDir* user_data_dir,
85                           base::ScopedTempDir* extension_dir,
86                           std::vector<std::string>* extension_bg_pages) {
87   base::FilePath program = capabilities.binary;
88   if (program.empty()) {
89     if (!FindChrome(&program))
90       return Status(kUnknownError, "cannot find Chrome binary");
91   } else if (!base::PathExists(program)) {
92     return Status(kUnknownError,
93                   base::StringPrintf("no chrome binary at %" PRFilePath,
94                                      program.value().c_str()));
95   }
96   CommandLine command(program);
97   Switches switches;
98
99   for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
100     switches.SetSwitch(kCommonSwitches[i]);
101   switches.SetSwitch("disable-hang-monitor");
102   switches.SetSwitch("disable-prompt-on-repost");
103   switches.SetSwitch("disable-sync");
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
155 Status 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(60);
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
186 Status 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
207 Status 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
338 Status 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
393 Status 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
443 namespace internal {
444
445 void 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
458 std::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
466 Status 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
483 Status 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 (file_util::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 (file_util::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
575 void 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
585 Status 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
629 Status 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()) != file_util::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
661 Status 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 (file_util::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