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