Upstream version 7.36.149.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_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/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("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
156 Status 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->browser_info()->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
187 Status 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
208 Status 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
244   // We need to allow new privileges so that chrome's setuid sandbox can run.
245   options.allow_new_privs = true;
246 #endif
247
248 #if !defined(OS_WIN)
249   if (!capabilities.log_path.empty())
250     options.environ["CHROME_LOG_FILE"] = capabilities.log_path;
251   if (capabilities.detach)
252     options.new_process_group = true;
253 #endif
254
255 #if defined(OS_POSIX)
256   base::FileHandleMappingVector no_stderr;
257   base::ScopedFD devnull;
258   if (!CommandLine::ForCurrentProcess()->HasSwitch("verbose")) {
259     // Redirect stderr to /dev/null, so that Chrome log spew doesn't confuse
260     // users.
261     devnull.reset(HANDLE_EINTR(open("/dev/null", O_WRONLY)));
262     if (!devnull.is_valid())
263       return Status(kUnknownError, "couldn't open /dev/null");
264     no_stderr.push_back(std::make_pair(devnull.get(), STDERR_FILENO));
265     options.fds_to_remap = &no_stderr;
266   }
267 #endif
268
269 #if defined(OS_WIN)
270   std::string command_string = base::WideToUTF8(command.GetCommandLineString());
271 #else
272   std::string command_string = command.GetCommandLineString();
273 #endif
274   VLOG(0) << "Launching chrome: " << command_string;
275   base::ProcessHandle process;
276   if (!base::LaunchProcess(command, options, &process))
277     return Status(kUnknownError, "chrome failed to start");
278
279   scoped_ptr<DevToolsHttpClient> devtools_client;
280   status = WaitForDevToolsAndCheckVersion(
281       NetAddress(port), context_getter, socket_factory, &devtools_client);
282
283   if (status.IsError()) {
284     int exit_code;
285     base::TerminationStatus chrome_status =
286         base::GetTerminationStatus(process, &exit_code);
287     if (chrome_status != base::TERMINATION_STATUS_STILL_RUNNING) {
288       std::string termination_reason;
289       switch (chrome_status) {
290         case base::TERMINATION_STATUS_NORMAL_TERMINATION:
291           termination_reason = "exited normally";
292           break;
293         case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
294           termination_reason = "exited abnormally";
295           break;
296         case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
297           termination_reason = "was killed";
298           break;
299         case base::TERMINATION_STATUS_PROCESS_CRASHED:
300           termination_reason = "crashed";
301           break;
302         default:
303           termination_reason = "unknown";
304           break;
305       }
306       return Status(kUnknownError,
307                     "Chrome failed to start: " + termination_reason);
308     }
309     if (!base::KillProcess(process, 0, true)) {
310       int exit_code;
311       if (base::GetTerminationStatus(process, &exit_code) ==
312           base::TERMINATION_STATUS_STILL_RUNNING)
313         return Status(kUnknownError, "cannot kill Chrome", status);
314     }
315     return status;
316   }
317   scoped_ptr<ChromeDesktopImpl> chrome_desktop(
318       new ChromeDesktopImpl(devtools_client.Pass(),
319                             devtools_event_listeners,
320                             port_reservation.Pass(),
321                             process,
322                             command,
323                             &user_data_dir,
324                             &extension_dir));
325   for (size_t i = 0; i < extension_bg_pages.size(); ++i) {
326     VLOG(0) << "Waiting for extension bg page load: " << extension_bg_pages[i];
327     scoped_ptr<WebView> web_view;
328     Status status = chrome_desktop->WaitForPageToLoad(
329         extension_bg_pages[i], base::TimeDelta::FromSeconds(10), &web_view);
330     if (status.IsError()) {
331       return Status(kUnknownError,
332                     "failed to wait for extension background page to load: " +
333                         extension_bg_pages[i],
334                     status);
335     }
336   }
337   *chrome = chrome_desktop.Pass();
338   return Status(kOk);
339 }
340
341 Status LaunchAndroidChrome(
342     URLRequestContextGetter* context_getter,
343     int port,
344     scoped_ptr<PortReservation> port_reservation,
345     const SyncWebSocketFactory& socket_factory,
346     const Capabilities& capabilities,
347     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
348     DeviceManager* device_manager,
349     scoped_ptr<Chrome>* chrome) {
350   Status status(kOk);
351   scoped_ptr<Device> device;
352   if (capabilities.android_device_serial.empty()) {
353     status = device_manager->AcquireDevice(&device);
354   } else {
355     status = device_manager->AcquireSpecificDevice(
356         capabilities.android_device_serial, &device);
357   }
358   if (status.IsError())
359     return status;
360
361   Switches switches(capabilities.switches);
362   for (size_t i = 0; i < arraysize(kCommonSwitches); ++i)
363     switches.SetSwitch(kCommonSwitches[i]);
364   switches.SetSwitch("disable-fre");
365   switches.SetSwitch("enable-remote-debugging");
366   status = device->SetUp(capabilities.android_package,
367                          capabilities.android_activity,
368                          capabilities.android_process,
369                          switches.ToString(),
370                          capabilities.android_use_running_app,
371                          port);
372   if (status.IsError()) {
373     device->TearDown();
374     return status;
375   }
376
377   scoped_ptr<DevToolsHttpClient> devtools_client;
378   status = WaitForDevToolsAndCheckVersion(NetAddress(port),
379                                           context_getter,
380                                           socket_factory,
381                                           &devtools_client);
382   if (status.IsError()) {
383     device->TearDown();
384     return status;
385   }
386
387   chrome->reset(new ChromeAndroidImpl(devtools_client.Pass(),
388                                       devtools_event_listeners,
389                                       port_reservation.Pass(),
390                                       device.Pass()));
391   return Status(kOk);
392 }
393
394 }  // namespace
395
396 Status LaunchChrome(
397     URLRequestContextGetter* context_getter,
398     const SyncWebSocketFactory& socket_factory,
399     DeviceManager* device_manager,
400     PortServer* port_server,
401     PortManager* port_manager,
402     const Capabilities& capabilities,
403     ScopedVector<DevToolsEventListener>& devtools_event_listeners,
404     scoped_ptr<Chrome>* chrome) {
405   if (capabilities.IsExistingBrowser()) {
406     return LaunchExistingChromeSession(
407         context_getter, socket_factory,
408         capabilities, devtools_event_listeners, chrome);
409   }
410
411   int port = 0;
412   scoped_ptr<PortReservation> port_reservation;
413   Status port_status(kOk);
414
415   if (capabilities.IsAndroid()) {
416     port_status = port_manager->ReservePortFromPool(&port, &port_reservation);
417     if (port_status.IsError())
418       return Status(kUnknownError, "cannot reserve port for Chrome",
419                     port_status);
420     return LaunchAndroidChrome(context_getter,
421                                port,
422                                port_reservation.Pass(),
423                                socket_factory,
424                                capabilities,
425                                devtools_event_listeners,
426                                device_manager,
427                                chrome);
428   } else {
429     if (port_server)
430       port_status = port_server->ReservePort(&port, &port_reservation);
431     else
432       port_status = port_manager->ReservePort(&port, &port_reservation);
433     if (port_status.IsError())
434       return Status(kUnknownError, "cannot reserve port for Chrome",
435                     port_status);
436     return LaunchDesktopChrome(context_getter,
437                                port,
438                                port_reservation.Pass(),
439                                socket_factory,
440                                capabilities,
441                                devtools_event_listeners,
442                                chrome);
443   }
444 }
445
446 namespace internal {
447
448 void ConvertHexadecimalToIDAlphabet(std::string* id) {
449   for (size_t i = 0; i < id->size(); ++i) {
450     int val;
451     if (base::HexStringToInt(base::StringPiece(id->begin() + i,
452                                                id->begin() + i + 1),
453                              &val)) {
454       (*id)[i] = val + 'a';
455     } else {
456       (*id)[i] = 'a';
457     }
458   }
459 }
460
461 std::string GenerateExtensionId(const std::string& input) {
462   uint8 hash[16];
463   crypto::SHA256HashString(input, hash, sizeof(hash));
464   std::string output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
465   ConvertHexadecimalToIDAlphabet(&output);
466   return output;
467 }
468
469 Status GetExtensionBackgroundPage(const base::DictionaryValue* manifest,
470                                   const std::string& id,
471                                   std::string* bg_page) {
472   std::string bg_page_name;
473   bool persistent = true;
474   manifest->GetBoolean("background.persistent", &persistent);
475   const base::Value* unused_value;
476   if (manifest->Get("background.scripts", &unused_value))
477     bg_page_name = "_generated_background_page.html";
478   manifest->GetString("background.page", &bg_page_name);
479   manifest->GetString("background_page", &bg_page_name);
480   if (bg_page_name.empty() || !persistent)
481     return Status(kOk);
482   *bg_page = "chrome-extension://" + id + "/" + bg_page_name;
483   return Status(kOk);
484 }
485
486 Status ProcessExtension(const std::string& extension,
487                         const base::FilePath& temp_dir,
488                         base::FilePath* path,
489                         std::string* bg_page) {
490   // Decodes extension string.
491   // Some WebDriver client base64 encoders follow RFC 1521, which require that
492   // 'encoded lines be no more than 76 characters long'. Just remove any
493   // newlines.
494   std::string extension_base64;
495   base::RemoveChars(extension, "\n", &extension_base64);
496   std::string decoded_extension;
497   if (!base::Base64Decode(extension_base64, &decoded_extension))
498     return Status(kUnknownError, "cannot base64 decode");
499
500   // If the file is a crx file, extract the extension's ID from its public key.
501   // Otherwise generate a random public key and use its derived extension ID.
502   std::string public_key;
503   std::string magic_header = decoded_extension.substr(0, 4);
504   if (magic_header.size() != 4)
505     return Status(kUnknownError, "cannot extract magic number");
506
507   const bool is_crx_file = magic_header == "Cr24";
508
509   if (is_crx_file) {
510     // Assume a CRX v2 file - see https://developer.chrome.com/extensions/crx.
511     std::string key_len_str = decoded_extension.substr(8, 4);
512     if (key_len_str.size() != 4)
513       return Status(kUnknownError, "cannot extract public key length");
514     uint32 key_len = *reinterpret_cast<const uint32*>(key_len_str.c_str());
515     public_key = decoded_extension.substr(16, key_len);
516     if (key_len != public_key.size())
517       return Status(kUnknownError, "invalid public key length");
518   } else {
519     // Not a CRX file. Generate RSA keypair to get a valid extension id.
520     scoped_ptr<crypto::RSAPrivateKey> key_pair(
521         crypto::RSAPrivateKey::Create(2048));
522     if (!key_pair)
523       return Status(kUnknownError, "cannot generate RSA key pair");
524     std::vector<uint8> public_key_vector;
525     if (!key_pair->ExportPublicKey(&public_key_vector))
526       return Status(kUnknownError, "cannot extract public key");
527     public_key =
528         std::string(reinterpret_cast<char*>(&public_key_vector.front()),
529                     public_key_vector.size());
530   }
531   std::string public_key_base64;
532   base::Base64Encode(public_key, &public_key_base64);
533   std::string id = GenerateExtensionId(public_key);
534
535   // Unzip the crx file.
536   base::ScopedTempDir temp_crx_dir;
537   if (!temp_crx_dir.CreateUniqueTempDir())
538     return Status(kUnknownError, "cannot create temp dir");
539   base::FilePath extension_crx = temp_crx_dir.path().AppendASCII("temp.crx");
540   int size = static_cast<int>(decoded_extension.length());
541   if (base::WriteFile(extension_crx, decoded_extension.c_str(), size) !=
542       size) {
543     return Status(kUnknownError, "cannot write file");
544   }
545   base::FilePath extension_dir = temp_dir.AppendASCII("extension_" + id);
546   if (!zip::Unzip(extension_crx, extension_dir))
547     return Status(kUnknownError, "cannot unzip");
548
549   // Parse the manifest and set the 'key' if not already present.
550   base::FilePath manifest_path(extension_dir.AppendASCII("manifest.json"));
551   std::string manifest_data;
552   if (!base::ReadFileToString(manifest_path, &manifest_data))
553     return Status(kUnknownError, "cannot read manifest");
554   scoped_ptr<base::Value> manifest_value(base::JSONReader::Read(manifest_data));
555   base::DictionaryValue* manifest;
556   if (!manifest_value || !manifest_value->GetAsDictionary(&manifest))
557     return Status(kUnknownError, "invalid manifest");
558
559   std::string manifest_key_base64;
560   if (manifest->GetString("key", &manifest_key_base64)) {
561     // If there is a key in both the header and the manifest, use the key in the
562     // manifest. This allows chromedriver users users who generate dummy crxs
563     // to set the manifest key and have a consistent ID.
564     std::string manifest_key;
565     if (!base::Base64Decode(manifest_key_base64, &manifest_key))
566       return Status(kUnknownError, "'key' in manifest is not base64 encoded");
567     std::string manifest_id = GenerateExtensionId(manifest_key);
568     if (id != manifest_id) {
569       if (is_crx_file) {
570         LOG(WARNING)
571             << "Public key in crx header is different from key in manifest"
572             << std::endl << "key from header:   " << public_key_base64
573             << std::endl << "key from manifest: " << manifest_key_base64
574             << std::endl << "generated extension id from header key:   " << id
575             << std::endl << "generated extension id from manifest key: "
576             << manifest_id;
577       }
578       id = manifest_id;
579     }
580   } else {
581     manifest->SetString("key", public_key_base64);
582     base::JSONWriter::Write(manifest, &manifest_data);
583     if (base::WriteFile(
584             manifest_path, manifest_data.c_str(), manifest_data.size()) !=
585         static_cast<int>(manifest_data.size())) {
586       return Status(kUnknownError, "cannot add 'key' to manifest");
587     }
588   }
589
590   // Get extension's background page URL, if there is one.
591   std::string bg_page_tmp;
592   Status status = GetExtensionBackgroundPage(manifest, id, &bg_page_tmp);
593   if (status.IsError())
594     return status;
595
596   *path = extension_dir;
597   if (bg_page_tmp.size())
598     *bg_page = bg_page_tmp;
599   return Status(kOk);
600 }
601
602 void UpdateExtensionSwitch(Switches* switches,
603                            const char name[],
604                            const base::FilePath::StringType& extension) {
605   base::FilePath::StringType value = switches->GetSwitchValueNative(name);
606   if (value.length())
607     value += FILE_PATH_LITERAL(",");
608   value += extension;
609   switches->SetSwitch(name, value);
610 }
611
612 Status ProcessExtensions(const std::vector<std::string>& extensions,
613                          const base::FilePath& temp_dir,
614                          bool include_automation_extension,
615                          Switches* switches,
616                          std::vector<std::string>* bg_pages) {
617   std::vector<std::string> bg_pages_tmp;
618   std::vector<base::FilePath::StringType> extension_paths;
619   for (size_t i = 0; i < extensions.size(); ++i) {
620     base::FilePath path;
621     std::string bg_page;
622     Status status = ProcessExtension(extensions[i], temp_dir, &path, &bg_page);
623     if (status.IsError()) {
624       return Status(
625           kUnknownError,
626           base::StringPrintf("cannot process extension #%" PRIuS, i + 1),
627           status);
628     }
629     extension_paths.push_back(path.value());
630     if (bg_page.length())
631       bg_pages_tmp.push_back(bg_page);
632   }
633
634   if (include_automation_extension) {
635     base::FilePath automation_extension;
636     Status status = UnpackAutomationExtension(temp_dir, &automation_extension);
637     if (status.IsError())
638       return status;
639     if (switches->HasSwitch("disable-extensions")) {
640       UpdateExtensionSwitch(switches, "load-component-extension",
641                             automation_extension.value());
642     } else {
643       extension_paths.push_back(automation_extension.value());
644     }
645   }
646
647   if (extension_paths.size()) {
648     base::FilePath::StringType extension_paths_value = JoinString(
649         extension_paths, FILE_PATH_LITERAL(','));
650     UpdateExtensionSwitch(switches, "load-extension", extension_paths_value);
651   }
652   bg_pages->swap(bg_pages_tmp);
653   return Status(kOk);
654 }
655
656 Status WritePrefsFile(
657     const std::string& template_string,
658     const base::DictionaryValue* custom_prefs,
659     const base::FilePath& path) {
660   int code;
661   std::string error_msg;
662   scoped_ptr<base::Value> template_value(base::JSONReader::ReadAndReturnError(
663           template_string, 0, &code, &error_msg));
664   base::DictionaryValue* prefs;
665   if (!template_value || !template_value->GetAsDictionary(&prefs)) {
666     return Status(kUnknownError,
667                   "cannot parse internal JSON template: " + error_msg);
668   }
669
670   if (custom_prefs) {
671     for (base::DictionaryValue::Iterator it(*custom_prefs); !it.IsAtEnd();
672          it.Advance()) {
673       prefs->Set(it.key(), it.value().DeepCopy());
674     }
675   }
676
677   std::string prefs_str;
678   base::JSONWriter::Write(prefs, &prefs_str);
679   VLOG(0) << "Populating " << path.BaseName().value()
680           << " file: " << PrettyPrintValue(*prefs);
681   if (static_cast<int>(prefs_str.length()) != base::WriteFile(
682           path, prefs_str.c_str(), prefs_str.length())) {
683     return Status(kUnknownError, "failed to write prefs file");
684   }
685   return Status(kOk);
686 }
687
688 Status PrepareUserDataDir(
689     const base::FilePath& user_data_dir,
690     const base::DictionaryValue* custom_prefs,
691     const base::DictionaryValue* custom_local_state) {
692   base::FilePath default_dir =
693       user_data_dir.AppendASCII(chrome::kInitialProfile);
694   if (!base::CreateDirectory(default_dir))
695     return Status(kUnknownError, "cannot create default profile directory");
696
697   Status status =
698       WritePrefsFile(kPreferences,
699                      custom_prefs,
700                      default_dir.Append(chrome::kPreferencesFilename));
701   if (status.IsError())
702     return status;
703
704   status = WritePrefsFile(kLocalState,
705                           custom_local_state,
706                           user_data_dir.Append(chrome::kLocalStateFilename));
707   if (status.IsError())
708     return status;
709
710   // Write empty "First Run" file, otherwise Chrome will wipe the default
711   // profile that was written.
712   if (base::WriteFile(
713           user_data_dir.Append(chrome::kFirstRunSentinel), "", 0) != 0) {
714     return Status(kUnknownError, "failed to write first run file");
715   }
716   return Status(kOk);
717 }
718
719 }  // namespace internal